Desugared Library: Pay-as-you-go emulated interfaces
- Merge of the interface duplicator into the ClassProcessor since the
boundary where interfaces should be inserted is no longer the simple
isLibraryClass heuristic.
Bug: 161399032
Change-Id: I57891de0113f92b94d0b1a44b11b97d82dcacada
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 f2ae9a1..758830d 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
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -29,9 +30,12 @@
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -79,6 +83,12 @@
boolean isEmpty() {
return signatures.isEmpty();
}
+
+ public MethodSignatures withoutAll(MethodSignatures other) {
+ Set<Wrapper<DexMethod>> merged = new HashSet<>(signatures);
+ merged.removeAll(other.signatures);
+ return signatures.size() == merged.size() ? this : new MethodSignatures(merged);
+ }
}
// Collection of information known at the point of a given (non-library) class.
@@ -86,7 +96,8 @@
// class hierarchy. Thus, in the case of additions the parent pointer will contain prior info.
private static class ClassInfo {
- static final ClassInfo EMPTY = new ClassInfo(null, ImmutableList.of());
+ static final ClassInfo EMPTY =
+ new ClassInfo(null, ImmutableList.of(), EmulatedInterfaceInfo.EMPTY);
final ClassInfo parent;
@@ -94,17 +105,26 @@
// class hierarchy. This set consists of the default interface methods, i.e., the targets of the
// forwarding methods, *not* the forwarding methods themselves.
final ImmutableList<DexEncodedMethod> forwardedMethodTargets;
+ // If the forwarding methods for the emulated interface methods have not been added yet,
+ // this contains the information to add it in the subclasses.
+ final EmulatedInterfaceInfo emulatedInterfaceInfo;
- ClassInfo(ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+ ClassInfo(
+ ClassInfo parent,
+ ImmutableList<DexEncodedMethod> forwardedMethodTargets,
+ EmulatedInterfaceInfo emulatedInterfaceInfo) {
this.parent = parent;
this.forwardedMethodTargets = forwardedMethodTargets;
+ this.emulatedInterfaceInfo = emulatedInterfaceInfo;
}
static ClassInfo create(
- ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+ ClassInfo parent,
+ ImmutableList<DexEncodedMethod> forwardedMethodTargets,
+ EmulatedInterfaceInfo emulatedInterfaceInfo) {
return forwardedMethodTargets.isEmpty()
? parent
- : new ClassInfo(parent, forwardedMethodTargets);
+ : new ClassInfo(parent, forwardedMethodTargets, emulatedInterfaceInfo);
}
public boolean isEmpty() {
@@ -117,6 +137,107 @@
}
}
+ // Collection of information on what signatures and what emulated interfaces require
+ // forwarding methods for library classes and interfaces.
+ private static class SignaturesInfo {
+
+ static final SignaturesInfo EMPTY =
+ new SignaturesInfo(MethodSignatures.EMPTY, EmulatedInterfaceInfo.EMPTY);
+
+ final MethodSignatures signatures;
+ final EmulatedInterfaceInfo emulatedInterfaceInfo;
+
+ private SignaturesInfo(
+ MethodSignatures methodsToForward, EmulatedInterfaceInfo emulatedInterfaceInfo) {
+ this.signatures = methodsToForward;
+ this.emulatedInterfaceInfo = emulatedInterfaceInfo;
+ }
+
+ public static SignaturesInfo create(MethodSignatures signatures) {
+ if (signatures.isEmpty()) {
+ return EMPTY;
+ }
+ return new SignaturesInfo(signatures, EmulatedInterfaceInfo.EMPTY);
+ }
+
+ public SignaturesInfo merge(SignaturesInfo other) {
+ if (isEmpty()) {
+ return other;
+ }
+ if (other.isEmpty()) {
+ return this;
+ }
+ return new SignaturesInfo(
+ signatures.merge(other.signatures),
+ emulatedInterfaceInfo.merge(other.emulatedInterfaceInfo));
+ }
+
+ public MethodSignatures emulatedInterfaceSignaturesToForward() {
+ return emulatedInterfaceInfo.signatures.withoutAll(signatures);
+ }
+
+ boolean isEmpty() {
+ return signatures.isEmpty() && emulatedInterfaceInfo.isEmpty();
+ }
+
+ public SignaturesInfo withSignatures(MethodSignatures additions) {
+ if (additions.isEmpty()) {
+ return this;
+ }
+ MethodSignatures newSignatures = signatures.merge(additions);
+ return new SignaturesInfo(newSignatures, emulatedInterfaceInfo);
+ }
+
+ public SignaturesInfo withEmulatedInterfaceInfo(
+ EmulatedInterfaceInfo additionalEmulatedInterfaceInfo) {
+ if (additionalEmulatedInterfaceInfo.isEmpty()) {
+ return this;
+ }
+ return new SignaturesInfo(
+ signatures, emulatedInterfaceInfo.merge(additionalEmulatedInterfaceInfo));
+ }
+ }
+
+ // List of emulated interfaces and corresponding signatures which may require forwarding methods.
+ // If one of the signatures has an override, then the class holding the override is required to
+ // add the forwarding methods for all signatures, and introduce the corresponding emulated
+ // interface in its interfaces attribute for correct emulated dispatch.
+ // If no override is present, then no forwarding methods are required, the class relies on the
+ // default behavior of the emulated dispatch.
+ private static class EmulatedInterfaceInfo {
+
+ static final EmulatedInterfaceInfo EMPTY =
+ new EmulatedInterfaceInfo(MethodSignatures.EMPTY, ImmutableSet.of());
+
+ final MethodSignatures signatures;
+ final ImmutableSet<DexType> emulatedInterfaces;
+
+ private EmulatedInterfaceInfo(
+ MethodSignatures methodsToForward, ImmutableSet<DexType> emulatedInterfaces) {
+ this.signatures = methodsToForward;
+ this.emulatedInterfaces = emulatedInterfaces;
+ }
+
+ public EmulatedInterfaceInfo merge(EmulatedInterfaceInfo other) {
+ if (isEmpty()) {
+ return other;
+ }
+ if (other.isEmpty()) {
+ return this;
+ }
+ ImmutableSet.Builder<DexType> newEmulatedInterfaces = ImmutableSet.builder();
+ newEmulatedInterfaces.addAll(emulatedInterfaces);
+ newEmulatedInterfaces.addAll(other.emulatedInterfaces);
+ return new EmulatedInterfaceInfo(
+ signatures.merge(other.signatures), newEmulatedInterfaces.build());
+ }
+
+ public boolean isEmpty() {
+ assert !emulatedInterfaces.isEmpty() || signatures.isEmpty();
+ return emulatedInterfaces.isEmpty();
+ }
+ }
+
// Helper to keep track of the direct active subclass and nearest program subclass for reporting.
private static class ReportingContext {
@@ -181,10 +302,10 @@
private final Map<DexClass, ClassInfo> classInfo = new IdentityHashMap<>();
// Mapping from library classes to their information summary.
- private final Map<DexLibraryClass, MethodSignatures> libraryClassInfo = new IdentityHashMap<>();
+ private final Map<DexLibraryClass, SignaturesInfo> libraryClassInfo = new IdentityHashMap<>();
// Mapping from arbitrary interfaces to an information summary.
- private final Map<DexClass, MethodSignatures> interfaceInfo = new IdentityHashMap<>();
+ private final Map<DexClass, SignaturesInfo> interfaceInfo = new IdentityHashMap<>();
// Mapping from actual program classes to the synthesized forwarding methods to be created.
private final Map<DexProgramClass, ProgramMethodSet> newSyntheticMethods =
@@ -228,35 +349,113 @@
}
// Computes the set of method signatures that may need forwarding methods on derived classes.
- private MethodSignatures computeInterfaceInfo(DexClass iface, MethodSignatures signatures) {
+ private SignaturesInfo computeInterfaceInfo(DexClass iface, SignaturesInfo interfaceInfo) {
assert iface.isInterface();
assert iface.superType == dexItemFactory.objectType;
+ assert !rewriter.isEmulatedInterface(iface.type);
// Add non-library default methods as well as those for desugared library classes.
if (!iface.isLibraryClass() || (needsLibraryInfo() && rewriter.isInDesugaredLibrary(iface))) {
- Set<Wrapper<DexMethod>> additions =
- new HashSet<>(iface.getMethodCollection().numberOfVirtualMethods());
- for (DexEncodedMethod method : iface.virtualMethods(DexEncodedMethod::isDefaultMethod)) {
- additions.add(equivalence.wrap(method.method));
- }
- if (!additions.isEmpty()) {
- signatures = signatures.merge(MethodSignatures.create(additions));
- }
+ MethodSignatures signatures = getDefaultMethods(iface);
+ interfaceInfo = interfaceInfo.withSignatures(signatures);
}
- return signatures;
+ return interfaceInfo;
+ }
+
+ private SignaturesInfo computeEmulatedInterfaceInfo(
+ DexClass iface, SignaturesInfo interfaceInfo) {
+ assert iface.isInterface();
+ assert iface.superType == dexItemFactory.objectType;
+ assert rewriter.isEmulatedInterface(iface.type);
+ assert needsLibraryInfo();
+ MethodSignatures signatures = getDefaultMethods(iface);
+ EmulatedInterfaceInfo emulatedInterfaceInfo =
+ new EmulatedInterfaceInfo(signatures, ImmutableSet.of(iface.type));
+ return interfaceInfo.withEmulatedInterfaceInfo(emulatedInterfaceInfo);
+ }
+
+ private MethodSignatures getDefaultMethods(DexClass iface) {
+ assert iface.isInterface();
+ Set<Wrapper<DexMethod>> defaultMethods =
+ new HashSet<>(iface.getMethodCollection().numberOfVirtualMethods());
+ for (DexEncodedMethod method : iface.virtualMethods(DexEncodedMethod::isDefaultMethod)) {
+ defaultMethods.add(equivalence.wrap(method.method));
+ }
+ return MethodSignatures.create(defaultMethods);
}
// Computes the set of signatures of that may need forwarding methods on classes that derive
// from a library class.
- private MethodSignatures computeLibraryClassInfo(
- DexLibraryClass clazz, MethodSignatures signatures) {
+ private SignaturesInfo computeLibraryClassInfo(DexLibraryClass clazz, SignaturesInfo signatures) {
// The result is the identity as the library class does not itself contribute to the set.
return signatures;
}
// The computation of a class information and the insertions of forwarding methods.
private ClassInfo computeClassInfo(
- DexClass clazz, ClassInfo superInfo, MethodSignatures signatures) {
+ DexClass clazz, ClassInfo superInfo, SignaturesInfo signatureInfo) {
Builder<DexEncodedMethod> additionalForwards = ImmutableList.builder();
+ // First we deal with non-emulated interface desugaring.
+ resolveForwardingMethods(clazz, superInfo, signatureInfo.signatures, additionalForwards);
+ // Second we deal with emulated interface, if one method has override in the current class,
+ // we resolve them, else we propagate the emulated interface info down.
+ if (shouldResolveForwardingMethodsForEmulatedInterfaces(
+ clazz, signatureInfo.emulatedInterfaceInfo)) {
+ resolveForwardingMethods(
+ clazz,
+ superInfo,
+ signatureInfo.emulatedInterfaceSignaturesToForward(),
+ additionalForwards);
+ duplicateEmulatedInterfaces(clazz, signatureInfo.emulatedInterfaceInfo.emulatedInterfaces);
+ return ClassInfo.create(superInfo, additionalForwards.build(), EmulatedInterfaceInfo.EMPTY);
+ }
+ return ClassInfo.create(
+ superInfo, additionalForwards.build(), signatureInfo.emulatedInterfaceInfo);
+ }
+
+ // All classes implementing an emulated interface and overriding a default method should now
+ // implement the interface and the emulated one for correct emulated dispatch.
+ // The class signature won't include the correct type parameters for the duplicated interfaces,
+ // i.e., there will be foo.A instead of foo.A<K,V>, but such parameters are unused.
+ private void duplicateEmulatedInterfaces(
+ DexClass clazz, ImmutableSet<DexType> emulatedInterfaces) {
+ if (clazz.isNotProgramClass()) {
+ return;
+ }
+ // We need to introduce them in deterministic order for deterministic compilation.
+ ArrayList<DexType> sortedEmulatedInterfaces = new ArrayList<>(emulatedInterfaces);
+ Collections.sort(sortedEmulatedInterfaces, DexType::slowCompareTo);
+ List<GenericSignature.ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>();
+ for (DexType extraInterface : sortedEmulatedInterfaces) {
+ extraInterfaceSignatures.add(
+ new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(extraInterface)));
+ }
+ clazz.asProgramClass().addExtraInterfaces(extraInterfaceSignatures);
+ }
+
+ // If any of the signature would lead to a different behavior than the default method on the
+ // emulated interface, we need to resolve the forwarding methods.
+ private boolean shouldResolveForwardingMethodsForEmulatedInterfaces(
+ DexClass clazz, EmulatedInterfaceInfo emulatedInterfaceInfo) {
+ AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+ for (Wrapper<DexMethod> signature : emulatedInterfaceInfo.signatures.signatures) {
+ ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(signature.get(), clazz);
+ if (resolutionResult.isFailedResolution()) {
+ return true;
+ }
+ DexClass resolvedHolder = resolutionResult.asSingleResolution().getResolvedHolder();
+ if (!resolvedHolder.isLibraryClass()
+ && !emulatedInterfaceInfo.emulatedInterfaces.contains(resolvedHolder.type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void resolveForwardingMethods(
+ DexClass clazz,
+ ClassInfo superInfo,
+ MethodSignatures signatures,
+ Builder<DexEncodedMethod> additionalForwards) {
for (Wrapper<DexMethod> wrapper : signatures.signatures) {
resolveForwardForSignature(
clazz,
@@ -268,7 +467,6 @@
}
});
}
- return ClassInfo.create(superInfo, additionalForwards.build());
}
// Looks up a method signature from the point of 'clazz', if it can dispatch to a default method
@@ -439,7 +637,10 @@
// a library class or is not, but cannot be both.
ReportingContext thisContext = context.forClass(clazz);
ClassInfo superInfo = visitClassInfo(clazz.superType, thisContext);
- MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+ SignaturesInfo signatures = visitLibraryClassInfo(clazz.superType);
+ // The class may inherit emulated interface info from its program superclass if the latter
+ // did not require to resolve the forwarding methods for emualted interfaces.
+ signatures = signatures.withEmulatedInterfaceInfo(superInfo.emulatedInterfaceInfo);
assert superInfo.isEmpty() || signatures.isEmpty();
for (DexType iface : clazz.interfaces.values) {
signatures = signatures.merge(visitInterfaceInfo(iface, thisContext));
@@ -447,24 +648,24 @@
return computeClassInfo(clazz, superInfo, signatures);
}
- private MethodSignatures visitLibraryClassInfo(DexType type) {
+ private SignaturesInfo visitLibraryClassInfo(DexType type) {
// No desugaring required, no library class analysis.
if (ignoreLibraryInfo()) {
- return MethodSignatures.EMPTY;
+ return SignaturesInfo.EMPTY;
}
DexClass clazz = definitionOrNull(type, LibraryReportingContext.LIBRARY_CONTEXT);
- return clazz == null ? MethodSignatures.EMPTY : visitLibraryClassInfo(clazz);
+ return clazz == null ? SignaturesInfo.EMPTY : visitLibraryClassInfo(clazz);
}
- private MethodSignatures visitLibraryClassInfo(DexClass clazz) {
+ private SignaturesInfo visitLibraryClassInfo(DexClass clazz) {
assert !clazz.isInterface();
return clazz.isLibraryClass()
? libraryClassInfo.computeIfAbsent(clazz.asLibraryClass(), this::visitLibraryClassInfoRaw)
- : MethodSignatures.EMPTY;
+ : SignaturesInfo.EMPTY;
}
- private MethodSignatures visitLibraryClassInfoRaw(DexLibraryClass clazz) {
- MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+ private SignaturesInfo visitLibraryClassInfoRaw(DexLibraryClass clazz) {
+ SignaturesInfo signatures = visitLibraryClassInfo(clazz.superType);
for (DexType iface : clazz.interfaces.values) {
signatures =
signatures.merge(visitInterfaceInfo(iface, LibraryReportingContext.LIBRARY_CONTEXT));
@@ -472,24 +673,26 @@
return computeLibraryClassInfo(clazz, signatures);
}
- private MethodSignatures visitInterfaceInfo(DexType iface, ReportingContext context) {
+ private SignaturesInfo visitInterfaceInfo(DexType iface, ReportingContext context) {
DexClass definition = definitionOrNull(iface, context);
- return definition == null ? MethodSignatures.EMPTY : visitInterfaceInfo(definition, context);
+ return definition == null ? SignaturesInfo.EMPTY : visitInterfaceInfo(definition, context);
}
- private MethodSignatures visitInterfaceInfo(DexClass iface, ReportingContext context) {
+ private SignaturesInfo visitInterfaceInfo(DexClass iface, ReportingContext context) {
if (iface.isLibraryClass() && ignoreLibraryInfo()) {
- return MethodSignatures.EMPTY;
+ return SignaturesInfo.EMPTY;
}
return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
}
- private MethodSignatures visitInterfaceInfoRaw(DexClass iface, ReportingContext context) {
+ private SignaturesInfo visitInterfaceInfoRaw(DexClass iface, ReportingContext context) {
ReportingContext thisContext = context.forClass(iface);
- MethodSignatures signatures = MethodSignatures.EMPTY;
+ SignaturesInfo interfaceInfo = SignaturesInfo.EMPTY;
for (DexType superiface : iface.interfaces.values) {
- signatures = signatures.merge(visitInterfaceInfo(superiface, thisContext));
+ interfaceInfo = interfaceInfo.merge(visitInterfaceInfo(superiface, thisContext));
}
- return computeInterfaceInfo(iface, signatures);
+ return rewriter.isEmulatedInterface(iface.type)
+ ? computeEmulatedInterfaceInfo(iface, interfaceInfo)
+ : computeInterfaceInfo(iface, interfaceInfo);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java
deleted file mode 100644
index 6f72091..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2020, 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.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class DesugaredLibraryEmulatedInterfaceDuplicator {
-
- final AppView<?> appView;
- final Map<DexType, DexType> emulatedInterfaces;
-
- public DesugaredLibraryEmulatedInterfaceDuplicator(AppView<?> appView) {
- this.appView = appView;
- emulatedInterfaces =
- appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
- }
-
- public void duplicateEmulatedInterfaces() {
- // All classes implementing an emulated interface now implements the interface and the
- // emulated one, as well as hidden overrides, for correct emulated dispatch.
- // We not that duplicated interfaces won't feature the correct type parameters in the
- // class signature since such signature is expected to be unused.
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (clazz.type == appView.dexItemFactory().objectType) {
- continue;
- }
- if (emulatedInterfaces.containsKey(clazz.type)) {
- transformEmulatedInterfaces(clazz);
- } else {
- duplicateEmulatedInterfaces(clazz);
- }
- }
- }
-
- private void transformEmulatedInterfaces(DexProgramClass clazz) {
- List<ClassTypeSignature> newInterfaces = new ArrayList<>();
- GenericSignature.ClassSignature classSignature = clazz.getClassSignature();
- for (int i = 0; i < clazz.interfaces.size(); i++) {
- DexType itf = clazz.interfaces.values[i];
- assert emulatedInterfaces.containsKey(itf);
- List<FieldTypeSignature> typeArguments;
- if (classSignature == null) {
- typeArguments = Collections.emptyList();
- } else {
- ClassTypeSignature classTypeSignature = classSignature.superInterfaceSignatures().get(i);
- assert itf == classTypeSignature.type();
- typeArguments = classTypeSignature.typeArguments();
- }
- newInterfaces.add(new ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
- }
- clazz.replaceInterfaces(newInterfaces);
- }
-
- private void duplicateEmulatedInterfaces(DexProgramClass clazz) {
- List<DexType> extraInterfaces = new ArrayList<>();
- LinkedList<DexClass> workList = new LinkedList<>();
- Set<DexType> processed = Sets.newIdentityHashSet();
- workList.add(clazz);
- while (!workList.isEmpty()) {
- DexClass dexClass = workList.removeFirst();
- if (processed.contains(dexClass.type)) {
- continue;
- }
- processed.add(dexClass.type);
- if (dexClass.superType != appView.dexItemFactory().objectType) {
- processSuperType(clazz.superType, extraInterfaces, workList);
- }
- for (DexType itf : dexClass.interfaces) {
- processSuperType(itf, extraInterfaces, workList);
- }
- }
- extraInterfaces = removeDuplicates(extraInterfaces);
- List<ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>();
- for (DexType extraInterface : extraInterfaces) {
- extraInterfaceSignatures.add(new ClassTypeSignature(extraInterface));
- }
- clazz.addExtraInterfaces(extraInterfaceSignatures);
- }
-
- private List<DexType> removeDuplicates(List<DexType> extraInterfaces) {
- if (extraInterfaces.size() <= 1) {
- return extraInterfaces;
- }
- // TODO(b/161399032): It would be nice to remove duplicate based on inheritance, i.e.,
- // if there is ConcurrentMap<K,V> and Map<K,V>, Map<K,V> can be removed.
- return new ArrayList<>(new HashSet<>(extraInterfaces));
- }
-
- void processSuperType(
- DexType superType, List<DexType> extraInterfaces, LinkedList<DexClass> workList) {
- if (emulatedInterfaces.containsKey(superType)) {
- extraInterfaces.add(emulatedInterfaces.get(superType));
- } else {
- DexClass superClass = appView.definitionFor(superType);
- if (shouldProcessSuperclass(superClass)) {
- workList.add(superClass);
- }
- }
- }
-
- private boolean shouldProcessSuperclass(DexClass superclazz) {
- if (appView.options().isDesugaredLibraryCompilation()) {
- return false;
- }
- // TODO(b/161399032): Pay-as-you-go design: stop duplication on library boundaries.
- return superclazz != null && superclazz.isLibraryClass();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 61a5c56..ee05401 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -26,8 +26,10 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.GenericSignature;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -207,6 +209,10 @@
return emulatedInterfaces.containsKey(itf);
}
+ DexType getEmulatedInterface(DexType itf) {
+ return emulatedInterfaces.get(itf);
+ }
+
// Rewrites the references to static and default interface methods.
// NOTE: can be called for different methods concurrently.
public void rewriteMethodReferences(DexEncodedMethod encodedMethod, IRCode code) {
@@ -430,8 +436,19 @@
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
if (dexType != null) {
- rewriteCurrentInstructionToEmulatedInterfaceCall(
- dexType, invokedMethod, invokeMethod, instructions);
+ // The call potentially ends up in a library class, in which case we need to rewrite,
+ // since the code may be in the desugared library.
+ SingleResolutionResult resolution =
+ appView
+ .appInfoForDesugaring()
+ .resolveMethod(invokedMethod, invokeMethod.getInterfaceBit())
+ .asSingleResolution();
+ if (resolution != null
+ && (resolution.getResolvedHolder().isLibraryClass()
+ || appView.options().isDesugaredLibraryCompilation())) {
+ rewriteCurrentInstructionToEmulatedInterfaceCall(
+ dexType, invokedMethod, invokeMethod, instructions);
+ }
}
}
}
@@ -448,17 +465,6 @@
if (dexClass == null) {
return null;
}
- // TODO(b/120884788): Make sure program class are looked up before library class.
- // Since program classes are desugared, no need to rewrite invokes which can target only
- // program types.
- if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
- return null;
- }
- // Since desugared library classes are desugared, no need to rewrite invokes which can target
- // only such classes program types.
- if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
- return null;
- }
DexEncodedMethod singleTarget = null;
if (dexClass.isInterface()) {
// Look for exact method on the interface.
@@ -934,11 +940,11 @@
if (appView.options().isDesugaredLibraryCompilation()) {
generateEmulateInterfaceLibrary(builder);
}
- new DesugaredLibraryEmulatedInterfaceDuplicator(appView).duplicateEmulatedInterfaces();
// Process all classes first. Add missing forwarding methods to
// replace desugared default interface methods.
processClasses(builder, flavour, synthesizedMethods::add);
+ transformEmulatedInterfaces();
// Process interfaces, create companion or dispatch class if needed, move static
// methods to companion class, copy default interface methods to companion classes,
@@ -976,6 +982,40 @@
clear();
}
+ private void transformEmulatedInterfaces() {
+ for (DexType dexType : emulatedInterfaces.keySet()) {
+ DexClass dexClass = appView.definitionFor(dexType);
+ if (dexClass != null && dexClass.isProgramClass()) {
+ transformEmulatedInterfaces(dexClass.asProgramClass());
+ }
+ }
+ }
+
+ // The method transforms emulated interface such as they implement the rewritten version
+ // of each emulated interface they implement. Such change should have no effect on the look-up
+ // results, since each class implementing an emulated interface should also implement the
+ // rewritten one.
+ private void transformEmulatedInterfaces(DexProgramClass clazz) {
+ List<GenericSignature.ClassTypeSignature> newInterfaces = new ArrayList<>();
+ GenericSignature.ClassSignature classSignature = clazz.getClassSignature();
+ for (int i = 0; i < clazz.interfaces.size(); i++) {
+ DexType itf = clazz.interfaces.values[i];
+ assert emulatedInterfaces.containsKey(itf);
+ List<GenericSignature.FieldTypeSignature> typeArguments;
+ if (classSignature == null) {
+ typeArguments = Collections.emptyList();
+ } else {
+ GenericSignature.ClassTypeSignature classTypeSignature =
+ classSignature.superInterfaceSignatures().get(i);
+ assert itf == classTypeSignature.type();
+ typeArguments = classTypeSignature.typeArguments();
+ }
+ newInterfaces.add(
+ new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
+ }
+ clazz.replaceInterfaces(newInterfaces);
+ }
+
private void clear() {
this.cache.clear();
this.synthesizedMethods.clear();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
index 42582e6..0689a75 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -4,15 +4,10 @@
package com.android.tools.r8.desugar.desugaredlibrary;
-import static junit.framework.TestCase.assertTrue;
-
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -55,7 +50,6 @@
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .inspect(this::assertForwardingMethods)
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
@@ -85,53 +79,6 @@
StringUtils.lines("false", "false", "false", "false", "false", "false"));
}
- private void assertForwardingMethods(CodeInspector inspector) {
- if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
- return;
- }
- ClassSubject cal = inspector.clazz(CustomArrayList.class);
- MethodSubject spliteratorCal = cal.method("j$.util.Spliterator", "spliterator");
- assertTrue(spliteratorCal.isPresent());
- assertTrue(
- spliteratorCal
- .streamInstructions()
- .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
- MethodSubject streamCal = cal.method("j$.util.stream.Stream", "stream");
- assertTrue(streamCal.isPresent());
- assertTrue(
- streamCal
- .streamInstructions()
- .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-
- ClassSubject clhs = inspector.clazz(CustomLinkedHashSet.class);
- MethodSubject spliteratorClhs = clhs.method("j$.util.Spliterator", "spliterator");
- assertTrue(spliteratorClhs.isPresent());
- assertTrue(
- spliteratorClhs
- .streamInstructions()
- .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("DesugarLinkedHashSet")));
- MethodSubject streamClhs = clhs.method("j$.util.stream.Stream", "stream");
- assertTrue(streamClhs.isPresent());
- assertTrue(
- streamClhs
- .streamInstructions()
- .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-
- ClassSubject cl = inspector.clazz(CustomList.class);
- MethodSubject spliteratorCl = cl.method("j$.util.Spliterator", "spliterator");
- assertTrue(spliteratorCl.isPresent());
- assertTrue(
- spliteratorCl
- .streamInstructions()
- .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
- MethodSubject streamCl = cl.method("j$.util.stream.Stream", "stream");
- assertTrue(streamCl.isPresent());
- assertTrue(
- streamCl
- .streamInstructions()
- .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
- }
-
static class Executor {
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 2330854..3df7246 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -24,7 +24,6 @@
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -60,21 +59,18 @@
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
.assertNoMessages()
- .inspect(
- inspector -> {
- this.assertCustomCollectionCallsCorrect(inspector, false);
- })
+ .inspect(this::assertCustomCollectionCallsCorrect)
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
- .run(parameters.getRuntime(), EXECUTOR)
+ .run(parameters.getRuntime(), Executor.class)
.assertSuccess();
- assertResultCorrect(d8TestRunResult.getStdOut(), d8TestRunResult.getStdErr());
+ assertResultCorrect(d8TestRunResult.getStdOut());
}
- private void assertResultCorrect(String stdOut, String stdErr) {
+ private void assertResultCorrect(String stdOut) {
if (requiresEmulatedInterfaceCoreLibDesugaring(parameters) && !shrinkDesugaredLibrary) {
// When shrinking the class names are not printed correctly anymore due to minification.
// Expected output is emulated interfaces expected output.
@@ -92,29 +88,20 @@
.addKeepClassAndMembersRules(Executor.class)
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .inspect(
- inspector -> {
- this.assertCustomCollectionCallsCorrect(inspector, true);
- })
+ .inspect(this::assertCustomCollectionCallsCorrect)
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
keepRuleConsumer.get(),
shrinkDesugaredLibrary)
- .run(parameters.getRuntime(), EXECUTOR)
+ .run(parameters.getRuntime(), Executor.class)
.assertSuccess();
- assertResultCorrect(r8TestRunResult.getStdOut(), r8TestRunResult.getStdErr());
+ assertResultCorrect(r8TestRunResult.getStdOut());
}
- private void assertCustomCollectionCallsCorrect(CodeInspector inspector, boolean r8) {
+ private void assertCustomCollectionCallsCorrect(CodeInspector inspector) {
MethodSubject direct = inspector.clazz(EXECUTOR).uniqueMethodWithName("directTypes");
- // TODO(b/134732760): Due to memberRebinding, R8 is not as precise as D8 regarding
- // desugaring of invokes. This will be fixed when creation of desugared method is moved
- // ahead of R8 compilation pipeline.
- if (!r8) {
- Assert.assertFalse(
- direct.streamInstructions().anyMatch(instr -> instr.toString().contains("$-EL")));
- } else if (requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+ if (requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
assertTrue(
direct
.streamInstructions()
@@ -140,22 +127,17 @@
instr ->
instr.toString().contains("$-EL")
|| instr.toString().contains("Comparator$-CC")));
- inherited.streamInstructions().forEach(x -> assertInheritedDispatchCorrect(x, r8));
+ inherited.streamInstructions().forEach(this::assertInheritedDispatchCorrect);
}
- private void assertInheritedDispatchCorrect(InstructionSubject instructionSubject, boolean r8) {
+ private void assertInheritedDispatchCorrect(InstructionSubject instructionSubject) {
if (!instructionSubject.isConstString(JumboStringMode.ALLOW)) {
- for (String s : new String[] {"stream", "parallelStream", "spliterator", "sort"}) {
+ for (String s : new String[] {">stream", "spliterator", "sort"}) {
if (instructionSubject.toString().contains(s)) {
- if (!r8 || instructionSubject.isInvokeStatic()) {
assertTrue(instructionSubject.isInvokeStatic());
assertTrue(
instructionSubject.toString().contains("$-EL")
|| instructionSubject.toString().contains("Comparator$-CC"));
- } else {
- // Has been devirtualized.
- assertTrue(instructionSubject.isInvokeVirtual());
- }
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
new file mode 100644
index 0000000..a6ea994
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
@@ -0,0 +1,200 @@
+// 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.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NoDesugaredLibraryDexFileTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public NoDesugaredLibraryDexFileTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCustomCollectionD8() throws Exception {
+ Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(NoDesugaredLibraryDexFileTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertNoForwardingStreamMethod)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines("1", "0");
+ assertTrue(keepRuleConsumer.get().isEmpty());
+ }
+
+ @Test
+ public void testCustomCollectionR8() throws Exception {
+ Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .addInnerClasses(NoDesugaredLibraryDexFileTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(Executor.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertNoForwardingStreamMethod)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines("1", "0");
+ assertTrue(keepRuleConsumer.get().isEmpty());
+ }
+
+ private void assertNoForwardingStreamMethod(CodeInspector inspector) {
+ assertTrue(inspector.clazz(CustomArrayList.class).uniqueMethodWithName("stream").isAbsent());
+ assertTrue(inspector.clazz(CustomSortedSet.class).uniqueMethodWithName("stream").isAbsent());
+ }
+
+ static class Executor {
+
+ // No method here is using any emulated interface default method, so, there is no need for
+ // the desugared library dex file despite desugared library being enabled.
+ public static void main(String[] args) {
+ ArrayList<Object> cArrayList = new CustomArrayList<>();
+ SortedSet<Object> cSortedSet = new CustomSortedSet<>();
+ cArrayList.add(1);
+ cSortedSet.add(1);
+ System.out.println(cArrayList.size());
+ System.out.println(cSortedSet.size());
+ }
+ }
+
+ // Extends directly a core library class which implements other library interfaces.
+ private static class CustomArrayList<E> extends ArrayList<E> {}
+
+ // Implements directly a core library interface which implements other library interfaces.
+ static class CustomSortedSet<E> implements SortedSet<E> {
+
+ @Nullable
+ @Override
+ public Comparator<? super E> comparator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public SortedSet<E> subSet(E fromElement, E toElement) {
+ return new CustomSortedSet<>();
+ }
+
+ @NotNull
+ @Override
+ public SortedSet<E> headSet(E toElement) {
+ return new CustomSortedSet<>();
+ }
+
+ @NotNull
+ @Override
+ public SortedSet<E> tailSet(E fromElement) {
+ return new CustomSortedSet<>();
+ }
+
+ @Override
+ public E first() {
+ return null;
+ }
+
+ @Override
+ public E last() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] a) {
+ return a;
+ }
+
+ @Override
+ public boolean add(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public boolean removeAll(@NotNull Collection c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection c) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection c) {
+ return false;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
new file mode 100644
index 0000000..98ad7dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
@@ -0,0 +1,206 @@
+// 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.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.SortedSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NoForwardingMethodsTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public NoForwardingMethodsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCustomCollectionD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(NoForwardingMethodsTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertNoForwardingStreamMethod)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines("str:1", "0");
+ }
+
+ @Test
+ public void testCustomCollectionR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .addInnerClasses(NoForwardingMethodsTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(Executor.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::assertNoForwardingStreamMethod)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines("str:1", "0");
+ }
+
+ private void assertNoForwardingStreamMethod(CodeInspector inspector) {
+ assertTrue(inspector.clazz(CustomArrayList.class).uniqueMethodWithName("stream").isAbsent());
+ assertTrue(inspector.clazz(CustomSortedSet.class).uniqueMethodWithName("stream").isAbsent());
+ }
+
+ static class Executor {
+
+ // The main method is using stream, but since there are no overrides, the classes should not
+ // have any forwarding method.
+ public static void main(String[] args) {
+ ArrayList<Object> cArrayList = new CustomArrayList<>();
+ SortedSet<Object> cSortedSet = new CustomSortedSet<>();
+ cArrayList.add(1);
+ cSortedSet.add(1);
+ System.out.println(cArrayList.stream().map(i -> "str:" + i).toArray()[0]);
+ System.out.println(cSortedSet.stream().filter(Objects::isNull).count());
+ }
+ }
+
+ // Extends directly a core library class which implements other library interfaces.
+ private static class CustomArrayList<E> extends ArrayList<E> {}
+
+ // Implements directly a core library interface which implements other library interfaces.
+ static class CustomSortedSet<E> implements SortedSet<E> {
+
+ @Nullable
+ @Override
+ public Comparator<? super E> comparator() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public SortedSet<E> subSet(E fromElement, E toElement) {
+ return new CustomSortedSet<>();
+ }
+
+ @NotNull
+ @Override
+ public SortedSet<E> headSet(E toElement) {
+ return new CustomSortedSet<>();
+ }
+
+ @NotNull
+ @Override
+ public SortedSet<E> tailSet(E fromElement) {
+ return new CustomSortedSet<>();
+ }
+
+ @Override
+ public E first() {
+ return null;
+ }
+
+ @Override
+ public E last() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public Iterator<E> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @NotNull
+ @Override
+ public Object[] toArray() {
+ return new Object[0];
+ }
+
+ @NotNull
+ @Override
+ public <T> T[] toArray(@NotNull T[] a) {
+ return a;
+ }
+
+ @Override
+ public boolean add(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return false;
+ }
+
+ @Override
+ public boolean addAll(@NotNull Collection c) {
+ return false;
+ }
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public boolean removeAll(@NotNull Collection c) {
+ return false;
+ }
+
+ @Override
+ public boolean retainAll(@NotNull Collection c) {
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection c) {
+ return false;
+ }
+ }
+}