Desugared library fix LinkedHashSet subclasses
- When looking up emulated interface in the
class processor to generate virtual overrides
retarget core lib members should be considered
if they overlap.
Bug: 142846107
Change-Id: I0abc1f4c4f5fe1143ad3ae41de0bbe773bd593c5
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index d81f3fe..688e751 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.ir.code.Invoke;
@@ -114,7 +115,12 @@
DexClass target = appView.definitionFor(method.holder);
// NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
// even if this results in invalid code, these classes are never desugared.
- assert target != null && !rewriter.isNonDesugaredLibraryClass(target);
+ assert target != null;
+ // In desugared library, emulated interface methods can be overridden by retarget lib members.
+ DexMethod forwardMethod =
+ target.isInterface()
+ ? rewriter.defaultAsMethodOfCompanionClass(method)
+ : retargetMethod(method);
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
DexMethod newMethod = dexItemFactory.createMethod(clazz.type, method.proto, method.name);
@@ -125,7 +131,7 @@
ForwardMethodSourceCode.builder(newMethod);
forwardSourceCodeBuilder
.setReceiver(clazz.type)
- .setTarget(rewriter.defaultAsMethodOfCompanionClass(method))
+ .setTarget(forwardMethod)
.setInvokeType(Invoke.Type.STATIC)
.setIsInterface(false); // Holder is companion class, not an interface.
return new DexEncodedMethod(
@@ -136,6 +142,18 @@
new SynthesizedCode(forwardSourceCodeBuilder::build));
}
+ private DexMethod retargetMethod(DexMethod method) {
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+ appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+ Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
+ assert typeMap != null;
+ assert typeMap.get(method.holder) != null;
+ return dexItemFactory.createMethod(
+ typeMap.get(method.holder),
+ dexItemFactory.prependTypeToProto(method.holder, method.proto),
+ method.name);
+ }
+
// For a given class `clazz` inspects all interfaces it implements directly or
// indirectly and collect a set of all default methods to be implemented
// in this class.
@@ -215,17 +233,29 @@
// Remove from candidates methods defined in class or any of its superclasses.
List<DexEncodedMethod> toBeImplemented = new ArrayList<>(candidates.size());
current = clazz;
+ Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+ appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
while (true) {
// In desugared library look-up, methods from library classes cannot hide methods from
// emulated interfaces (the method being desugared implied the implementation is not
- // present in the library class).
+ // present in the library class), except through retarget core lib member.
if (desugaredLibraryLookup && current.isLibraryClass()) {
Iterator<DexEncodedMethod> iterator = candidates.iterator();
while (iterator.hasNext()) {
DexEncodedMethod candidate = iterator.next();
- if (rewriter.isEmulatedInterface(candidate.method.holder)) {
- toBeImplemented.add(candidate);
- iterator.remove();
+ if (rewriter.isEmulatedInterface(candidate.method.holder)
+ && current.lookupVirtualMethod(candidate.method) != null) {
+ // A library class overrides an emulated interface method. This override is valid
+ // only if it goes through retarget core lib member, else it needs to be implemented.
+ Map<DexType, DexType> typeMap = retargetCoreLibMember.get(candidate.method.name);
+ if (typeMap != null && typeMap.containsKey(current.type)) {
+ // A rewrite needs to be performed, but instead of rewriting to the companion class,
+ // D8/R8 needs to rewrite to the retarget member.
+ toBeImplemented.add(current.lookupVirtualMethod(candidate.method));
+ } else {
+ toBeImplemented.add(candidate);
+ iterator.remove();
+ }
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/LinkedHashSetTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/LinkedHashSetTest.java
new file mode 100644
index 0000000..178af61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/LinkedHashSetTest.java
@@ -0,0 +1,120 @@
+// 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.corelib;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.LinkedHashSet;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LinkedHashSetTest extends CoreLibDesugarTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public LinkedHashSetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testLinkedHashSetOverrides() throws Exception {
+ String stdOut =
+ testForD8()
+ .addInnerClasses(LinkedHashSetTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccess()
+ .getStdOut();
+ assertLines2By2Correct(stdOut);
+ }
+
+ static class Executor {
+ @SuppressWarnings("RedundantOperationOnEmptyContainer")
+ public static void main(String[] args) {
+ Spliterator<String> spliterator;
+
+ // Spliterator of Set is only distinct.
+ // Spliterator of LinkedHashSet is distinct and ordered.
+ // Spliterator of CustomLinkedHashSetOverride is distinct, ordered and immutable.
+ // If an incorrect method is found, characteristics are incorrect.
+
+ spliterator = new LinkedHashSet<String>().spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+
+ spliterator = new CustomLinkedHashSetOverride<String>().spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("true");
+
+ spliterator = new CustomLinkedHashSetNoOverride<String>().spliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+
+ spliterator = new CustomLinkedHashSetOverride<String>().superSpliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("false");
+
+ spliterator = new SubclassOverride<String>().superSpliterator();
+ System.out.println(spliterator.hasCharacteristics(Spliterator.DISTINCT));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.ORDERED));
+ System.out.println("true");
+ System.out.println(spliterator.hasCharacteristics(Spliterator.IMMUTABLE));
+ System.out.println("true");
+ }
+ }
+
+ static class CustomLinkedHashSetOverride<E> extends LinkedHashSet<E> {
+
+ @Override
+ public Spliterator<E> spliterator() {
+ return Spliterators.spliterator(
+ this, Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.IMMUTABLE);
+ }
+
+ public Spliterator<E> superSpliterator() {
+ return super.spliterator();
+ }
+ }
+
+ static class SubclassOverride<E> extends CustomLinkedHashSetOverride<E> {
+ @Override
+ public Spliterator<E> superSpliterator() {
+ return super.spliterator();
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class CustomLinkedHashSetNoOverride<E> extends LinkedHashSet<E> {}
+}