Support complex hierarchy in desugared library
Bug: b/202188674
Change-Id: I23d6ef12ec723f50a62e8da7b79c557058a4de99
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 1a029f8..b891c89 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -28,6 +28,7 @@
import java.util.Collections;
import java.util.Deque;
import java.util.List;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -556,6 +557,16 @@
.lookupMaximallySpecificTarget(clazz, method);
}
+ /**
+ * Helper methods used for emulated interface resolution (not in JVM specifications). Answers the
+ * abstract interface methods that the resolution could but does not necessarily resolve into.
+ */
+ public List<Entry<DexClass, DexEncodedMethod>> getAbstractInterfaceMethods(
+ DexClass clazz, DexMethod method) {
+ return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+ .getAbstractInterfaceMethods(clazz, method);
+ }
+
MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
.resolveMaximallySpecificTarget(clazz, method);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
index f7271b7..11f9bd9 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -241,6 +241,12 @@
return resolveMaximallySpecificTargetHelper(clazz, method).lookup();
}
+ // Non-private method used for emulated interface only.
+ List<Entry<DexClass, DexEncodedMethod>> getAbstractInterfaceMethods(
+ DexClass clazz, DexMethod method) {
+ return resolveMaximallySpecificTargetHelper(clazz, method).getAbstractMethods();
+ }
+
private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
DexClass clazz, DexMethod method) {
MaximallySpecificMethodsBuilder builder =
@@ -706,6 +712,28 @@
return nonAbstractMethods;
}
+ List<Entry<DexClass, DexEncodedMethod>> getAbstractMethods() {
+ List<Entry<DexClass, DexEncodedMethod>> abstractMethods = new ArrayList<>();
+ addAbstractMethods(abstractMethods, maximallySpecificMethodsOnCompletePaths);
+ addAbstractMethods(abstractMethods, maximallySpecificMethodsOnIncompletePaths);
+ return abstractMethods;
+ }
+
+ private void addAbstractMethods(
+ List<Entry<DexClass, DexEncodedMethod>> abstractMethods,
+ Map<DexClass, DexEncodedMethod> candidates) {
+ for (Entry<DexClass, DexEncodedMethod> entry : candidates.entrySet()) {
+ DexEncodedMethod method = entry.getValue();
+ if (method == null) {
+ // Ignore shadowed candidates.
+ continue;
+ }
+ if (method.isAbstract()) {
+ abstractMethods.add(entry);
+ }
+ }
+ }
+
private static SingleResolutionResult<?> singleResultHelper(
DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) {
return MethodResolutionResult.createSingleResolutionResult(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 92467a3..16821510 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -49,6 +49,7 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
@@ -656,6 +657,27 @@
&& !emulatedInterfaceInfo.contains(resolvedHolder.type)) {
return true;
}
+ // If the method overrides an abstract method which does not match the criteria, then
+ // forwarding methods are required so the invokes on the top level holder can resolve at
+ // runtime. See b/202188674.
+ if (overridesAbstractNonLibraryInterfaceMethod(
+ clazz, signature.get(), emulatedInterfaceInfo)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean overridesAbstractNonLibraryInterfaceMethod(
+ DexClass clazz, DexMethod dexMethod, EmulatedInterfaceInfo emulatedInterfaceInfo) {
+ List<Entry<DexClass, DexEncodedMethod>> abstractInterfaceMethods =
+ appView.appInfoForDesugaring().getAbstractInterfaceMethods(clazz, dexMethod);
+ for (Entry<DexClass, DexEncodedMethod> classAndMethod : abstractInterfaceMethods) {
+ DexClass foundMethodClass = classAndMethod.getKey();
+ if (!foundMethodClass.isLibraryClass()
+ && !emulatedInterfaceInfo.contains(foundMethodClass.type)) {
+ return true;
+ }
}
return false;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/TelemetryMapInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/TelemetryMapInterfaceTest.java
new file mode 100644
index 0000000..174c311
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/TelemetryMapInterfaceTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, 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 com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction for b/202188674.
+@RunWith(Parameterized.class)
+public class TelemetryMapInterfaceTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("k=key1;v=value1", "k=key1;v=value1", "k=key1;v=value1", "k=key1;v=value1");
+
+ private final TestParameters parameters;
+ private final CompilationSpecification compilationSpecification;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ getJdk8Jdk11(),
+ SPECIFICATIONS_WITH_CF2CF);
+ }
+
+ public TelemetryMapInterfaceTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.compilationSpecification = compilationSpecification;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ }
+
+ @Test
+ public void testMap() throws Throwable {
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Concrete concrete = new Concrete();
+ concrete.put("key1", "value1");
+ concrete.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+ MapInterface mapInterface = concrete;
+ mapInterface.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+ HashMap<String, Object> hashMap = concrete;
+ hashMap.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+ Map<String, Object> map = concrete;
+ map.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+ }
+ }
+
+ interface MapInterface {
+
+ void forEach(BiConsumer<? super String, ? super Object> consumer);
+ }
+
+ static class Concrete extends HashMap<String, Object> implements MapInterface {}
+}