diff options
9 files changed, 184 insertions, 3 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 } ] } 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/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 1f42fa8aab..29cac86c16 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..d38907d182 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ICodeReader.scala @@ -506,6 +506,12 @@ 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 + 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 +650,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 +669,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/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) + } +} |