Tests for edge cases when desugaring default methods in the library.

Bug: 145506767
Bug: 145504401
Change-Id: If3b8791653df21ad794d8c6eda2ea92ace7a1a87
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
new file mode 100644
index 0000000..39a186b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibraryTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Predicate;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This test checks that if a default interface method in a library is not overridden by a class
+ * method in the library, then multiple maximally specific method lead to ICCE.
+ *
+ * <p>Concretely, Collection defines a default removeIf() method which is not overridden in either
+ * the List interface or the LinkedList class. Thus, a class that has an unrelated default method
+ * for removeIf will cause a conflict throwing ICCE.
+ */
+@RunWith(Parameterized.class)
+public class DefaultMethodOverrideConflictWithLibraryTest extends CoreLibDesugarTestBase {
+
+  private static List<Class<?>> CLASSES = ImmutableList.of(Main.class, MyRemoveIf.class);
+
+  private static List<byte[]> getTransforms() throws IOException {
+    return ImmutableList.of(
+        transformer(ConflictingClass.class).setImplements(MyRemoveIf.class).transform());
+  }
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public DefaultMethodOverrideConflictWithLibraryTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    TestRuntime systemRuntime = TestRuntime.getSystemRuntime();
+    if (systemRuntime.isCf() && systemRuntime.asCf().isNewerThanOrEqual(CfVm.JDK8)) {
+      // This test assumes that the library defines a Collection class with a default removeIf.
+      Method removeIf = Collection.class.getDeclaredMethod("removeIf", Predicate.class);
+      assertNotNull(removeIf);
+      assertTrue(removeIf.isDefault());
+      // Also, the LinkedList implementation does *not* define an override of removeIf.
+      try {
+        LinkedList.class.getDeclaredMethod("removeIf", Predicate.class);
+        fail("Unexpected defintion of removeIf on LinkedList");
+      } catch (NoSuchMethodException e) {
+        // Expected.
+      }
+    }
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(CLASSES)
+          .addProgramClassFileData(getTransforms())
+          .run(parameters.getRuntime(), Main.class)
+          .assertFailureWithErrorThatMatches(getExpectedError());
+    } else {
+      testForD8()
+          .setMinApi(parameters.getApiLevel())
+          .addProgramClasses(CLASSES)
+          .addProgramClassFileData(getTransforms())
+          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary, parameters.getApiLevel())
+          .run(parameters.getRuntime(), Main.class)
+          .assertFailureWithErrorThatMatches(getExpectedError());
+    }
+  }
+
+  private Matcher<String> getExpectedError() {
+    return containsString(IncompatibleClassChangeError.class.getName());
+  }
+
+  // Interface with a default method that can lead to a conflict.
+  interface MyRemoveIf {
+
+    default boolean removeIf(Predicate<? super Integer> filter) {
+      System.out.println("MyRemoveIf::removeIf");
+      return false;
+    }
+  }
+
+  // Derived list with no override of removeIf but with a default method in MyRemoveIf.
+  // The two maximally specific methods Collection::removeIf and MyRemoveIf must cause ICCE.
+  static class ConflictingClass extends LinkedList<Integer> /* implements MyRemoveIf via ASM */ {
+    // Intentionally empty.
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new ConflictingClass().removeIf(e -> false));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
new file mode 100644
index 0000000..67c92ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This test checks that if a default interface method in a library is overridden by a class method
+ * also in the library, then that class method remains the target of resolution and dispatch.
+ *
+ * <p>Concretely, List (and Collection) define a default spliterator() method which is overridden in
+ * the ArrayList class. Thus, any class deriving ArrayList for which spliterator is not overridden
+ * should end up targeting that of ArrayList and not other potential non-library default interface
+ * methods.
+ */
+@RunWith(Parameterized.class)
+public class DefaultMethodOverrideInLibraryTest extends CoreLibDesugarTestBase {
+
+  static final String EXPECTED = StringUtils.lines("0", "42");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public DefaultMethodOverrideInLibraryTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    TestRuntime systemRuntime = TestRuntime.getSystemRuntime();
+    if (systemRuntime.isCf() && systemRuntime.asCf().isNewerThanOrEqual(CfVm.JDK8)) {
+      // This test assumes that the library defines an ArrayList class with a declared spliterator.
+      // For that reason, resolution will find that definition prior to a default interface method.
+      Method spliterator = ArrayList.class.getDeclaredMethod("spliterator");
+      assertNotNull(spliterator);
+      assertFalse(spliterator.isDefault());
+    }
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addInnerClasses(DefaultMethodOverrideInLibraryTest.class)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED);
+    } else {
+      testForD8()
+          .setMinApi(parameters.getApiLevel())
+          .addInnerClasses(DefaultMethodOverrideInLibraryTest.class)
+          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary, parameters.getApiLevel())
+          .run(parameters.getRuntime(), Main.class)
+          .apply(this::checkResult);
+    }
+  }
+
+  private void checkResult(D8TestRunResult result) {
+    // TODO(b/145506767): Default method desugaring fails to generate a library forwarding method.
+    if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+      result.assertFailureWithErrorThatMatches(
+          containsString(
+              parameters
+                      .getRuntime()
+                      .asDex()
+                      .getVm()
+                      .getVersion()
+                      .isOlderThanOrEqual(Version.V4_4_4)
+                  ? "VerifyError"
+                  : AbstractMethodError.class.getName()));
+      return;
+    }
+    // TODO(b/145504401): Execution on Art 7.0.0 has the wrong runtime behavior.
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V7_0_0)) {
+      result.assertSuccessWithOutputLines("42", "42");
+      return;
+    }
+    result.assertSuccessWithOutput(EXPECTED);
+  }
+
+  // Custom spliterator, just returns 42 in estimateSize, otherwise unused.
+  static class MySpliterator implements Spliterator<Integer> {
+
+    @Override
+    public boolean tryAdvance(Consumer<? super Integer> action) {
+      return false;
+    }
+
+    @Override
+    public Spliterator<Integer> trySplit() {
+      return null;
+    }
+
+    @Override
+    public long estimateSize() {
+      return 42; // Overridden to differ from the default.
+    }
+
+    @Override
+    public int characteristics() {
+      return 0;
+    }
+  }
+
+  // Custom list interface with a default method for spliterator.
+  interface MyIntegerList extends List<Integer> {
+
+    @Override
+    default Spliterator<Integer> spliterator() {
+      return new MySpliterator();
+    }
+  }
+
+  // Derived list with no override of spliterator. The call will thus go to the super class, not
+  // the default method!
+  static class MyIntegerArrayListWithoutOverride extends ArrayList<Integer>
+      implements MyIntegerList {
+    // No override of spliterator.
+  }
+
+  // Derived list with an override of spliterator. The call must hit the classes override and that
+  // will explictly call the custom default method.
+  static class MyIntegerArrayListWithOverride extends ArrayList<Integer> implements MyIntegerList {
+
+    @Override
+    public Spliterator<Integer> spliterator() {
+      return MyIntegerList.super.spliterator();
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new MyIntegerArrayListWithoutOverride().spliterator().estimateSize());
+      System.out.println(new MyIntegerArrayListWithOverride().spliterator().estimateSize());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
new file mode 100644
index 0000000..e764381
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDefaultMethodOverrideInLibraryTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This test checks that if a default interface method in a library is not overridden by a class
+ * method in the library, then a program defined maximally specific method becomes the target if
+ * present.
+ *
+ * <p>Concretely, Collection defines a default removeIf() method which is not overridden in either
+ * the List interface or the LinkedList class. Thus, any class deriving LinkedList for which
+ * removeIf is overridden or a new default method is added should target those extensions.
+ */
+@RunWith(Parameterized.class)
+public class NoDefaultMethodOverrideInLibraryTest extends CoreLibDesugarTestBase {
+
+  static final String EXPECTED = StringUtils.lines("MyIntegerList::removeIf", "false", "false");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public NoDefaultMethodOverrideInLibraryTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    TestRuntime systemRuntime = TestRuntime.getSystemRuntime();
+    if (systemRuntime.isCf() && systemRuntime.asCf().isNewerThanOrEqual(CfVm.JDK8)) {
+      // This test assumes that the library defines a Collection class with a default removeIf.
+      Method removeIf = Collection.class.getDeclaredMethod("removeIf", Predicate.class);
+      assertNotNull(removeIf);
+      assertTrue(removeIf.isDefault());
+      // Also, the LinkedList implementation does *not* define an override of removeIf.
+      try {
+        LinkedList.class.getDeclaredMethod("removeIf", Predicate.class);
+        fail("Unexpected defintion of removeIf on LinkedList");
+      } catch (NoSuchMethodException e) {
+        // Expected.
+      }
+    }
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addInnerClasses(NoDefaultMethodOverrideInLibraryTest.class)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED);
+    } else {
+      testForD8()
+          .setMinApi(parameters.getApiLevel())
+          .addInnerClasses(NoDefaultMethodOverrideInLibraryTest.class)
+          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary, parameters.getApiLevel())
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED);
+    }
+  }
+
+  // Custom list interface with a default method for removeIf.
+  interface MyIntegerList extends List<Integer> {
+
+    @Override
+    default boolean removeIf(Predicate<? super Integer> filter) {
+      System.out.println("MyIntegerList::removeIf");
+      return false;
+    }
+  }
+
+  // Derived list with no override of removeIf but with a default method in MyIntegerList.
+  // The call will thus go to the maximally specific method, which is MyIntegerList::removeIf.
+  static class MyIntegerLinkedListWithoutOverride extends LinkedList<Integer>
+      implements MyIntegerList {
+    // No override of spliterator.
+  }
+
+  // Derived list with an override of removeIf. The call must hit the classes override and that
+  // will explictly call the library default method (Collection.removeIf).
+  static class MyIntegerLinkedListWithOverride extends LinkedList<Integer>
+      implements MyIntegerList {
+
+    @Override
+    public boolean removeIf(Predicate<? super Integer> filter) {
+      return super.removeIf(filter);
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new MyIntegerLinkedListWithoutOverride().removeIf(e -> false));
+      System.out.println(new MyIntegerLinkedListWithOverride().removeIf(e -> false));
+    }
+  }
+}