From 7c3402dbe49fbb6fe7a152c700636a48826e94b5 Mon Sep 17 00:00:00 2001 From: Esko Luontola Date: Sat, 29 Mar 2014 01:02:13 +0200 Subject: [PATCH] Remove bridge methods from interfaces, fixes #13 --- README.md | 6 +++ .../retrolambda/test/DefaultMethodsTest.java | 38 +++++++++++++++++++ .../retrolambda/LambdaUsageBackporter.java | 10 +++++ 3 files changed, 54 insertions(+) create mode 100644 end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java diff --git a/README.md b/README.md index 168d4f0f..8498dffc 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,12 @@ the lambda accesses an instance variable). Version History --------------- +### Upcoming + +- Removes from interfaces bridge methods which were generated by JDK 8 e.g. + when an interface overrides a method and refines its return type + ([Issue #13](https://github.com/orfjackal/retrolambda/issues/13)) + ### Retrolambda 1.1.3 (2014-03-25) - Fixed incompatibility with the Eclipse JDT compiler, version Kepler SR2 diff --git a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java new file mode 100644 index 00000000..45efae2d --- /dev/null +++ b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java @@ -0,0 +1,38 @@ +// Copyright © 2013-2014 Esko Luontola +// 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.test; + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class DefaultMethodsTest { + + /** + * JDK 8 adds a bridge method to an interface when it overrides a method + * from the parent interface and refines its return type. This uses Java 8's + * default methods feature, which won't work on Java 7 and below, so we have + * to remove it for it - this makes the bytecode same as what JDK 7 produces. + */ + @Test + public void will_remove_non_abstract_methods_from_interfaces() { + class Foo implements Child { + @Override + public String foo() { + return "foo"; + } + } + assertThat(new Foo().foo(), is("foo")); + } + + public interface Parent { + Object foo(); + } + + public interface Child extends Parent { + String foo(); // refined return type + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java index 993f3de0..fbd9a2e1 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java @@ -36,6 +36,7 @@ private static void resetLambdaClassSequenceNumber() { private static class MyClassVisitor extends ClassVisitor { private final int targetVersion; + private int classAccess; private String className; public MyClassVisitor(ClassWriter cw, int targetVersion) { @@ -49,17 +50,26 @@ public void visit(int version, int access, String name, String signature, String version = targetVersion; } super.visit(version, access, name, signature, superName, interfaces); + this.classAccess = access; this.className = name; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (isBridgeMethodOnInterface(access)) { + return null; + } if (LambdaNaming.LAMBDA_IMPL_METHOD.matcher(name).matches()) { access = Flags.makeNonPrivate(access); } MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); return new InvokeDynamicInsnConvertingMethodVisitor(api, mv, className); } + + private boolean isBridgeMethodOnInterface(int methodAccess) { + return Flags.hasFlag(classAccess, Opcodes.ACC_INTERFACE) && + Flags.hasFlag(methodAccess, Opcodes.ACC_BRIDGE); + } } private static class InvokeDynamicInsnConvertingMethodVisitor extends MethodVisitor {