Skip to content

Commit

Permalink
Update the enclosing method references for (anonymous) classes inside…
Browse files Browse the repository at this point in the history
… lambda expressions

Fixes #121
  • Loading branch information
luontola committed Feb 23, 2017
1 parent 006a423 commit 6d5a4c1
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 5 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ optimizations to that mechanism may break Retrolambda.
Version History
---------------

### Upcoming

- Fix anonymous classes inside lambda expressions
([Issue #121](https://github.com/orfjackal/retrolambda/issues/121))

### Retrolambda 2.5.0 (2017-01-22)

- Fixed lambda expressions in subclasses accidentally overriding lambda
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import net.orfjackal.retrolambda.test.anotherpackage.DifferentPackageBase;
import org.apache.commons.lang.SystemUtils;
import org.junit.*;
import org.junit.Test;
import org.objectweb.asm.*;
import org.objectweb.asm.Type;

Expand Down Expand Up @@ -249,7 +249,6 @@ private static String privateClassMethod() {


@Test
@Ignore // TODO: fix issue #121
public void enclosing_method_of_anonymous_class_inside_lambda_expression() throws Exception {
Callable<Object> lambda = () -> new Object() {
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
package net.orfjackal.retrolambda;

import net.orfjackal.retrolambda.interfaces.*;
import net.orfjackal.retrolambda.lambdas.Handles;
import net.orfjackal.retrolambda.util.Bytecode;
import net.orfjackal.retrolambda.lambdas.*;
import net.orfjackal.retrolambda.util.*;
import org.objectweb.asm.*;

import java.util.*;
Expand All @@ -19,6 +19,7 @@ public class ClassAnalyzer {

private final Map<Type, ClassInfo> classes = new HashMap<>();
private final Map<MethodRef, MethodRef> relocatedMethods = new HashMap<>();
private final Map<MethodRef, MethodRef> renamedLambdaMethods = new HashMap<>();

public void analyze(byte[] bytecode) {
analyze(new ClassReader(bytecode));
Expand All @@ -33,6 +34,7 @@ public void analyze(ClassReader cr) {
} else {
analyzeClass(c, cr);
}
analyzeClassOrInterface(c, cr);
}

private void analyzeClass(ClassInfo c, ClassReader cr) {
Expand Down Expand Up @@ -98,7 +100,33 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
}, ClassReader.SKIP_CODE);
}

public static boolean isDefaultMethod(int access) {
private void analyzeClassOrInterface(ClassInfo c, ClassReader cr) {
cr.accept(new ClassVisitor(ASM5) {
private String owner;

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.owner = name;
}

@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodRef method = new MethodRef(Handles.accessToTag(access, true), owner, name, desc);

// XXX: duplicates code in net.orfjackal.retrolambda.lambdas.BackportLambdaInvocations.visitMethod()
if (LambdaNaming.isBodyMethod(access, name)
&& Flags.isPrivateMethod(access)
&& Flags.isInstanceMethod(access)) {
desc = Types.prependArgumentType(Type.getObjectType(owner), desc); // add 'this' as first parameter
renamedLambdaMethods.put(method, new MethodRef(H_INVOKESTATIC, owner, name, desc));
}

return null;
}
}, ClassReader.SKIP_CODE);
}

private static boolean isDefaultMethod(int access) {
return !isAbstractMethod(access)
&& !isStaticMethod(access)
&& isPublicMethod(access);
Expand Down Expand Up @@ -139,6 +167,10 @@ public MethodRef getMethodCallTarget(MethodRef original) {
return relocatedMethods.getOrDefault(original, original);
}

public MethodRef getRenamedLambdaMethod(MethodRef original) {
return renamedLambdaMethods.getOrDefault(original, original);
}

public MethodRef getMethodDefaultImplementation(MethodRef interfaceMethod) {
MethodSignature signature = interfaceMethod.getSignature();
for (MethodInfo method : getDefaultMethods(Type.getObjectType(interfaceMethod.owner))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private byte[] transform(String className, Consumer<ClassVisitor> reader, ClassV
next = new RequireNonNull(next);
}
next = new FixInvokeStaticOnInterfaceMethod(next);
next = new UpdateRenamedEnclosingMethods(next, analyzer);
next = chain.wrap(next);

reader.accept(next);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright © 2013-2017 Esko Luontola and other Retrolambda contributors
// 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.lambdas;

import net.orfjackal.retrolambda.ClassAnalyzer;
import net.orfjackal.retrolambda.interfaces.MethodRef;
import org.objectweb.asm.ClassVisitor;

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

public class UpdateRenamedEnclosingMethods extends ClassVisitor {

private final ClassAnalyzer analyzer;

public UpdateRenamedEnclosingMethods(ClassVisitor next, ClassAnalyzer analyzer) {
super(ASM5, next);
this.analyzer = analyzer;
}

@Override
public void visitOuterClass(String owner, String name, String desc) {
MethodRef method = analyzer.getRenamedLambdaMethod(new MethodRef(0, owner, name, desc));
super.visitOuterClass(method.owner, method.name, method.desc);
}
}

0 comments on commit 6d5a4c1

Please sign in to comment.