diff options
-rw-r--r-- | bincompat-backward.whitelist.conf | 4 | ||||
-rw-r--r-- | bincompat-forward.whitelist.conf | 16 | ||||
-rw-r--r-- | build.xml | 12 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/icode/Members.scala | 1 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala | 19 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/opt/Inliners.scala | 1 | ||||
-rwxr-xr-x | src/compiler/scala/tools/nsc/doc/base/comment/Body.scala | 18 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala | 7 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala | 9 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/ClassfileConstants.scala | 5 | ||||
-rw-r--r-- | test/files/pos/t6210.flags | 1 | ||||
-rw-r--r-- | test/files/pos/t6210.scala | 21 | ||||
-rw-r--r-- | test/files/run/classfile-format-51.scala | 126 | ||||
-rw-r--r-- | test/scaladoc/run/SI-6580.check | 11 | ||||
-rw-r--r-- | test/scaladoc/run/SI-6580.scala | 32 |
15 files changed, 267 insertions, 16 deletions
diff --git a/bincompat-backward.whitelist.conf b/bincompat-backward.whitelist.conf index 4794666721..4f627780e6 100644 --- a/bincompat-backward.whitelist.conf +++ b/bincompat-backward.whitelist.conf @@ -158,6 +158,10 @@ filter { { matchName="scala.reflect.internal.Names#NameOps.name" problemName=MissingFieldProblem + }, + { + matchName="scala.reflect.internal.ClassfileConstants.xxxunusedxxxx" + problemName=MissingMethodProblem } ] } diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index 529fab1e14..76e189653b 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -354,6 +354,22 @@ filter { { matchName="scala.reflect.internal.StdNames#TermNames.SelectFromTypeTree" problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.ClassfileConstants.CONSTANT_INVOKEDYNAMIC" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.ClassfileConstants.invokedynamic" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.ClassfileConstants.CONSTANT_METHODHANDLE" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.ClassfileConstants.CONSTANT_METHODTYPE" + problemName=MissingMethodProblem } ] } @@ -44,8 +44,8 @@ targets exercised: <target name="distpack-maven" depends="dist.done, docs.done"> <ant antfile="${src.dir}/build/pack.xml" target="pack-maven.done" inheritall="yes" inheritrefs="yes"/></target> - <target name="distpack-opt" description="Builds an optimised distribution."> <optimize name="distpack"/></target> - <target name="distpack-maven-opt" description="Builds an optimised maven distribution."><optimize name="distpack-maven"/></target> + <target name="distpack-opt" description="Builds an optimised distribution."> <optimized name="distpack"/></target> + <target name="distpack-maven-opt" description="Builds an optimised maven distribution."><optimized name="distpack-maven"/></target> <target name="all.done" depends="dist.done, test.done"/> @@ -916,7 +916,7 @@ targets exercised: <do> <stopwatch name="docs.@{project}.timer"/> <mkdir dir="${build-docs.dir}/@{project}"/> - <if><equals arg1="@{classpathref}" arg2="NOT SET"/><then> + <if><equals arg1="@{docroot}" arg2="NOT SET"/><then> <scaladoc destdir="${build-docs.dir}/@{project}" doctitle="@{title}" @@ -1849,7 +1849,7 @@ targets exercised: </target> <target name="replacestarr-opt" description="Replaces the Starr compiler and library by fresh, optimised ones built from current sources and tests them."> - <optimize name="replacestarr"/></target> + <optimized name="replacestarr"/></target> <!-- Ant on Windows is not able to delete jar files that are referenced in any <path>. See ticket 1290 on trac. --> @@ -1866,13 +1866,13 @@ targets exercised: </target> <target name="replacestarrwin-opt" description="Creates a new Starr on Windows. Manually execute 'ant locker.clean build' first!"> - <optimize name="replacestarrwin"/></target> + <optimized name="replacestarrwin"/></target> <target name="replacelocker" description="Replaces the Locker compiler and library by fresh ones built from current sources." depends="palo.clean, locker.unlock, palo.done"/> <target name="replacelocker-opt" description="Replaces the Locker compiler and library by fresh, optimised ones built from current sources."> - <optimize name="replacelocker"/></target> + <optimized name="replacelocker"/></target> <target name="buildlocker" description="Does the same for locker as build does for quick." depends="locker.unlock, palo.bin"/> <target name="unlocklocker" description="Same as buildlocker." depends="buildlocker"/> <!-- REMOVE --> diff --git a/src/compiler/scala/tools/nsc/backend/icode/Members.scala b/src/compiler/scala/tools/nsc/backend/icode/Members.scala index 7ba212f42e..b74770f051 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Members.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Members.scala @@ -171,6 +171,7 @@ trait Members { var returnType: TypeKind = _ var recursive: Boolean = false var bytecodeHasEHs = false // set by ICodeReader only, used by Inliner to prevent inlining (SI-6188) + var bytecodeHasInvokeDynamic = false // set by ICodeReader only, used by Inliner to prevent inlining until we have proper invoke dynamic support /** local variables and method parameters */ var locals: List[Local] = Nil diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala index 8c9a72638d..a3a0edb35d 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala @@ -409,6 +409,25 @@ trait Opcodes { self: ICodes => override def category = mthdsCat } + + /** + * A place holder entry that allows us to parse class files with invoke dynamic + * instructions. Because the compiler doesn't yet really understand the + * behavior of invokeDynamic, this op acts as a poison pill. Any attempt to analyze + * this instruction will cause a failure. The only optimization that + * should ever look at non-Scala generated icode is the inliner, and it + * has been modified to not examine any method with invokeDynamic + * instructions. So if this poison pill ever causes problems then + * there's been a serious misunderstanding + */ + // TODO do the real thing + case class INVOKE_DYNAMIC(poolEntry: Char) extends Instruction { + private def error = sys.error("INVOKE_DYNAMIC is not fully implemented and should not be analyzed") + override def consumed = error + override def produced = error + override def producedTypes = error + override def category = error + } case class BOX(boxType: TypeKind) extends Instruction { assert(boxType.isValueType && (boxType ne UNIT)) // documentation diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala index 521b6cc132..498db78636 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -961,6 +961,7 @@ abstract class Inliners extends SubComponent { if(isInlineForbidden) { rs ::= "is annotated @noinline" } if(inc.isSynchronized) { rs ::= "is synchronized method" } if(inc.m.bytecodeHasEHs) { rs ::= "bytecode contains exception handlers / finally clause" } // SI-6188 + if(inc.m.bytecodeHasInvokeDynamic) { rs ::= "bytecode contains invoke dynamic" } if(rs.isEmpty) null else rs.mkString("", ", and ", "") } diff --git a/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala index 02e662da85..eb0d751f3e 100755 --- a/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala +++ b/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala @@ -75,16 +75,20 @@ object EntityLink { def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link)) } final case class HtmlTag(data: String) extends Inline { - def canClose(open: HtmlTag) = { - open.data.stripPrefix("<") == data.stripPrefix("</") + private val Pattern = """(?ms)\A<(/?)(.*?)[\s>].*\z""".r + private val (isEnd, tagName) = data match { + case Pattern(s1, s2) => + (! s1.isEmpty, Some(s2.toLowerCase)) + case _ => + (false, None) } - def close = { - if (data.indexOf("</") == -1) - Some(HtmlTag("</" + data.stripPrefix("<"))) - else - None + def canClose(open: HtmlTag) = { + isEnd && tagName == open.tagName } + + private val TagsNotToClose = Set("br", "img") + def close = tagName collect { case name if !TagsNotToClose(name) => HtmlTag(s"</$name>") } } /** The summary of a comment, usually its first sentence. There must be exactly one summary per body. */ diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 7f822e1c5d..fb2301de65 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -137,10 +137,13 @@ abstract class ClassfileParser { (in.nextByte.toInt: @switch) match { case CONSTANT_UTF8 | CONSTANT_UNICODE => in.skip(in.nextChar) - case CONSTANT_CLASS | CONSTANT_STRING => + case CONSTANT_CLASS | CONSTANT_STRING | CONSTANT_METHODTYPE=> in.skip(2) + case CONSTANT_METHODHANDLE => + in.skip(3) case CONSTANT_FIELDREF | CONSTANT_METHODREF | CONSTANT_INTFMETHODREF - | CONSTANT_NAMEANDTYPE | CONSTANT_INTEGER | CONSTANT_FLOAT => + | CONSTANT_NAMEANDTYPE | CONSTANT_INTEGER | CONSTANT_FLOAT + | CONSTANT_INVOKEDYNAMIC => in.skip(4) case CONSTANT_LONG | CONSTANT_DOUBLE => in.skip(8) diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala index 13c0d8993a..c304c18c4f 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala @@ -506,6 +506,13 @@ abstract class ICodeReader extends ClassfileParser { code.emit(UNBOX(toTypeKind(m.info.resultType))) else code.emit(CALL_METHOD(m, Static(false))) + case JVM.invokedynamic => + // TODO, this is just a place holder. A real implementation must parse the class constant entry + debuglog("Found JVM invokedynamic instructionm, inserting place holder ICode INVOKE_DYNAMIC.") + containsInvokeDynamic = true + val poolEntry = in.nextChar + in.skip(2) + code.emit(INVOKE_DYNAMIC(poolEntry)) case JVM.new_ => code.emit(NEW(REFERENCE(pool.getClassSymbol(in.nextChar)))) @@ -644,6 +651,7 @@ abstract class ICodeReader extends ClassfileParser { var containsDUPX = false var containsNEW = false var containsEHs = false + var containsInvokeDynamic = false def emit(i: Instruction) { instrs += ((pc, i)) @@ -662,6 +670,7 @@ abstract class ICodeReader extends ClassfileParser { val code = new Code(method) method.setCode(code) method.bytecodeHasEHs = containsEHs + method.bytecodeHasInvokeDynamic = containsInvokeDynamic var bb = code.startBlock def makeBasicBlocks: mutable.Map[Int, BasicBlock] = diff --git a/src/reflect/scala/reflect/internal/ClassfileConstants.scala b/src/reflect/scala/reflect/internal/ClassfileConstants.scala index 7ccb661426..c198271fb1 100644 --- a/src/reflect/scala/reflect/internal/ClassfileConstants.scala +++ b/src/reflect/scala/reflect/internal/ClassfileConstants.scala @@ -72,6 +72,9 @@ object ClassfileConstants { final val CONSTANT_METHODREF = 10 final val CONSTANT_INTFMETHODREF = 11 final val CONSTANT_NAMEANDTYPE = 12 + final val CONSTANT_METHODHANDLE = 15 + final val CONSTANT_METHODTYPE = 16 + final val CONSTANT_INVOKEDYNAMIC = 18 // tags describing the type of a literal in attribute values final val BYTE_TAG = 'B' @@ -306,7 +309,7 @@ object ClassfileConstants { final val invokespecial = 0xb7 final val invokestatic = 0xb8 final val invokeinterface = 0xb9 - final val xxxunusedxxxx = 0xba + final val invokedynamic = 0xba final val new_ = 0xbb final val newarray = 0xbc diff --git a/test/files/pos/t6210.flags b/test/files/pos/t6210.flags new file mode 100644 index 0000000000..e8fb65d50c --- /dev/null +++ b/test/files/pos/t6210.flags @@ -0,0 +1 @@ +-Xfatal-warnings
\ No newline at end of file diff --git a/test/files/pos/t6210.scala b/test/files/pos/t6210.scala new file mode 100644 index 0000000000..1ce8493872 --- /dev/null +++ b/test/files/pos/t6210.scala @@ -0,0 +1,21 @@ +abstract sealed trait AST +abstract sealed trait AExpr extends AST +case class AAssign(name: String, v: AExpr) extends AExpr +case class AConstBool(v: Boolean) extends AExpr + +trait Ty {} +case class TInt() extends Ty +case class TBool() extends Ty + +object Foo { + def checkExpr(ast: AExpr): Ty = { + var astTy:Ty = ast match { + case AAssign(nm: String, v:AExpr) => TBool() + + case AConstBool(v: Boolean) => TBool() + + case _ => throw new Exception(s"Unhandled case check(ast: ${ast.getClass})") + } + astTy + } +} diff --git a/test/files/run/classfile-format-51.scala b/test/files/run/classfile-format-51.scala new file mode 100644 index 0000000000..9b1e612f4f --- /dev/null +++ b/test/files/run/classfile-format-51.scala @@ -0,0 +1,126 @@ +import java.io.{File, FileOutputStream} + +import scala.tools.nsc.settings.ScalaVersion +import scala.tools.partest._ +import scala.tools.asm +import asm.{AnnotationVisitor, ClassWriter, FieldVisitor, Handle, MethodVisitor, Opcodes} +import Opcodes._ + +// This test ensures that we can read JDK 7 (classfile format 51) files, including those +// with invokeDynamic instructions and associated constant pool entries +// to do that it first uses ASM to generate a class called DynamicInvoker. Then +// it runs a normal compile on the source in the 'code' field that refers to +// DynamicInvoker. Any failure will be dumped to std out. +// +// By it's nature the test can only work on JDK 7+ because under JDK 6 some of the +// classes referred to by DynamicInvoker won't be available and DynamicInvoker won't +// verify. So the test includes a version check that short-circuites the whole test +// on JDK 6 +object Test extends DirectTest { + override def extraSettings: String = "-optimise -usejavacp -d " + testOutput.path + " -cp " + testOutput.path + + def generateClass() { + val invokerClassName = "DynamicInvoker" + val bootstrapMethodName = "bootstrap" + val bootStrapMethodType = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;" + val targetMethodName = "target" + val targetMethodType = "()Ljava/lang/String;" + + val cw = new ClassWriter(0) + cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, invokerClassName, null, "java/lang/Object", null) + + val constructor = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null) + constructor.visitCode() + constructor.visitVarInsn(ALOAD, 0) + constructor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V") + constructor.visitInsn(RETURN) + constructor.visitMaxs(1, 1) + constructor.visitEnd() + + val target = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, targetMethodName, targetMethodType, null, null) + target.visitCode() + target.visitLdcInsn("hello") + target.visitInsn(ARETURN) + target.visitMaxs(1, 1) + target.visitEnd() + + val bootstrap = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, bootstrapMethodName, bootStrapMethodType, null, null) + bootstrap.visitCode() +// val lookup = MethodHandles.lookup(); + bootstrap.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;") + bootstrap.visitVarInsn(ASTORE, 3) // lookup + +// val clazz = lookup.lookupClass(); + bootstrap.visitVarInsn(ALOAD, 3) // lookup + bootstrap.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "lookupClass", "()Ljava/lang/Class;") + bootstrap.visitVarInsn(ASTORE, 4) // clazz + +// val methodType = MethodType.fromMethodDescriptorString("()Ljava/lang/String, clazz.getClassLoader()") + bootstrap.visitLdcInsn("()Ljava/lang/String;") + bootstrap.visitVarInsn(ALOAD, 4) // CLAZZ + bootstrap.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;") + bootstrap.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodType", "fromMethodDescriptorString", "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;") + bootstrap.visitVarInsn(ASTORE, 5) // methodType + +// val methodHandle = lookup.findStatic(thisClass, "target", methodType) + bootstrap.visitVarInsn(ALOAD, 3) // lookup + bootstrap.visitVarInsn(ALOAD, 4) // clazz + bootstrap.visitLdcInsn("target") + bootstrap.visitVarInsn(ALOAD, 5) // methodType + bootstrap.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;") + bootstrap.visitVarInsn(ASTORE, 6) // methodHandle + +// new ConstantCallSite(methodHandle) + bootstrap.visitTypeInsn(NEW, "java/lang/invoke/ConstantCallSite") + bootstrap.visitInsn(DUP) + bootstrap.visitVarInsn(ALOAD, 6) // methodHandle + bootstrap.visitMethodInsn(INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "<init>", "(Ljava/lang/invoke/MethodHandle;)V") + bootstrap.visitInsn(ARETURN) + bootstrap.visitMaxs(4,7) + bootstrap.visitEnd() + + val test = cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "test", s"()Ljava/lang/String;", null, null) + test.visitCode() + val bootstrapHandle = new Handle(H_INVOKESTATIC, invokerClassName, bootstrapMethodName, bootStrapMethodType) + test.visitInvokeDynamicInsn("invoke", targetMethodType, bootstrapHandle) + test.visitInsn(ARETURN) + test.visitMaxs(1, 1) + test.visitEnd() + + cw.visitEnd() + val bytes = cw.toByteArray() + + val fos = new FileOutputStream(new File(s"${testOutput.path}/$invokerClassName.class")) + try + fos write bytes + finally + fos.close() + + } + + def code = +""" +object Driver { + val invoker = new DynamicInvoker() + println(invoker.test()) +} +""" + + override def show(): Unit = { + // redirect err to out, for logging + val prevErr = System.err + System.setErr(System.out) + try { + // this test is only valid under JDK 1.7+ + // cheat a little by using 'ScalaVersion' because it can parse java versions just as well + val requiredJavaVersion = ScalaVersion("1.7") + val executingJavaVersion = ScalaVersion(System.getProperty("java.specification.version")) + if (executingJavaVersion >= requiredJavaVersion) { + generateClass() + compile() + } + } + finally + System.setErr(prevErr) + } +} diff --git a/test/scaladoc/run/SI-6580.check b/test/scaladoc/run/SI-6580.check new file mode 100644 index 0000000000..2fb6ec3258 --- /dev/null +++ b/test/scaladoc/run/SI-6580.check @@ -0,0 +1,11 @@ +Chain(List(Chain(List(Text(Here z(1) is defined as follows:), Text( +), HtmlTag(<br>), Text( +), Text( ), HtmlTag(<img src='http://example.com/fig1.png'>), Text( +), HtmlTag(<br>), Text( +), Text(plus z(1) times), Text( +), HtmlTag(<br>), Text( +), Text( ), HtmlTag(<img src='http://example.com/fig2.png'>), Text( +), HtmlTag(<br>), Text( +), Text(equals QL of something +))))) +Done. diff --git a/test/scaladoc/run/SI-6580.scala b/test/scaladoc/run/SI-6580.scala new file mode 100644 index 0000000000..c544138f44 --- /dev/null +++ b/test/scaladoc/run/SI-6580.scala @@ -0,0 +1,32 @@ +import scala.tools.nsc.doc +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.html.page.{Index, ReferenceIndex} +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + override def scaladocSettings = "" + override def code = """ + + object Test { + /** Here z(1) is defined as follows: + * <br> + * <img src='http://example.com/fig1.png'> + * <br> + * plus z(1) times + * <br> + * <img src='http://example.com/fig2.png'> + * <br> + * equals QL of something + */ + def f = 1 + } + + """ + + def testModel(rootPackage: Package) { + import access._ + + val f = rootPackage._object("Test")._method("f") + println(f.comment.get.short) + } +} |