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() + } +}