Skip to content

Commit 8e13c80

Browse files
committed
package testpackage;
public interface MyInterface { default String def() { return "[" + toString() + ", " + "def]"; } default String join(MyInterface other) { return def() + other.def(); } } class C implements MyInterface{} compiles to: public class testpackage.MyClass implements testpackage.MyInterface { public testpackage.MyClass(); Code: 0: aload_0 1: invokespecial luontola#16 // Method java/lang/Object."<init>":()V 4: return public java.lang.String join(testpackage.MyInterface); Code: 0: aload_0 1: aload_1 2: invokestatic luontola#46 // Method testpackage/MyInterfacehelper.join:(Ltestpackage/MyInterface;Ltestpackage/MyInterface;)Ljava/lang/String; 5: areturn public java.lang.String def(); Code: 0: aload_0 1: invokestatic luontola#49 // Method testpackage/MyInterfacehelper.def:(Ltestpackage/MyInterface;)Ljava/lang/String; 4: areturn } Where testpackage.MyInterfacehelper is compiled to public class testpackage.MyInterfacehelper { private testpackage.MyInterfacehelper(); Code: 0: aload_0 1: invokespecial luontola#9 // Method java/lang/Object."<init>":()V 4: return public static java.lang.String def(testpackage.MyInterface); Code: 0: new luontola#13 // class java/lang/StringBuilder 3: dup 4: invokespecial luontola#14 // Method java/lang/StringBuilder."<init>":()V 7: ldc luontola#16 // String [ 9: invokevirtual luontola#20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: invokevirtual luontola#24 // Method java/lang/Object.toString:()Ljava/lang/String; 16: invokevirtual luontola#20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc luontola#26 // String , 21: invokevirtual luontola#20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: ldc luontola#28 // String def] 26: invokevirtual luontola#20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 29: invokevirtual luontola#29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 32: areturn public static java.lang.String join(testpackage.MyInterface, testpackage.MyInterface); Code: 0: new luontola#13 // class java/lang/StringBuilder 3: dup 4: invokespecial luontola#14 // Method java/lang/StringBuilder."<init>":()V 7: aload_0 8: invokeinterface luontola#35, 1 // InterfaceMethod testpackage/MyInterface.def:()Ljava/lang/String; 13: invokevirtual luontola#20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 16: aload_1 17: invokeinterface luontola#35, 1 // InterfaceMethod testpackage/MyInterface.def:()Ljava/lang/String; 22: invokevirtual luontola#20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 25: invokevirtual luontola#29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 28: areturn } ============== interface StaticTest { static <T> T staticMethod(T t) { return t; } } compiles to public class testpackage.StaticTesthelper { private testpackage.StaticTesthelper(); Code: 0: aload_0 1: invokespecial luontola#9 // Method java/lang/Object."<init>":()V 4: return public static <T> T staticMethod$static(T); Code: 0: aload_0 1: areturn } ======== Brigde methods are generated properly in example: public interface BridgeParent<T> { T get(); } public interface StringBridges extends BridgeParent<String> { @OverRide default String get() { return "default method"; } default String concrete() { return "concrete"; } } public class testpackage.StringBridgeshelper SourceFile: "testpackage/StringBridges.java" minor version: 0 major version: 50 flags: ACC_PUBLIC, ACC_SUPER Constant pool: luontola#1 = Utf8 testpackage/StringBridgeshelper luontola#2 = Class luontola#1 // testpackage/StringBridgeshelper luontola#3 = Utf8 java/lang/Object luontola#4 = Class luontola#3 // java/lang/Object luontola#5 = Utf8 testpackage/StringBridges.java luontola#6 = Utf8 <init> luontola#7 = Utf8 ()V luontola#8 = NameAndType luontola#6:luontola#7 // "<init>":()V luontola#9 = Methodref luontola#4.luontola#8 // java/lang/Object."<init>":()V luontola#10 = Utf8 get luontola#11 = Utf8 (Ltestpackage/StringBridges;)Ljava/lang/String; luontola#12 = Utf8 default method luontola#13 = String luontola#12 // default method luontola#14 = Utf8 concrete luontola#15 = String luontola#14 // concrete luontola#16 = Utf8 (Ltestpackage/StringBridges;)Ljava/lang/Object; luontola#17 = Utf8 testpackage/StringBridges luontola#18 = Class luontola#17 // testpackage/StringBridges luontola#19 = Utf8 ()Ljava/lang/String; luontola#20 = NameAndType luontola#10:luontola#19 // get:()Ljava/lang/String; luontola#21 = InterfaceMethodref luontola#18.luontola#20 // testpackage/StringBridges.get:()Ljava/lang/String; luontola#22 = Utf8 Code luontola#23 = Utf8 LineNumberTable luontola#24 = Utf8 SourceFile { private testpackage.StringBridgeshelper(); descriptor: ()V flags: ACC_PRIVATE Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial luontola#9 // Method java/lang/Object."<init>":()V 4: return public static java.lang.String get(testpackage.StringBridges); descriptor: (Ltestpackage/StringBridges;)Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: ldc luontola#13 // String default method 2: areturn LineNumberTable: line 9: 0 public static java.lang.String concrete(testpackage.StringBridges); descriptor: (Ltestpackage/StringBridges;)Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: ldc luontola#15 // String concrete 2: areturn LineNumberTable: line 13: 0 public static java.lang.Object get(testpackage.StringBridges); descriptor: (Ltestpackage/StringBridges;)Ljava/lang/Object; flags: ACC_PUBLIC, ACC_STATIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokeinterface luontola#21, 1 // InterfaceMethod testpackage/StringBridges.get:()Ljava/lang/String; 6: areturn LineNumberTable: line 6: 0 }
1 parent 374c4f2 commit 8e13c80

File tree

4 files changed

+252
-1
lines changed

4 files changed

+252
-1
lines changed

retrolambda/pom.xml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
<packaging>jar</packaging>
1515

1616
<dependencies>
17+
<dependency>
18+
<groupId>org.scala-lang</groupId>
19+
<artifactId>scala-library</artifactId>
20+
<version>2.11.2</version>
21+
</dependency>
1722

1823
<dependency>
1924
<groupId>org.ow2.asm</groupId>
@@ -70,7 +75,21 @@
7075
</filters>
7176
</configuration>
7277
</plugin>
73-
78+
<plugin>
79+
<groupId>net.alchim31.maven</groupId>
80+
<artifactId>scala-maven-plugin</artifactId>
81+
<version>3.2.0</version>
82+
<executions>
83+
<execution>
84+
<id>scala-compile-first</id>
85+
<phase>process-resources</phase>
86+
<goals>
87+
<goal>add-source</goal>
88+
<goal>compile</goal>
89+
</goals>
90+
</execution>
91+
</executions>
92+
</plugin>
7493
</plugins>
7594
</build>
7695

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Created by arneball on 2014-07-29.
3+
*/
4+
5+
import java.lang.reflect.{Method, Modifier}
6+
import java.net.{URL, URLClassLoader}
7+
import java.nio.file._
8+
import java.nio.file.attribute.BasicFileAttributes
9+
import org.objectweb.asm._
10+
import collection.JavaConversions._
11+
12+
object AsmTest {
13+
lazy val (output: Path, cp: Array[URL], input: Path, bytecodeVersion: Int, ucl: URLClassLoader) = {
14+
val List(_input, _output, _cp, _bytecode) = List("inputDir", "outputDir", "classpath", "bytecodeVersion").map{ name =>
15+
System.getProperties.getProperty("retrometh." + name)
16+
}
17+
val byteCodeVersion = _bytecode match {
18+
case "1.7" | "7" | "51" => Opcodes.V1_7
19+
case "1.6" | "6" | "50" => Opcodes.V1_6
20+
case "1.5" | "5" | "49" => Opcodes.V1_5
21+
case _ => throw new Exception("BytecodeVersion must be 1.6, 6, 1.7 or 7")
22+
}
23+
val input = Paths.get(_input)
24+
val output = Option(_output).map{ Paths.get(_) }.getOrElse(input)
25+
26+
val cp = _cp.split(System.getProperty("path.separator")).map{ p =>
27+
Paths.get(p).toUri.toURL
28+
} :+ input.toUri.toURL
29+
Implicits.println(s"Input: $input, Output: $output, classpath: ${cp.mkString}")
30+
(output, cp, input, byteCodeVersion, new URLClassLoader(cp))
31+
}
32+
33+
def main(args: Array[String]): Unit = {
34+
Files.walkFileTree(input, new SimpleFileVisitor[Path] {
35+
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = file.toString.toLowerCase.endsWith("class") match {
36+
case true =>
37+
println("Found class file " + file)
38+
val bytecode = Files.readAllBytes(file)
39+
val wr = new ClassWriter(ClassWriter.COMPUTE_FRAMES)
40+
val stage1 = new InterfaceModifier(wr, bytecodeVersion)
41+
val stage2 = new ClassModifier(stage1, bytecodeVersion)
42+
val reader = new ClassReader(bytecode).accept(stage2, 0);
43+
val outputFile = output.resolve(input.relativize(file));
44+
Files.createDirectories(outputFile.getParent());
45+
Files.write(outputFile, wr.toByteArray);
46+
FileVisitResult.CONTINUE
47+
case that =>
48+
FileVisitResult.CONTINUE
49+
}
50+
})
51+
}
52+
}
53+
54+
object Implicits {
55+
def println(str: String) = Predef.println("KALLE " + str)
56+
val Sig = raw"\((.*)\)(.*)".r
57+
implicit class StrWr(val str: String) extends AnyVal {
58+
def addParam(cl: String) = str match {
59+
case Sig(content, returntype) =>
60+
val tmp = s"(L${cl};$content)$returntype"
61+
println(s"Before $str, now $tmp")
62+
tmp
63+
}
64+
def getInternalClass = AsmTest.ucl.loadClass(str.replace("/", "."))
65+
}
66+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import java.lang.reflect.{Method, Modifier}
2+
3+
import Implicits._
4+
import org.objectweb.asm.Opcodes._
5+
import org.objectweb.asm.Type._
6+
import org.objectweb.asm._
7+
8+
import scala.collection.mutable.ArrayBuffer
9+
10+
case class MethodContainer(methodName: String, methodDesc: String, interface: String, signature: String)
11+
class ClassModifier(visitor: ClassVisitor, byteCodeVersion: Int = V1_6) extends ClassVisitor(ASM5, visitor) {
12+
13+
var methodsToOverride: List[MethodContainer] = Nil
14+
var className: String = _
15+
type MethodDesc = (String, String)
16+
var implementedMethods = ArrayBuffer[MethodDesc]()
17+
18+
override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) = {
19+
val isClass = (access & ACC_INTERFACE) == 0
20+
if(isClass) {
21+
val defaultMethods = for {
22+
interface <- interfaces
23+
cl = interface.getInternalClass
24+
m @ DefaultMethod() <- cl.getMethods
25+
} yield MethodContainer(m.getName, Type.getMethodDescriptor(m), interface, signature)
26+
println(s"Default methods: ${defaultMethods.mkString(",")}")
27+
methodsToOverride = defaultMethods.toList
28+
className = name
29+
}
30+
super.visit(byteCodeVersion, access, name, signature, superName, interfaces)
31+
}
32+
33+
// gotta keep track of overriden default methods
34+
override def visitMethod(access: Int, name: String, desc: String, signature: String, exceptions: Array[String]): MethodVisitor = {
35+
implementedMethods += name -> desc
36+
new InterfaceToHelperRewriter(super.visitMethod(access, name, desc, signature, exceptions))
37+
}
38+
39+
override def visitEnd() = {
40+
def createProxy(l: MethodContainer) = l match {
41+
case MethodContainer(name, desc, interface, signature) if !implementedMethods.contains(name -> desc) =>
42+
val tmp = super.visitMethod(ACC_PUBLIC, name, desc, signature, null)
43+
tmp.visitVarInsn(ALOAD, 0)
44+
Type.getArgumentTypes(desc).zipWithIndex.foreach{
45+
case (PrimitiveLoad(instruction), i) => tmp.visitVarInsn(instruction, i + 1)
46+
case (_, i) => tmp.visitVarInsn(ALOAD, i + 1)
47+
}
48+
tmp.visitMethodInsn(INVOKESTATIC, interface + "helper", name, desc.addParam(interface), false)
49+
Type.getReturnType(desc) match {
50+
case ReturnIns(ins) => tmp.visitInsn(ins)
51+
case otherwise: Type => tmp.visitInsn(ARETURN)
52+
}
53+
tmp.visitMaxs(0, 0)
54+
tmp.visitEnd()
55+
case _ => // noop
56+
}
57+
methodsToOverride.foreach{ createProxy }
58+
super.visitEnd()
59+
}
60+
}
61+
62+
class InterfaceToHelperRewriter(mv: MethodVisitor) extends MethodVisitor(Opcodes.ASM5, mv) {
63+
override def visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, isInterface: Boolean) = opcode match {
64+
case INVOKESPECIAL if isInterface =>
65+
super.visitMethodInsn(INVOKESTATIC, owner + "helper", name, desc.addParam(owner), false)
66+
case INVOKESTATIC if isInterface =>
67+
super.visitMethodInsn(INVOKESTATIC, owner + "helper", name + "$static", desc, false)
68+
case _ =>
69+
super.visitMethodInsn(opcode, owner, name, desc, isInterface)
70+
}
71+
}
72+
73+
object DefaultMethod {
74+
def unapply(m: Method) = !Modifier.isAbstract(m.getModifiers)
75+
}
76+
77+
object InstructorExtractor {
78+
val (loadMap, returnMap) = {
79+
val types = List(INT_TYPE, LONG_TYPE, FLOAT_TYPE, DOUBLE_TYPE, VOID_TYPE)
80+
val loads = List(ILOAD, LLOAD, FLOAD, DLOAD, -1) // omg a null
81+
val rets = List(IRETURN, LRETURN, FRETURN, DRETURN, RETURN)
82+
val mkMap = types.zip(_: List[Int]).toMap
83+
mkMap(loads) -> mkMap(rets)
84+
}
85+
}
86+
87+
class InstructorExtractor(m: Map[Type, Int]) {
88+
def unapply(f: Type) = m.get(f)
89+
}
90+
object PrimitiveLoad extends InstructorExtractor(InstructorExtractor.loadMap)
91+
object ReturnIns extends InstructorExtractor(InstructorExtractor.returnMap)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import java.nio.file.Files
2+
3+
import Implicits._
4+
import org.objectweb.asm.Opcodes._
5+
import org.objectweb.asm._
6+
7+
import scala.collection.{mutable => m}
8+
9+
class InterfaceModifier(classWriter: ClassWriter, targetByteCode: Int = Opcodes.V1_6) extends ClassVisitor(ASM5, classWriter) with Opcodes {
10+
private var cName: String = _
11+
private var isInterface = false
12+
private lazy val helperClassVisitor = mkHelperClass
13+
private lazy val helperClassName = cName + "helper"
14+
15+
override def visitMethod(access: Int, name: String, desc: String, signature: String, exceptions: Array[String]): MethodVisitor = {
16+
val methConcrete = (ACC_ABSTRACT & access) == 0
17+
val isStatic = (ACC_STATIC & access) != 0
18+
(methConcrete, isInterface, isStatic) match {
19+
case (true, true, false) => // concrete interface method
20+
super.visitMethod(access | ACC_ABSTRACT, name, desc, signature, exceptions)
21+
val tmp = helperClassVisitor.visitMethod(access | ACC_STATIC, name, desc.addParam(cName), signature, exceptions)
22+
new BodyStripper(tmp)
23+
case (true, true, true) => // static interface method
24+
helperClassVisitor.visitMethod(access, name + "$static", desc, signature, exceptions)
25+
case _ =>
26+
super.visitMethod(access, name, desc, signature, exceptions)
27+
}
28+
}
29+
30+
override def visitEnd() = {
31+
val newPath = AsmTest.output.resolve(helperClassName + ".class")
32+
println("CREATING HELPER AT " + newPath)
33+
helperClassVisitor.visitEnd()
34+
super.visitEnd()
35+
Files.createDirectories(newPath.getParent)
36+
Files.write(newPath, helperClassVisitor.toByteArray)
37+
}
38+
39+
private def mkHelperClass = {
40+
val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
41+
42+
cw.visit(targetByteCode,
43+
ACC_PUBLIC + ACC_SUPER,
44+
helperClassName,
45+
null,
46+
"java/lang/Object",
47+
null)
48+
49+
cw.visitSource(s"$cName.java", null)
50+
51+
{
52+
val mv = cw.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null)
53+
mv.visitVarInsn(ALOAD, 0)
54+
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
55+
mv.visitInsn(RETURN)
56+
mv.visitMaxs(0, 0)
57+
mv.visitEnd()
58+
}
59+
cw
60+
}
61+
62+
override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) = {
63+
isInterface = (access & ACC_INTERFACE) != 0
64+
cName = name
65+
super.visit(targetByteCode, access, name, signature, superName, interfaces)
66+
}
67+
}
68+
69+
// works, strips the body
70+
class BodyStripper(newMethod: MethodVisitor) extends MethodVisitor(Opcodes.ASM5, newMethod) {
71+
override def visitEnd() = {
72+
newMethod.visitMaxs(0, 0)
73+
super.visitEnd()
74+
}
75+
}

0 commit comments

Comments
 (0)