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