Skip to content

Commit bccd9b2

Browse files
committed
Create bridge methods to private lambda implementation methods, instead of changing their visibility
Fixed #18, fixes #19
1 parent 2e9ecf3 commit bccd9b2

File tree

8 files changed

+199
-118
lines changed

8 files changed

+199
-118
lines changed

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ package-private.
142142
Version History
143143
---------------
144144

145+
### Upcoming
146+
147+
- Android: Fixed NoSuchMethodError when calling a private method to which
148+
there is a method reference
149+
([Issue #18](https://github.com/orfjackal/retrolambda/issues/18))
150+
- Fixed the possibility of accidentally overriding private methods to which
151+
there is method reference
152+
([Issue #19](https://github.com/orfjackal/retrolambda/issues/19))
153+
145154
### Retrolambda 1.2.2 (2014-05-15)
146155

147156
- Fixed method references to private methods; will now make them
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,12 @@
1-
// Copyright © 2013 Esko Luontola <www.orfjackal.net>
1+
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
22
// This software is released under the Apache License 2.0.
33
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
44

55
package net.orfjackal.retrolambda;
66

7-
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
8-
97
public class Flags {
108

11-
public static int makeNonPrivate(int access) {
12-
if (hasFlag(access, ACC_PRIVATE)) {
13-
return clearFlag(access, ACC_PRIVATE); // make package-private (i.e. no flag)
14-
}
15-
return access;
16-
}
17-
189
public static boolean hasFlag(int subject, int flag) {
1910
return (subject & flag) == flag;
2011
}
21-
22-
public static int clearFlag(int subject, int flag) {
23-
return subject & ~flag;
24-
}
2512
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
2+
// This software is released under the Apache License 2.0.
3+
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
4+
5+
package net.orfjackal.retrolambda;
6+
7+
import org.objectweb.asm.Handle;
8+
9+
import static org.objectweb.asm.Opcodes.*;
10+
11+
public class Handles {
12+
13+
public static int getOpcode(Handle handle) {
14+
int tag = handle.getTag();
15+
switch (tag) {
16+
case H_INVOKEVIRTUAL:
17+
return INVOKEVIRTUAL;
18+
case H_INVOKESTATIC:
19+
return INVOKESTATIC;
20+
case H_INVOKESPECIAL:
21+
return INVOKESPECIAL;
22+
case H_NEWINVOKESPECIAL:
23+
return INVOKESPECIAL; // we assume that the caller takes care of the NEW instruction
24+
case H_INVOKEINTERFACE:
25+
return INVOKEINTERFACE;
26+
default:
27+
throw new IllegalArgumentException("Unsupported tag " + tag + " in " + handle);
28+
}
29+
}
30+
}

retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaClassBackporter.java

+18-10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ private static class LambdaClassVisitor extends ClassVisitor {
2424
private String lambdaClass;
2525
private Type constructor;
2626
private Handle implMethod;
27+
private Handle bridgeMethod;
2728
private LambdaFactoryMethod factoryMethod;
2829

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

4143
if (version > targetVersion) {
@@ -54,7 +56,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
5456
}
5557
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
5658
mv = new MagicLambdaRemovingMethodVisitor(mv);
57-
mv = new PrivateMethodInvocationFixingMethodVisitor(mv, implMethod);
59+
mv = new PrivateMethodInvocationFixingMethodVisitor(mv, this);
5860
return mv;
5961
}
6062

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

140142
private final Handle implMethod;
143+
private final Handle bridgeMethod;
141144

142-
public PrivateMethodInvocationFixingMethodVisitor(MethodVisitor mv, Handle implMethod) {
145+
public PrivateMethodInvocationFixingMethodVisitor(MethodVisitor mv, LambdaClassVisitor context) {
143146
super(ASM5, mv);
144-
this.implMethod = implMethod;
147+
this.implMethod = context.implMethod;
148+
this.bridgeMethod = context.bridgeMethod;
145149
}
146150

147151
@Override
148152
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
149153
// Java 8's lambda classes get away with calling private virtual methods
150154
// by using invokespecial because the JVM relaxes the bytecode validation
151-
// of the lambda classes it generates. We must however use invokevirtual
152-
// for them (which is possible because we also make them non-private).
153-
if (opcode == INVOKESPECIAL
154-
&& !name.equals("<init>")
155-
&& owner.equals(implMethod.getOwner())
155+
// of the lambda classes it generates. We must however call them through
156+
// a non-private bridge method which we have generated.
157+
if (owner.equals(implMethod.getOwner())
156158
&& name.equals(implMethod.getName())
157159
&& desc.equals(implMethod.getDesc())) {
158-
opcode = INVOKEVIRTUAL;
160+
super.visitMethodInsn(
161+
Handles.getOpcode(bridgeMethod),
162+
bridgeMethod.getOwner(),
163+
bridgeMethod.getName(),
164+
bridgeMethod.getDesc(),
165+
bridgeMethod.getTag() == H_INVOKEINTERFACE);
166+
} else {
167+
super.visitMethodInsn(opcode, owner, name, desc, itf);
159168
}
160-
super.visitMethodInsn(opcode, owner, name, desc, itf);
161169
}
162170
}
163171
}

retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaReifier.java

+16-43
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@
1111
import java.util.*;
1212
import java.util.concurrent.*;
1313

14-
import static org.objectweb.asm.Opcodes.*;
15-
1614
public class LambdaReifier {
1715

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

26-
public static LambdaFactoryMethod reifyLambdaClass(Handle lambdaImplMethod, Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) {
25+
public static LambdaFactoryMethod reifyLambdaClass(Handle lambdaImplMethod, Handle lambdaBridgeMethod,
26+
Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) {
2727
try {
2828
setLambdaImplMethod(lambdaImplMethod);
29+
setLambdaBridgeMethod(lambdaBridgeMethod);
2930
setInvokedType(invokedType);
3031

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

50+
private static void setLambdaBridgeMethod(Handle lambdaBridgeMethod) {
51+
currentLambdaBridgeMethod.push(lambdaBridgeMethod);
52+
}
53+
4954
private static void setInvokedType(Type invokedType) {
5055
currentInvokedType.push(invokedType);
5156
}
@@ -58,6 +63,10 @@ public static Handle getLambdaImplMethod() {
5863
return currentLambdaImplMethod.getFirst();
5964
}
6065

66+
public static Handle getLambdaBridgeMethod() {
67+
return currentLambdaBridgeMethod.getFirst();
68+
}
69+
6170
public static LambdaFactoryMethod getLambdaFactoryMethod() {
6271
String lambdaClass = currentLambdaClass.getFirst();
6372
Type invokedType = currentInvokedType.getFirst();
@@ -66,6 +75,7 @@ public static LambdaFactoryMethod getLambdaFactoryMethod() {
6675

6776
private static void resetGlobals() {
6877
currentLambdaImplMethod.clear();
78+
currentLambdaBridgeMethod.clear();
6979
currentInvokedType.clear();
7080
currentLambdaClass.clear();
7181
}
@@ -77,12 +87,12 @@ private static CallSite callBootstrapMethod(Class<?> invoker, String invokedName
7787
List<Object> args = new ArrayList<>();
7888
args.add(caller);
7989
args.add(invokedName);
80-
args.add(toMethodType(invokedType, cl));
90+
args.add(Types.toMethodType(invokedType, cl));
8191
for (Object arg : bsmArgs) {
82-
args.add(asmToJdkType(arg, cl, caller));
92+
args.add(Types.asmToJdkType(arg, cl, caller));
8393
}
8494

85-
MethodHandle bootstrapMethod = toMethodHandle(bsm, cl, caller);
95+
MethodHandle bootstrapMethod = Types.toMethodHandle(bsm, cl, caller);
8696
return (CallSite) bootstrapMethod.invokeWithArguments(args);
8797
}
8898

@@ -91,41 +101,4 @@ private static MethodHandles.Lookup getLookup(Class<?> targetClass) throws Excep
91101
ctor.setAccessible(true);
92102
return ctor.newInstance(targetClass);
93103
}
94-
95-
private static Object asmToJdkType(Object arg, ClassLoader classLoader, MethodHandles.Lookup caller) throws Exception {
96-
if (arg instanceof Type) {
97-
return toMethodType((Type) arg, classLoader);
98-
} else if (arg instanceof Handle) {
99-
return toMethodHandle((Handle) arg, classLoader, caller);
100-
} else {
101-
return arg;
102-
}
103-
}
104-
105-
private static MethodType toMethodType(Type type, ClassLoader classLoader) {
106-
return MethodType.fromMethodDescriptorString(type.getInternalName(), classLoader);
107-
}
108-
109-
private static MethodHandle toMethodHandle(Handle handle, ClassLoader classLoader, MethodHandles.Lookup lookup) throws Exception {
110-
MethodType type = MethodType.fromMethodDescriptorString(handle.getDesc(), classLoader);
111-
Class<?> owner = classLoader.loadClass(handle.getOwner().replace('/', '.'));
112-
113-
switch (handle.getTag()) {
114-
case H_INVOKESTATIC:
115-
return lookup.findStatic(owner, handle.getName(), type);
116-
117-
case H_INVOKEVIRTUAL:
118-
case H_INVOKEINTERFACE:
119-
return lookup.findVirtual(owner, handle.getName(), type);
120-
121-
case H_INVOKESPECIAL:
122-
return lookup.findSpecial(owner, handle.getName(), type, owner);
123-
124-
case H_NEWINVOKESPECIAL:
125-
return lookup.findConstructor(owner, type);
126-
127-
default:
128-
throw new AssertionError("Unexpected handle type: " + handle);
129-
}
130-
}
131104
}

retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaSavingClassFileTransformer.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import org.objectweb.asm.ClassReader;
88

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

52-
} catch (IOException e) {
53-
e.printStackTrace();
51+
} catch (Throwable t) {
52+
// print to stdout to keep in sync with other log output
53+
System.out.println("ERROR: Failed so backport lambda class: " + className);
54+
t.printStackTrace(System.out);
5455
}
5556
return null;
5657
}

0 commit comments

Comments
 (0)