Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default methods #26

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
}