From 8e13c801cc87c8565799194f826399f0d23b4cdc Mon Sep 17 00:00:00 2001 From: Raul Bache Date: Mon, 11 Aug 2014 17:02:58 +0200 Subject: [PATCH] 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 #16 // Method java/lang/Object."":()V 4: return public java.lang.String join(testpackage.MyInterface); Code: 0: aload_0 1: aload_1 2: invokestatic #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 #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 #9 // Method java/lang/Object."":()V 4: return public static java.lang.String def(testpackage.MyInterface); Code: 0: new #13 // class java/lang/StringBuilder 3: dup 4: invokespecial #14 // Method java/lang/StringBuilder."":()V 7: ldc #16 // String [ 9: invokevirtual #20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: invokevirtual #24 // Method java/lang/Object.toString:()Ljava/lang/String; 16: invokevirtual #20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc #26 // String , 21: invokevirtual #20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: ldc #28 // String def] 26: invokevirtual #20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 29: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 32: areturn public static java.lang.String join(testpackage.MyInterface, testpackage.MyInterface); Code: 0: new #13 // class java/lang/StringBuilder 3: dup 4: invokespecial #14 // Method java/lang/StringBuilder."":()V 7: aload_0 8: invokeinterface #35, 1 // InterfaceMethod testpackage/MyInterface.def:()Ljava/lang/String; 13: invokevirtual #20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 16: aload_1 17: invokeinterface #35, 1 // InterfaceMethod testpackage/MyInterface.def:()Ljava/lang/String; 22: invokevirtual #20 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 25: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 28: areturn } ============== interface StaticTest { static T staticMethod(T t) { return t; } } compiles to public class testpackage.StaticTesthelper { private testpackage.StaticTesthelper(); Code: 0: aload_0 1: invokespecial #9 // Method java/lang/Object."":()V 4: return public static T staticMethod$static(T); Code: 0: aload_0 1: areturn } ======== Brigde methods are generated properly in example: public interface BridgeParent { T get(); } public interface StringBridges extends BridgeParent { @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: #1 = Utf8 testpackage/StringBridgeshelper #2 = Class #1 // testpackage/StringBridgeshelper #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 testpackage/StringBridges.java #6 = Utf8 #7 = Utf8 ()V #8 = NameAndType #6:#7 // "":()V #9 = Methodref #4.#8 // java/lang/Object."":()V #10 = Utf8 get #11 = Utf8 (Ltestpackage/StringBridges;)Ljava/lang/String; #12 = Utf8 default method #13 = String #12 // default method #14 = Utf8 concrete #15 = String #14 // concrete #16 = Utf8 (Ltestpackage/StringBridges;)Ljava/lang/Object; #17 = Utf8 testpackage/StringBridges #18 = Class #17 // testpackage/StringBridges #19 = Utf8 ()Ljava/lang/String; #20 = NameAndType #10:#19 // get:()Ljava/lang/String; #21 = InterfaceMethodref #18.#20 // testpackage/StringBridges.get:()Ljava/lang/String; #22 = Utf8 Code #23 = Utf8 LineNumberTable #24 = Utf8 SourceFile { private testpackage.StringBridgeshelper(); descriptor: ()V flags: ACC_PRIVATE Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #9 // Method java/lang/Object."":()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 #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 #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 #21, 1 // InterfaceMethod testpackage/StringBridges.get:()Ljava/lang/String; 6: areturn LineNumberTable: line 6: 0 } --- retrolambda/pom.xml | 21 ++++- retrolambda/src/main/scala/AsmTest.scala | 66 ++++++++++++++ .../src/main/scala/ClassModifier.scala | 91 +++++++++++++++++++ .../src/main/scala/InterfaceModifier.scala | 75 +++++++++++++++ 4 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 retrolambda/src/main/scala/AsmTest.scala create mode 100644 retrolambda/src/main/scala/ClassModifier.scala create mode 100644 retrolambda/src/main/scala/InterfaceModifier.scala diff --git a/retrolambda/pom.xml b/retrolambda/pom.xml index c25f5cf6..8a6dda98 100644 --- a/retrolambda/pom.xml +++ b/retrolambda/pom.xml @@ -14,6 +14,11 @@ jar + + org.scala-lang + scala-library + 2.11.2 + org.ow2.asm @@ -70,7 +75,21 @@ - + + net.alchim31.maven + scala-maven-plugin + 3.2.0 + + + scala-compile-first + process-resources + + add-source + compile + + + + diff --git a/retrolambda/src/main/scala/AsmTest.scala b/retrolambda/src/main/scala/AsmTest.scala new file mode 100644 index 00000000..73b1c875 --- /dev/null +++ b/retrolambda/src/main/scala/AsmTest.scala @@ -0,0 +1,66 @@ +/** + * Created by arneball on 2014-07-29. + */ + +import java.lang.reflect.{Method, Modifier} +import java.net.{URL, URLClassLoader} +import java.nio.file._ +import java.nio.file.attribute.BasicFileAttributes +import org.objectweb.asm._ +import collection.JavaConversions._ + +object AsmTest { + lazy val (output: Path, cp: Array[URL], input: Path, bytecodeVersion: Int, ucl: URLClassLoader) = { + val List(_input, _output, _cp, _bytecode) = List("inputDir", "outputDir", "classpath", "bytecodeVersion").map{ name => + System.getProperties.getProperty("retrometh." + name) + } + val byteCodeVersion = _bytecode match { + case "1.7" | "7" | "51" => Opcodes.V1_7 + case "1.6" | "6" | "50" => Opcodes.V1_6 + case "1.5" | "5" | "49" => Opcodes.V1_5 + case _ => throw new Exception("BytecodeVersion must be 1.6, 6, 1.7 or 7") + } + val input = Paths.get(_input) + val output = Option(_output).map{ Paths.get(_) }.getOrElse(input) + + val cp = _cp.split(System.getProperty("path.separator")).map{ p => + Paths.get(p).toUri.toURL + } :+ input.toUri.toURL + Implicits.println(s"Input: $input, Output: $output, classpath: ${cp.mkString}") + (output, cp, input, byteCodeVersion, new URLClassLoader(cp)) + } + + def main(args: Array[String]): Unit = { + Files.walkFileTree(input, new SimpleFileVisitor[Path] { + override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = file.toString.toLowerCase.endsWith("class") match { + case true => + println("Found class file " + file) + val bytecode = Files.readAllBytes(file) + val wr = new ClassWriter(ClassWriter.COMPUTE_FRAMES) + val stage1 = new InterfaceModifier(wr, bytecodeVersion) + val stage2 = new ClassModifier(stage1, bytecodeVersion) + val reader = new ClassReader(bytecode).accept(stage2, 0); + val outputFile = output.resolve(input.relativize(file)); + Files.createDirectories(outputFile.getParent()); + Files.write(outputFile, wr.toByteArray); + FileVisitResult.CONTINUE + case that => + FileVisitResult.CONTINUE + } + }) + } +} + +object Implicits { + def println(str: String) = Predef.println("KALLE " + str) + val Sig = raw"\((.*)\)(.*)".r + implicit class StrWr(val str: String) extends AnyVal { + def addParam(cl: String) = str match { + case Sig(content, returntype) => + val tmp = s"(L${cl};$content)$returntype" + println(s"Before $str, now $tmp") + tmp + } + def getInternalClass = AsmTest.ucl.loadClass(str.replace("/", ".")) + } +} \ No newline at end of file diff --git a/retrolambda/src/main/scala/ClassModifier.scala b/retrolambda/src/main/scala/ClassModifier.scala new file mode 100644 index 00000000..3b1e429d --- /dev/null +++ b/retrolambda/src/main/scala/ClassModifier.scala @@ -0,0 +1,91 @@ +import java.lang.reflect.{Method, Modifier} + +import Implicits._ +import org.objectweb.asm.Opcodes._ +import org.objectweb.asm.Type._ +import org.objectweb.asm._ + +import scala.collection.mutable.ArrayBuffer + +case class MethodContainer(methodName: String, methodDesc: String, interface: String, signature: String) +class ClassModifier(visitor: ClassVisitor, byteCodeVersion: Int = V1_6) extends ClassVisitor(ASM5, visitor) { + + var methodsToOverride: List[MethodContainer] = Nil + var className: String = _ + type MethodDesc = (String, String) + var implementedMethods = ArrayBuffer[MethodDesc]() + + override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) = { + val isClass = (access & ACC_INTERFACE) == 0 + if(isClass) { + val defaultMethods = for { + interface <- interfaces + cl = interface.getInternalClass + m @ DefaultMethod() <- cl.getMethods + } yield MethodContainer(m.getName, Type.getMethodDescriptor(m), interface, signature) + println(s"Default methods: ${defaultMethods.mkString(",")}") + methodsToOverride = defaultMethods.toList + className = name + } + super.visit(byteCodeVersion, access, name, signature, superName, interfaces) + } + + // gotta keep track of overriden default methods + override def visitMethod(access: Int, name: String, desc: String, signature: String, exceptions: Array[String]): MethodVisitor = { + implementedMethods += name -> desc + new InterfaceToHelperRewriter(super.visitMethod(access, name, desc, signature, exceptions)) + } + + override def visitEnd() = { + def createProxy(l: MethodContainer) = l match { + case MethodContainer(name, desc, interface, signature) if !implementedMethods.contains(name -> desc) => + val tmp = super.visitMethod(ACC_PUBLIC, name, desc, signature, null) + tmp.visitVarInsn(ALOAD, 0) + Type.getArgumentTypes(desc).zipWithIndex.foreach{ + case (PrimitiveLoad(instruction), i) => tmp.visitVarInsn(instruction, i + 1) + case (_, i) => tmp.visitVarInsn(ALOAD, i + 1) + } + tmp.visitMethodInsn(INVOKESTATIC, interface + "helper", name, desc.addParam(interface), false) + Type.getReturnType(desc) match { + case ReturnIns(ins) => tmp.visitInsn(ins) + case otherwise: Type => tmp.visitInsn(ARETURN) + } + tmp.visitMaxs(0, 0) + tmp.visitEnd() + case _ => // noop + } + methodsToOverride.foreach{ createProxy } + super.visitEnd() + } +} + +class InterfaceToHelperRewriter(mv: MethodVisitor) extends MethodVisitor(Opcodes.ASM5, mv) { + override def visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, isInterface: Boolean) = opcode match { + case INVOKESPECIAL if isInterface => + super.visitMethodInsn(INVOKESTATIC, owner + "helper", name, desc.addParam(owner), false) + case INVOKESTATIC if isInterface => + super.visitMethodInsn(INVOKESTATIC, owner + "helper", name + "$static", desc, false) + case _ => + super.visitMethodInsn(opcode, owner, name, desc, isInterface) + } +} + +object DefaultMethod { + def unapply(m: Method) = !Modifier.isAbstract(m.getModifiers) +} + +object InstructorExtractor { + val (loadMap, returnMap) = { + val types = List(INT_TYPE, LONG_TYPE, FLOAT_TYPE, DOUBLE_TYPE, VOID_TYPE) + val loads = List(ILOAD, LLOAD, FLOAD, DLOAD, -1) // omg a null + val rets = List(IRETURN, LRETURN, FRETURN, DRETURN, RETURN) + val mkMap = types.zip(_: List[Int]).toMap + mkMap(loads) -> mkMap(rets) + } +} + +class InstructorExtractor(m: Map[Type, Int]) { + def unapply(f: Type) = m.get(f) +} +object PrimitiveLoad extends InstructorExtractor(InstructorExtractor.loadMap) +object ReturnIns extends InstructorExtractor(InstructorExtractor.returnMap) \ No newline at end of file diff --git a/retrolambda/src/main/scala/InterfaceModifier.scala b/retrolambda/src/main/scala/InterfaceModifier.scala new file mode 100644 index 00000000..20bf92e7 --- /dev/null +++ b/retrolambda/src/main/scala/InterfaceModifier.scala @@ -0,0 +1,75 @@ +import java.nio.file.Files + +import Implicits._ +import org.objectweb.asm.Opcodes._ +import org.objectweb.asm._ + +import scala.collection.{mutable => m} + +class InterfaceModifier(classWriter: ClassWriter, targetByteCode: Int = Opcodes.V1_6) extends ClassVisitor(ASM5, classWriter) with Opcodes { + private var cName: String = _ + private var isInterface = false + private lazy val helperClassVisitor = mkHelperClass + private lazy val helperClassName = cName + "helper" + + override def visitMethod(access: Int, name: String, desc: String, signature: String, exceptions: Array[String]): MethodVisitor = { + val methConcrete = (ACC_ABSTRACT & access) == 0 + val isStatic = (ACC_STATIC & access) != 0 + (methConcrete, isInterface, isStatic) match { + case (true, true, false) => // concrete interface method + super.visitMethod(access | ACC_ABSTRACT, name, desc, signature, exceptions) + val tmp = helperClassVisitor.visitMethod(access | ACC_STATIC, name, desc.addParam(cName), signature, exceptions) + new BodyStripper(tmp) + case (true, true, true) => // static interface method + helperClassVisitor.visitMethod(access, name + "$static", desc, signature, exceptions) + case _ => + super.visitMethod(access, name, desc, signature, exceptions) + } + } + + override def visitEnd() = { + val newPath = AsmTest.output.resolve(helperClassName + ".class") + println("CREATING HELPER AT " + newPath) + helperClassVisitor.visitEnd() + super.visitEnd() + Files.createDirectories(newPath.getParent) + Files.write(newPath, helperClassVisitor.toByteArray) + } + + private def mkHelperClass = { + val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS) + + cw.visit(targetByteCode, + ACC_PUBLIC + ACC_SUPER, + helperClassName, + null, + "java/lang/Object", + null) + + cw.visitSource(s"$cName.java", null) + + { + val mv = cw.visitMethod(ACC_PRIVATE, "", "()V", null, null) + mv.visitVarInsn(ALOAD, 0) + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false) + mv.visitInsn(RETURN) + mv.visitMaxs(0, 0) + mv.visitEnd() + } + cw + } + + override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) = { + isInterface = (access & ACC_INTERFACE) != 0 + cName = name + super.visit(targetByteCode, access, name, signature, superName, interfaces) + } +} + +// works, strips the body +class BodyStripper(newMethod: MethodVisitor) extends MethodVisitor(Opcodes.ASM5, newMethod) { + override def visitEnd() = { + newMethod.visitMaxs(0, 0) + super.visitEnd() + } +}