Skip to content

Commit fc6da8d

Browse files
author
James Iry
committed
Test for reading JDK 8 (classfile format 52) class files.
This commit includes a test for reading JDK 8 (classfile format 52) class files, in particular default (aka defender) methods. It uses ASM to generate an interface with default methods then exercises that interface from Scala. Surprisingly no changes are necessary to the Scala code base to support reading format 52 class files. Because the test can only run under JDK 8, the JDK version is checked and the expected output is synthesized for previous versions.
1 parent 9eb63c5 commit fc6da8d

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
hello from publicMethod
2+
hello from staticMethod
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import java.io.{File, FileOutputStream}
2+
3+
import scala.tools.nsc.settings.ScalaVersion
4+
import scala.tools.partest._
5+
import scala.tools.asm
6+
import asm.{AnnotationVisitor, ClassWriter, FieldVisitor, Handle, MethodVisitor, Opcodes}
7+
import Opcodes._
8+
9+
// This test ensures that we can read JDK 8 (classfile format 52) files, including those
10+
// with default methods. To do that it first uses ASM to generate an interface called
11+
// HasDefaultMethod. Then it runs a normal compile on Scala source that extends that
12+
// interface. Any failure will be dumped to std out.
13+
//
14+
// By it's nature the test can only work on JDK 8+ because under JDK 7- the
15+
// interface won't verify.
16+
object Test extends DirectTest {
17+
override def extraSettings: String = "-optimise -usejavacp -d " + testOutput.path + " -cp " + testOutput.path
18+
19+
def generateInterface() {
20+
val interfaceName = "HasDefaultMethod"
21+
val methodType = "()Ljava/lang/String;"
22+
23+
val cw = new ClassWriter(0)
24+
cw.visit(52, ACC_PUBLIC+ACC_ABSTRACT+ACC_INTERFACE, interfaceName, null, "java/lang/Object", null)
25+
26+
def createMethod(flags:Int, name: String) {
27+
val method = cw.visitMethod(flags, name, methodType, null, null)
28+
method.visitCode()
29+
method.visitLdcInsn(s"hello from $name")
30+
method.visitInsn(ARETURN)
31+
method.visitMaxs(1, 1)
32+
method.visitEnd()
33+
}
34+
35+
createMethod(ACC_PUBLIC, "publicMethod")
36+
createMethod(ACC_PUBLIC+ACC_STATIC, "staticMethod")
37+
createMethod(ACC_PRIVATE, "privateMethod")
38+
39+
cw.visitEnd()
40+
val bytes = cw.toByteArray()
41+
42+
val fos = new FileOutputStream(new File(s"${testOutput.path}/$interfaceName.class"))
43+
try
44+
fos write bytes
45+
finally
46+
fos.close()
47+
48+
}
49+
50+
def code =
51+
"""
52+
class Driver extends HasDefaultMethod {
53+
println(publicMethod())
54+
println(HasDefaultMethod.staticMethod())
55+
}
56+
"""
57+
58+
override def show(): Unit = {
59+
// redirect err to out, for logging
60+
val prevErr = System.err
61+
System.setErr(System.out)
62+
try {
63+
// this test is only valid under JDK 1.8+
64+
// cheat a little by using 'ScalaVersion' because it can parse java versions just as well
65+
val requiredJavaVersion = ScalaVersion("1.8")
66+
val executingJavaVersion = ScalaVersion(System.getProperty("java.specification.version"))
67+
if (executingJavaVersion >= requiredJavaVersion) {
68+
generateInterface()
69+
compile()
70+
Class.forName("Driver").newInstance()
71+
} else {
72+
// under other versions just dump the expected results
73+
println("hello from publicMethod")
74+
println("hello from staticMethod")
75+
}
76+
}
77+
finally
78+
System.setErr(prevErr)
79+
}
80+
}

0 commit comments

Comments
 (0)