Skip to content

Commit

Permalink
Create bridge methods to private lambda implementation methods, inste…
Browse files Browse the repository at this point in the history
…ad of changing their visibility

Fixed #18, fixes #19
  • Loading branch information
luontola committed May 19, 2014
1 parent 2e9ecf3 commit bccd9b2
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 118 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ package-private.
Version History
---------------

### Upcoming

- Android: Fixed NoSuchMethodError when calling a private method to which
there is a method reference
([Issue #18](https://github.com/orfjackal/retrolambda/issues/18))
- Fixed the possibility of accidentally overriding private methods to which
there is method reference
([Issue #19](https://github.com/orfjackal/retrolambda/issues/19))

### Retrolambda 1.2.2 (2014-05-15)

- Fixed method references to private methods; will now make them
Expand Down
15 changes: 1 addition & 14 deletions retrolambda/src/main/java/net/orfjackal/retrolambda/Flags.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
// Copyright © 2013 Esko Luontola <www.orfjackal.net>
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package net.orfjackal.retrolambda;

import static org.objectweb.asm.Opcodes.ACC_PRIVATE;

public class Flags {

public static int makeNonPrivate(int access) {
if (hasFlag(access, ACC_PRIVATE)) {
return clearFlag(access, ACC_PRIVATE); // make package-private (i.e. no flag)
}
return access;
}

public static boolean hasFlag(int subject, int flag) {
return (subject & flag) == flag;
}

public static int clearFlag(int subject, int flag) {
return subject & ~flag;
}
}
30 changes: 30 additions & 0 deletions retrolambda/src/main/java/net/orfjackal/retrolambda/Handles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package net.orfjackal.retrolambda;

import org.objectweb.asm.Handle;

import static org.objectweb.asm.Opcodes.*;

public class Handles {

public static int getOpcode(Handle handle) {
int tag = handle.getTag();
switch (tag) {
case H_INVOKEVIRTUAL:
return INVOKEVIRTUAL;
case H_INVOKESTATIC:
return INVOKESTATIC;
case H_INVOKESPECIAL:
return INVOKESPECIAL;
case H_NEWINVOKESPECIAL:
return INVOKESPECIAL; // we assume that the caller takes care of the NEW instruction
case H_INVOKEINTERFACE:
return INVOKEINTERFACE;
default:
throw new IllegalArgumentException("Unsupported tag " + tag + " in " + handle);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ private static class LambdaClassVisitor extends ClassVisitor {
private String lambdaClass;
private Type constructor;
private Handle implMethod;
private Handle bridgeMethod;
private LambdaFactoryMethod factoryMethod;

public LambdaClassVisitor(ClassWriter cw, int targetVersion) {
Expand All @@ -36,6 +37,7 @@ public void visit(int version, int access, String name, String signature, String
lambdaClass = name;
LambdaReifier.setLambdaClass(lambdaClass);
implMethod = LambdaReifier.getLambdaImplMethod();
bridgeMethod = LambdaReifier.getLambdaBridgeMethod();
factoryMethod = LambdaReifier.getLambdaFactoryMethod();

if (version > targetVersion) {
Expand All @@ -54,7 +56,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
}
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
mv = new MagicLambdaRemovingMethodVisitor(mv);
mv = new PrivateMethodInvocationFixingMethodVisitor(mv, implMethod);
mv = new PrivateMethodInvocationFixingMethodVisitor(mv, this);
return mv;
}

Expand Down Expand Up @@ -138,26 +140,32 @@ public void visitMethodInsn(int opcode, String owner, String name, String desc,
private static class PrivateMethodInvocationFixingMethodVisitor extends MethodVisitor {

private final Handle implMethod;
private final Handle bridgeMethod;

public PrivateMethodInvocationFixingMethodVisitor(MethodVisitor mv, Handle implMethod) {
public PrivateMethodInvocationFixingMethodVisitor(MethodVisitor mv, LambdaClassVisitor context) {
super(ASM5, mv);
this.implMethod = implMethod;
this.implMethod = context.implMethod;
this.bridgeMethod = context.bridgeMethod;
}

@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
// Java 8's lambda classes get away with calling private virtual methods
// by using invokespecial because the JVM relaxes the bytecode validation
// of the lambda classes it generates. We must however use invokevirtual
// for them (which is possible because we also make them non-private).
if (opcode == INVOKESPECIAL
&& !name.equals("<init>")
&& owner.equals(implMethod.getOwner())
// of the lambda classes it generates. We must however call them through
// a non-private bridge method which we have generated.
if (owner.equals(implMethod.getOwner())
&& name.equals(implMethod.getName())
&& desc.equals(implMethod.getDesc())) {
opcode = INVOKEVIRTUAL;
super.visitMethodInsn(
Handles.getOpcode(bridgeMethod),
bridgeMethod.getOwner(),
bridgeMethod.getName(),
bridgeMethod.getDesc(),
bridgeMethod.getTag() == H_INVOKEINTERFACE);
} else {
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@
import java.util.*;
import java.util.concurrent.*;

import static org.objectweb.asm.Opcodes.*;

public class LambdaReifier {

// These globals are used for communicating with the Java agent which
// is spying on the LambdaMetafactory's dynamically generated bytecode.
// We expect only one class being processed at a time, so it should
// be an error if these collections contain more than one element.
private static final BlockingDeque<Handle> currentLambdaImplMethod = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Handle> currentLambdaBridgeMethod = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Type> currentInvokedType = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<String> currentLambdaClass = new LinkedBlockingDeque<>(1);

public static LambdaFactoryMethod reifyLambdaClass(Handle lambdaImplMethod, Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) {
public static LambdaFactoryMethod reifyLambdaClass(Handle lambdaImplMethod, Handle lambdaBridgeMethod,
Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) {
try {
setLambdaImplMethod(lambdaImplMethod);
setLambdaBridgeMethod(lambdaBridgeMethod);
setInvokedType(invokedType);

// Causes the lambda class to be loaded. Retrolambda's Java agent
Expand All @@ -46,6 +47,10 @@ private static void setLambdaImplMethod(Handle lambdaImplMethod) {
currentLambdaImplMethod.push(lambdaImplMethod);
}

private static void setLambdaBridgeMethod(Handle lambdaBridgeMethod) {
currentLambdaBridgeMethod.push(lambdaBridgeMethod);
}

private static void setInvokedType(Type invokedType) {
currentInvokedType.push(invokedType);
}
Expand All @@ -58,6 +63,10 @@ public static Handle getLambdaImplMethod() {
return currentLambdaImplMethod.getFirst();
}

public static Handle getLambdaBridgeMethod() {
return currentLambdaBridgeMethod.getFirst();
}

public static LambdaFactoryMethod getLambdaFactoryMethod() {
String lambdaClass = currentLambdaClass.getFirst();
Type invokedType = currentInvokedType.getFirst();
Expand All @@ -66,6 +75,7 @@ public static LambdaFactoryMethod getLambdaFactoryMethod() {

private static void resetGlobals() {
currentLambdaImplMethod.clear();
currentLambdaBridgeMethod.clear();
currentInvokedType.clear();
currentLambdaClass.clear();
}
Expand All @@ -77,12 +87,12 @@ private static CallSite callBootstrapMethod(Class<?> invoker, String invokedName
List<Object> args = new ArrayList<>();
args.add(caller);
args.add(invokedName);
args.add(toMethodType(invokedType, cl));
args.add(Types.toMethodType(invokedType, cl));
for (Object arg : bsmArgs) {
args.add(asmToJdkType(arg, cl, caller));
args.add(Types.asmToJdkType(arg, cl, caller));
}

MethodHandle bootstrapMethod = toMethodHandle(bsm, cl, caller);
MethodHandle bootstrapMethod = Types.toMethodHandle(bsm, cl, caller);
return (CallSite) bootstrapMethod.invokeWithArguments(args);
}

Expand All @@ -91,41 +101,4 @@ private static MethodHandles.Lookup getLookup(Class<?> targetClass) throws Excep
ctor.setAccessible(true);
return ctor.newInstance(targetClass);
}

private static Object asmToJdkType(Object arg, ClassLoader classLoader, MethodHandles.Lookup caller) throws Exception {
if (arg instanceof Type) {
return toMethodType((Type) arg, classLoader);
} else if (arg instanceof Handle) {
return toMethodHandle((Handle) arg, classLoader, caller);
} else {
return arg;
}
}

private static MethodType toMethodType(Type type, ClassLoader classLoader) {
return MethodType.fromMethodDescriptorString(type.getInternalName(), classLoader);
}

private static MethodHandle toMethodHandle(Handle handle, ClassLoader classLoader, MethodHandles.Lookup lookup) throws Exception {
MethodType type = MethodType.fromMethodDescriptorString(handle.getDesc(), classLoader);
Class<?> owner = classLoader.loadClass(handle.getOwner().replace('/', '.'));

switch (handle.getTag()) {
case H_INVOKESTATIC:
return lookup.findStatic(owner, handle.getName(), type);

case H_INVOKEVIRTUAL:
case H_INVOKEINTERFACE:
return lookup.findVirtual(owner, handle.getName(), type);

case H_INVOKESPECIAL:
return lookup.findSpecial(owner, handle.getName(), type, owner);

case H_NEWINVOKESPECIAL:
return lookup.findConstructor(owner, type);

default:
throw new AssertionError("Unexpected handle type: " + handle);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import org.objectweb.asm.ClassReader;

import java.io.IOException;
import java.lang.instrument.*;
import java.nio.file.*;
import java.security.ProtectionDomain;
Expand Down Expand Up @@ -49,8 +48,10 @@ public byte[] transform(ClassLoader loader, String className, Class<?> classBein
Files.createDirectories(savePath.getParent());
Files.write(savePath, backportedBytecode);

} catch (IOException e) {
e.printStackTrace();
} catch (Throwable t) {
// print to stdout to keep in sync with other log output
System.out.println("ERROR: Failed so backport lambda class: " + className);
t.printStackTrace(System.out);
}
return null;
}
Expand Down
Loading

0 comments on commit bccd9b2

Please sign in to comment.