Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Read version 51 (JDK 7) class files.
This commit makes the ClassFileReader/ICodeReader parse class files from JDK 7 (class file version 51). It does that by skipping over the method handle related entries in the constant pool and by doing some dummy processing on invoke dynamic instructions. The inliner is updated to not try to inline a method with an invoke dynamic instruction. A place holder INVOKE_DYNAMIC instruction is added to ICode but it is designed to create an error if there's ever any attempt to analyze it. Because the inliner is the only phase that ever tries to analyze ICode instructions not generated from Scala source and because Scala source will never emit an INVOKE_DYNAMIC, the place holder INVOKE_DYNAMIC should never cause any errors. A test is included that generates a class file with INVOKE_DYNAMIC and then compiles Scala code that depends on it.
- Loading branch information
Showing
9 changed files
with
184 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |