Skip to content

Commit

Permalink
package testpackage;
Browse files Browse the repository at this point in the history
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
}
  • Loading branch information
Arneball committed Aug 11, 2014
1 parent 374c4f2 commit 8e13c80
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 1 deletion.
21 changes: 20 additions & 1 deletion retrolambda/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.2</version>
</dependency>

<dependency>
<groupId>org.ow2.asm</groupId>
Expand Down Expand Up @@ -70,7 +75,21 @@
</filters>
</configuration>
</plugin>

<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
66 changes: 66 additions & 0 deletions retrolambda/src/main/scala/AsmTest.scala
Original file line number Diff line number Diff line change
@@ -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("/", "."))
}
}
91 changes: 91 additions & 0 deletions retrolambda/src/main/scala/ClassModifier.scala
Original file line number Diff line number Diff line change
@@ -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)
75 changes: 75 additions & 0 deletions retrolambda/src/main/scala/InterfaceModifier.scala
Original file line number Diff line number Diff line change
@@ -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, "<init>", "()V", null, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()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()
}
}

0 comments on commit 8e13c80

Please sign in to comment.