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));
+ }
+ }
+}