Merge "Handle several implementations of default methods"
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3619b9c..3601ee9 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -226,17 +226,44 @@
((DexEncodedMethod) dexItem).getCode() != null;
}
- private void checkIfMethodIsAmbiguous(DexItem previousResult, DexItem newResult) {
+ /** Returns if <code>interface1</code> is a super interface of <code>interface2</code> */
+ private boolean isSuperInterfaceOf(DexType interface1, DexType interface2) {
+ assert definitionFor(interface1).isInterface();
+ DexClass holder = definitionFor(interface2);
+ assert holder.isInterface();
+ for (DexType iface : holder.interfaces.values) {
+ if (iface == interface1 || isSuperInterfaceOf(interface1, iface)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private <S extends DexItem> S resolveAmbiguousResult(S previousResult, S newResult) {
+ // For default methods return the item found lowest in the interface hierarchy. Different
+ // implementations can come from different paths in a diamond.
+ // See §9.4.1 in The Java® Language Specification, Java SE 8 Edition.
if (previousResult != null
&& previousResult != newResult
&& isDefaultMethod(previousResult)
&& isDefaultMethod(newResult)) {
+ DexEncodedMethod previousMethod = (DexEncodedMethod) previousResult;
+ DexEncodedMethod newMethod = (DexEncodedMethod) newResult;
+ if (isSuperInterfaceOf(previousMethod.method.getHolder(), newMethod.method.getHolder())) {
+ return newResult;
+ }
+ if (isSuperInterfaceOf(newMethod.method.getHolder(), previousMethod.method.getHolder())) {
+ return previousResult;
+ }
throw new CompilationError("Duplicate default methods named "
+ previousResult.toSourceString()
+ " are inherited from the types "
- + ((DexEncodedMethod) previousResult).method.holder.getName()
+ + previousMethod.method.holder.getName()
+ " and "
- + ((DexEncodedMethod) newResult).method.holder.getName());
+ + newMethod.method.holder.getName());
+ } else {
+ // Return the first item found for everything except default methods.
+ return previousResult != null ? previousResult : newResult;
}
}
@@ -256,21 +283,13 @@
for (DexType iface : holder.interfaces.values) {
S localResult = lookupTargetAlongSuperAndInterfaceChain(iface, desc, lookup);
if (localResult != null) {
- checkIfMethodIsAmbiguous(result, localResult);
- // Return the first item found, we only continue to detect ambiguous method call.
- if (result == null) {
- result = localResult;
- }
+ result = resolveAmbiguousResult(result, localResult);
}
}
if (holder.superType != null) {
S localResult = lookupTargetAlongInterfaceChain(holder.superType, desc, lookup);
if (localResult != null) {
- checkIfMethodIsAmbiguous(result, localResult);
- // Return the first item found, we only continue to detect ambiguous method call.
- if (result == null) {
- result = localResult;
- }
+ result = resolveAmbiguousResult(result, localResult);
}
}
return result;
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
new file mode 100644
index 0000000..5ffc973
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b63935662;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.AndroidApp;
+import java.nio.file.Path;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Regress63935662 extends TestBase {
+
+ void run(AndroidApp app, Class mainClass) throws Exception {
+ Path proguardConfig = writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(app)
+ .addProguardConfigurationFiles(proguardConfig)
+ .setMinApiLevel(Constants.ANDROID_N_API)
+ .build();
+ String resultFromJava = runOnJava(mainClass);
+ app = ToolHelper.runR8(command);
+ String resultFromArt = runOnArt(app, mainClass);
+ Assert.assertEquals(resultFromJava, resultFromArt);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Class mainClass = TestClass.class;
+ AndroidApp app = readClasses(
+ TestClass.Top.class, TestClass.Left.class, TestClass.Right.class, TestClass.Bottom.class,
+ TestClass.X1.class, TestClass.X2.class, TestClass.X3.class, TestClass.X4.class, TestClass.X5.class,
+ mainClass);
+ run(app, mainClass);
+ }
+
+ @Test
+ public void test2() throws Exception {
+ Class mainClass = TestFromBug.class;
+ AndroidApp app = readClasses(
+ TestFromBug.Map.class, TestFromBug.AbstractMap.class,
+ TestFromBug.ConcurrentMap.class, TestFromBug.ConcurrentHashMap.class,
+ mainClass);
+ run(app, mainClass);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
new file mode 100644
index 0000000..878fcff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b63935662;
+
+public class TestClass {
+
+ interface Top {
+ default String name() { return "unnamed"; }
+ }
+
+ interface Left extends Top {
+ default String name() { return getClass().getName(); }
+ }
+
+ interface Right extends Top {
+ /* No override of default String name() */
+ }
+
+ interface Bottom extends Left, Right {}
+
+ static class X1 implements Bottom {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X2 implements Left, Right {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X3 implements Right, Left {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X4 implements Left, Right, Top {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ static class X5 implements Right, Left, Top {
+ void test() {
+ System.out.println(name());
+ }
+ }
+
+ public static void main(String[] args) {
+ new X1().test();
+ new X2().test();
+ new X3().test();
+ new X4().test();
+ new X5().test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java b/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java
new file mode 100644
index 0000000..9d3efc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/TestFromBug.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b63935662;
+
+import java.util.function.BiConsumer;
+
+public class TestFromBug {
+
+ public interface Map<K, V> {
+ default void forEach(BiConsumer<? super K, ? super V> action) {
+ System.out.println("Map.forEach");
+ }
+ }
+
+ public interface ConcurrentMap<K, V> extends Map<K,V> {
+ @Override
+ default void forEach(BiConsumer<? super K, ? super V> action) {
+ System.out.println("ConcurrentMap.forEach");
+ }
+ }
+
+ public static abstract class AbstractMap<K,V> implements Map<K, V> {}
+ public static class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V> {}
+
+ public static void main(String[] args) {
+ new ConcurrentHashMap<String, String>().forEach(null);
+ }
+}
\ No newline at end of file