Extend interface method resolution to account for multiple maximal specific
Bug: b/230289235
Bug: b/214382176
Bug: b/226170842
Change-Id: I3fe50eeb47d61b7c59f774743cde02e1a2e60b9e
diff --git a/src/main/java/com/android/tools/r8/graph/LookupResult.java b/src/main/java/com/android/tools/r8/graph/LookupResult.java
index b8bc3c8..a23a5a6 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupResult.java
@@ -5,11 +5,13 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
public abstract class LookupResult {
@@ -163,6 +165,7 @@
private final Map<DexMethod, LookupMethodTarget> methodTargets = new IdentityHashMap<>();
private final List<LookupLambdaTarget> lambdaTargets = new ArrayList<>();
private final List<DexEncodedMethod> methodsCausingFailure = new ArrayList<>();
+ private final Set<DexType> typesCausingFailure = Sets.newIdentityHashSet();
private LookupResultCollectionState state;
public Builder addMethodTarget(LookupMethodTarget methodTarget) {
@@ -181,6 +184,11 @@
return this;
}
+ public Builder addTypeCausingFailure(DexType typeCausingFailure) {
+ typesCausingFailure.add(typeCausingFailure);
+ return this;
+ }
+
public Builder setState(LookupResultCollectionState state) {
this.state = state;
return this;
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 a680b25..990f432 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.graph;
-import com.android.tools.r8.errors.Unreachable;
+import static com.android.tools.r8.graph.MethodResolution.UniquePathOracle.SplitToken.NO_SPLIT_TOKEN;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
import com.android.tools.r8.graph.MethodResolutionResult.ArrayCloneMethodResult;
import com.android.tools.r8.graph.MethodResolutionResult.ClassNotFoundResult;
import com.android.tools.r8.graph.MethodResolutionResult.IllegalAccessOrNoSuchMethodResult;
@@ -13,11 +15,17 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.function.Function;
/**
@@ -31,30 +39,36 @@
private final Function<DexType, ClassResolutionResult> definitionFor;
private final DexItemFactory factory;
private final boolean escapeIfLibraryHasProgramSuperType;
+ private final boolean canHaveIncompletePaths;
private MethodResolution(
Function<DexType, ClassResolutionResult> definitionFor,
DexItemFactory factory,
- boolean escapeIfLibraryHasProgramSuperType) {
+ boolean escapeIfLibraryHasProgramSuperType,
+ boolean canHaveIncompletePaths) {
this.definitionFor = definitionFor;
this.factory = factory;
this.escapeIfLibraryHasProgramSuperType = escapeIfLibraryHasProgramSuperType;
+ this.canHaveIncompletePaths = canHaveIncompletePaths;
}
+ @Deprecated
public static MethodResolution createLegacy(
Function<DexType, DexClass> definitionFor, DexItemFactory factory) {
+ // TODO(b/230289235): Remove this when R8/D8 can handle multiple definitions.
return new MethodResolution(
type -> {
DexClass clazz = definitionFor.apply(type);
return clazz == null ? ClassResolutionResult.NoResolutionResult.noResult() : clazz;
},
factory,
+ false,
false);
}
public static MethodResolution create(
Function<DexType, ClassResolutionResult> definitionFor, DexItemFactory factory) {
- return new MethodResolution(definitionFor, factory, true);
+ return new MethodResolution(definitionFor, factory, true, true);
}
private ClassResolutionResult definitionFor(DexType type) {
@@ -246,6 +260,128 @@
return builder;
}
+ /**
+ * UniquePathOracle will compute for all parent definitions in the hierarchy if a class is visited
+ * by all paths or not depending on class resolution resolving a type to either library or
+ * program/classpath definitions. It does so by visiting all paths upwards in the hierarchy and
+ * when a split point is seen it will create a "left" and "right" half rooted by the split type.
+ * If a definition is only seen by a single token it will always be on an incomplete path
+ * otherwise if the class is seen by both the left and right token it is visited by all:
+ *
+ * <pre>
+ * C_Library { left_B, right_B }
+ * / \
+ * B_Program extends C { left_B } B_Library extends C { right_B }
+ * \ /
+ * A_Program extends B { }
+ * </pre>
+ */
+ static class UniquePathOracle {
+
+ static class SplitToken {
+
+ static final SplitToken NO_SPLIT_TOKEN = new SplitToken(null);
+
+ private final DexType split;
+
+ private SplitToken(DexType split) {
+ this.split = split;
+ }
+
+ boolean isSplitToken() {
+ return this != NO_SPLIT_TOKEN;
+ }
+ }
+
+ private final Function<DexType, ClassResolutionResult> definitionFor;
+ private final boolean escapeIfLibraryHasProgramSuperType;
+ private final Map<DexClass, Map<DexType, SplitToken>> incompletePaths = new IdentityHashMap<>();
+ private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
+
+ public UniquePathOracle(
+ Function<DexType, ClassResolutionResult> definitionFor,
+ boolean escapeIfLibraryHasProgramSuperType) {
+ this.definitionFor = definitionFor;
+ this.escapeIfLibraryHasProgramSuperType = escapeIfLibraryHasProgramSuperType;
+ }
+
+ public boolean onIncompletePath(DexClass definition) {
+ Map<DexType, SplitToken> tokenMap = incompletePaths.get(definition);
+ assert tokenMap != null;
+ for (SplitToken value : tokenMap.values()) {
+ if (value.isSplitToken()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void lookupPath(DexType type, DexClass previousClass) {
+ lookupPath(type, previousClass, Collections.emptySet());
+ }
+
+ private void lookupPath(DexType type, DexClass previousClass, Set<SplitToken> splitTokens) {
+ if (splitTokens.isEmpty() && !seenTypes.add(type)) {
+ return;
+ }
+ ClassResolutionResult resolutionResult = definitionFor.apply(type);
+ resolutionResult.forEachClassResolutionResult(
+ clazz -> {
+ if (escapeIfLibraryHasProgramSuperType
+ && previousClass.isLibraryClass()
+ && !clazz.isLibraryClass()) {
+ return;
+ }
+ Set<SplitToken> currentSplitTokens;
+ if (resolutionResult.isMultipleClassResolutionResult()) {
+ currentSplitTokens = SetUtils.newIdentityHashSet(splitTokens);
+ currentSplitTokens.add(new SplitToken(type));
+ } else {
+ currentSplitTokens = splitTokens;
+ }
+ Map<DexType, SplitToken> paths =
+ incompletePaths.computeIfAbsent(clazz, ignoreKey(IdentityHashMap::new));
+ currentSplitTokens.forEach(
+ splitToken -> {
+ SplitToken otherSplitToken = paths.get(splitToken.split);
+ if (otherSplitToken == null) {
+ paths.put(splitToken.split, splitToken);
+ } else if (otherSplitToken != splitToken && otherSplitToken.isSplitToken()) {
+ // We have not seen this half of the split token, so it is visited on all paths.
+ paths.put(splitToken.split, NO_SPLIT_TOKEN);
+ }
+ });
+ clazz.interfaces.forEach(iface -> lookupPath(iface, clazz, currentSplitTokens));
+ if (clazz.superType != null) {
+ lookupPath(clazz.superType, clazz, currentSplitTokens);
+ }
+ });
+ }
+ }
+
+ static class AllUniquePathsOracle extends UniquePathOracle {
+
+ private static final AllUniquePathsOracle INSTANCE = new AllUniquePathsOracle();
+
+ static AllUniquePathsOracle getInstance() {
+ return INSTANCE;
+ }
+
+ private AllUniquePathsOracle() {
+ super(null, false);
+ }
+
+ @Override
+ void lookupPath(DexType type, DexClass previousClass) {
+ // Intentionally empty
+ }
+
+ @Override
+ public boolean onIncompletePath(DexClass definition) {
+ return false;
+ }
+ }
+
/** Helper method that builds the set of maximally specific methods. */
private void resolveMethodStep3Helper(
DexProto methodProto,
@@ -268,25 +404,59 @@
MaximallySpecificMethodsBuilder builder,
DexType superType,
List<DexType> interfaces) {
+ UniquePathOracle uniquePathOracle;
+ if (canHaveIncompletePaths) {
+ uniquePathOracle = new UniquePathOracle(definitionFor, escapeIfLibraryHasProgramSuperType);
+ interfaces.forEach(iFace -> uniquePathOracle.lookupPath(iFace, clazz));
+ if (superType != null) {
+ uniquePathOracle.lookupPath(superType, clazz);
+ }
+ } else {
+ uniquePathOracle = AllUniquePathsOracle.getInstance();
+ }
+ resolveMethodStep3Helper(
+ methodProto, methodName, clazz, builder, superType, interfaces, uniquePathOracle);
+ }
+
+ private void resolveMethodStep3Helper(
+ DexProto methodProto,
+ DexString methodName,
+ DexClass clazz,
+ MaximallySpecificMethodsBuilder builder,
+ DexType superType,
+ List<DexType> interfaces,
+ UniquePathOracle uniquePathOracle) {
for (DexType iface : interfaces) {
ClassResolutionResult classResolutionResult = definitionFor(iface);
- if (classResolutionResult.isMultipleClassResolutionResult()) {
- // TODO(b/214382176, b/226170842): Compute maximal specific set in precense of
- // multiple results.
- throw new Unreachable(
- "MethodResolution should not be passed definition with multiple results");
- }
classResolutionResult.forEachClassResolutionResult(
definition -> {
+ // Guard against going back into the program for resolution.
+ if (escapeIfLibraryHasProgramSuperType
+ && clazz != null
+ && clazz.isLibraryClass()
+ && !definition.isLibraryClass()) {
+ return;
+ }
+ if (classResolutionResult.isMultipleClassResolutionResult()) {
+ builder.addTypeWithMultipleDefinitions(iface);
+ }
assert definition.isInterface();
DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
if (isMaximallySpecificCandidate(result)) {
// The candidate is added and doing so will prohibit shadowed methods from being
// in the set.
- builder.addCandidate(definition, result);
+ builder.addCandidate(
+ definition, result, uniquePathOracle.onIncompletePath(definition));
} else {
// Look at the super-interfaces of this class and keep searching.
- resolveMethodStep3Helper(methodProto, methodName, definition, builder);
+ resolveMethodStep3Helper(
+ methodProto,
+ methodName,
+ definition,
+ builder,
+ definition.superType,
+ Arrays.asList(definition.interfaces.values),
+ uniquePathOracle);
}
});
}
@@ -302,7 +472,14 @@
&& !superClass.isLibraryClass()) {
return;
}
- resolveMethodStep3Helper(methodProto, methodName, superClass, builder);
+ resolveMethodStep3Helper(
+ methodProto,
+ methodName,
+ superClass,
+ builder,
+ superClass.superType,
+ Arrays.asList(superClass.interfaces.values),
+ uniquePathOracle);
});
}
}
@@ -386,9 +563,12 @@
// in the case that a type has a candidate which is shadowed by a subinterface, the map will
// map the class to a null entry, thus any addition to the map must check for key containment
// prior to writing.
- private final LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods =
- new LinkedHashMap<>();
+ private final LinkedHashMap<DexClass, DexEncodedMethod>
+ maximallySpecificMethodsOnCompletePaths = new LinkedHashMap<>();
+ private final LinkedHashMap<DexClass, DexEncodedMethod>
+ maximallySpecificMethodsOnIncompletePaths = new LinkedHashMap<>();
private final Function<DexType, ClassResolutionResult> definitionFor;
+ private final Set<DexType> typesWithMultipleDefinitions = Sets.newIdentityHashSet();
private final DexItemFactory factory;
private MaximallySpecificMethodsBuilder(
@@ -397,12 +577,28 @@
this.factory = factory;
}
- void addCandidate(DexClass holder, DexEncodedMethod method) {
+ void addTypeWithMultipleDefinitions(DexType type) {
+ typesWithMultipleDefinitions.add(type);
+ }
+
+ void addCandidate(DexClass holder, DexEncodedMethod method, boolean isIncompletePath) {
// If this candidate is already a candidate or it is shadowed, then no need to continue.
- if (maximallySpecificMethods.containsKey(holder)) {
+ if (isIncompletePath) {
+ if (!maximallySpecificMethodsOnIncompletePaths.containsKey(holder)) {
+ maximallySpecificMethodsOnIncompletePaths.put(holder, method);
+ }
+ // Incomplete paths can shadow complete path results if all partial paths is
+ // shadowing.
+ // I_L { f(); }
+ // / \
+ // J_L { f(); } // J_P { f(); }
+ // One way to track them is to count all coverings are equal to the splits. For now
+ // we bail out.
+ return;
+ } else if (maximallySpecificMethodsOnCompletePaths.containsKey(holder)) {
return;
}
- maximallySpecificMethods.put(holder, method);
+ maximallySpecificMethodsOnCompletePaths.put(holder, method);
// Prune exiting candidates and prohibit future candidates in the super hierarchy.
assert holder.isInterface();
assert holder.superType == factory.objectType;
@@ -415,29 +611,26 @@
if (type == null) {
return;
}
- ClassResolutionResult classResolutionResult = definitionFor.apply(type);
- if (classResolutionResult.isMultipleClassResolutionResult()) {
- // TODO(b/214382176, b/226170842): Compute maximal specific set in precense of
- // multiple results.
- throw new Unreachable(
- "MethodResolution should not be passed definition with multiple results");
- }
- classResolutionResult.forEachClassResolutionResult(
- clazz -> {
- assert clazz.isInterface();
- assert clazz.superType == factory.objectType;
- // A null entry signifies that the candidate is shadowed blocking future candidates.
- // If the candidate is already shadowed at this type there is no need to shadow
- // further up.
- if (maximallySpecificMethods.containsKey(clazz)
- && maximallySpecificMethods.get(clazz) == null) {
- return;
- }
- maximallySpecificMethods.put(clazz, null);
- for (DexType iface : clazz.interfaces.values) {
- markShadowed(iface);
- }
- });
+ definitionFor
+ .apply(type)
+ .forEachClassResolutionResult(
+ clazz -> {
+ assert clazz.isInterface();
+ assert clazz.superType == factory.objectType;
+ // A null entry signifies that the candidate is shadowed blocking future candidates.
+ // If the candidate is already shadowed at this type there is no need to shadow
+ // further up.
+ if (maximallySpecificMethodsOnCompletePaths.getOrDefault(
+ clazz, DexEncodedMethod.SENTINEL)
+ == null) {
+ return;
+ }
+ maximallySpecificMethodsOnCompletePaths.put(clazz, null);
+ maximallySpecificMethodsOnIncompletePaths.put(clazz, null);
+ for (DexType iface : clazz.interfaces.values) {
+ markShadowed(iface);
+ }
+ });
}
DexClassAndMethod lookup() {
@@ -450,40 +643,64 @@
}
private MethodResolutionResult internalResolve(DexClass initialResolutionHolder) {
- if (maximallySpecificMethods.isEmpty()) {
+ if (maximallySpecificMethodsOnCompletePaths.isEmpty()
+ && maximallySpecificMethodsOnIncompletePaths.isEmpty()) {
return NoSuchMethodResult.INSTANCE;
}
- // Fast path in the common case of a single method.
- if (maximallySpecificMethods.size() == 1) {
- return singleResultHelper(
- initialResolutionHolder, maximallySpecificMethods.entrySet().iterator().next());
+ List<Entry<DexClass, DexEncodedMethod>> nonAbstractOnComplete =
+ getNonAbstractMethods(maximallySpecificMethodsOnCompletePaths);
+ if (nonAbstractOnComplete.size() > 1) {
+ return IncompatibleClassResult.create(
+ ListUtils.map(nonAbstractOnComplete, Entry::getValue));
}
- Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
- List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
- new ArrayList<>(maximallySpecificMethods.size());
- for (Entry<DexClass, DexEncodedMethod> entry : maximallySpecificMethods.entrySet()) {
+ List<Entry<DexClass, DexEncodedMethod>> nonAbstractOnIncomplete =
+ getNonAbstractMethods(maximallySpecificMethodsOnIncompletePaths);
+ if (nonAbstractOnIncomplete.isEmpty()) {
+ // If there are no non-abstract methods, then any candidate on a complete path will suffice
+ // as a target. For deterministic resolution, we return the first mapped method (of the
+ // linked map).
+ if (nonAbstractOnComplete.isEmpty()) {
+ return singleResultHelper(
+ initialResolutionHolder, firstNonNullEntry(maximallySpecificMethodsOnCompletePaths));
+ } else {
+ // If there is exactly one non-abstract method (a default method) it is the resolution
+ // target.
+ return singleResultHelper(initialResolutionHolder, nonAbstractOnComplete.get(0));
+ }
+ } else {
+ // We cannot guarantee a single target or an error, so we have to report all on incomplete
+ // paths.
+ MethodResolutionResult.Builder builder =
+ MethodResolutionResult.builder().allowMultipleProgramResults();
+ if (nonAbstractOnComplete.isEmpty()) {
+ assert !typesWithMultipleDefinitions.isEmpty();
+ builder.addResolutionResult(new NoSuchMethodResult(typesWithMultipleDefinitions));
+ } else {
+ nonAbstractOnComplete.forEach(
+ entry ->
+ builder.addResolutionResult(singleResultHelper(initialResolutionHolder, entry)));
+ }
+ nonAbstractOnIncomplete.forEach(
+ entry ->
+ builder.addResolutionResult(singleResultHelper(initialResolutionHolder, entry)));
+ return builder.buildOrIfEmpty(NoSuchMethodResult.INSTANCE);
+ }
+ }
+
+ private List<Entry<DexClass, DexEncodedMethod>> getNonAbstractMethods(
+ Map<DexClass, DexEncodedMethod> candidates) {
+ List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods = new ArrayList<>();
+ for (Entry<DexClass, DexEncodedMethod> entry : candidates.entrySet()) {
DexEncodedMethod method = entry.getValue();
if (method == null) {
// Ignore shadowed candidates.
continue;
}
- if (firstMaximallySpecificMethod == null) {
- firstMaximallySpecificMethod = entry;
- }
if (method.isNonAbstractVirtualMethod()) {
nonAbstractMethods.add(entry);
}
}
- // If there are no non-abstract methods, then any candidate will suffice as a target.
- // For deterministic resolution, we return the first mapped method (of the linked map).
- if (nonAbstractMethods.isEmpty()) {
- return singleResultHelper(initialResolutionHolder, firstMaximallySpecificMethod);
- }
- // If there is exactly one non-abstract method (a default method) it is the resolution target.
- if (nonAbstractMethods.size() == 1) {
- return singleResultHelper(initialResolutionHolder, nonAbstractMethods.get(0));
- }
- return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
+ return nonAbstractMethods;
}
private static SingleResolutionResult<?> singleResultHelper(
@@ -493,5 +710,17 @@
entry.getKey(),
entry.getValue());
}
+
+ private static Entry<DexClass, DexEncodedMethod> firstNonNullEntry(
+ Map<DexClass, DexEncodedMethod> candidates) {
+ for (Entry<DexClass, DexEncodedMethod> entry : candidates.entrySet()) {
+ DexEncodedMethod method = entry.getValue();
+ if (method != null) {
+ return entry;
+ }
+ }
+ assert false : "Should not be called on a collection without any non-null candidates";
+ return null;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 65ff279..a2dd885 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
@@ -83,6 +84,14 @@
return false;
}
+ public boolean isMultiMethodResolutionResult() {
+ return false;
+ }
+
+ public final void forEachMethodResolutionResult(Consumer<MethodResolutionResult> resultConsumer) {
+ visitMethodResolutionResults(resultConsumer, resultConsumer, resultConsumer, resultConsumer);
+ }
+
/** Returns non-null if isFailedResolution() is true, otherwise null. */
public FailedResolutionResult asFailedResolution() {
return null;
@@ -162,14 +171,23 @@
public abstract LookupTarget lookupVirtualDispatchTarget(
LambdaDescriptor lambdaInstance,
AppInfoWithClassHierarchy appInfo,
+ Consumer<DexType> typeCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
public abstract void visitMethodResolutionResults(
- Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+ Consumer<? super SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ programOrClasspathConsumer,
Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
Consumer<? super FailedResolutionResult> failedResolutionConsumer);
+ public void visitMethodResolutionResults(
+ Consumer<? super MethodResolutionResult> resultConsumer,
+ Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
+ visitMethodResolutionResults(
+ resultConsumer, resultConsumer, resultConsumer, failedResolutionConsumer);
+ }
+
public boolean hasProgramResult() {
return false;
}
@@ -502,7 +520,11 @@
incompleteness.checkClass(subClass);
LookupMethodTarget lookupTarget =
lookupVirtualDispatchTarget(
- subClass, appInfo, resolvedHolder.type, resultBuilder::addMethodCausingFailure);
+ subClass,
+ appInfo,
+ resolvedHolder.type,
+ resultBuilder::addTypeCausingFailure,
+ resultBuilder::addMethodCausingFailure);
if (lookupTarget != null) {
incompleteness.checkDexClassAndMethod(lookupTarget);
addVirtualDispatchTarget(lookupTarget, resolvedHolder.isInterface(), resultBuilder);
@@ -513,7 +535,10 @@
|| resolvedHolder.type == appInfo.dexItemFactory().objectType;
LookupTarget target =
lookupVirtualDispatchTarget(
- lambda, appInfo, resultBuilder::addMethodCausingFailure);
+ lambda,
+ appInfo,
+ resultBuilder::addTypeCausingFailure,
+ resultBuilder::addMethodCausingFailure);
if (target != null) {
if (target.isLambdaTarget()) {
resultBuilder.addLambdaTarget(target.asLambdaTarget());
@@ -639,20 +664,22 @@
InstantiatedObject instance, AppInfoWithClassHierarchy appInfo) {
return instance.isClass()
? lookupVirtualDispatchTarget(instance.asClass(), appInfo)
- : lookupVirtualDispatchTarget(instance.asLambda(), appInfo, emptyConsumer());
+ : lookupVirtualDispatchTarget(
+ instance.asLambda(), appInfo, emptyConsumer(), emptyConsumer());
}
@Override
public LookupMethodTarget lookupVirtualDispatchTarget(
DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
return lookupVirtualDispatchTarget(
- dynamicInstance, appInfo, initialResolutionHolder.type, emptyConsumer());
+ dynamicInstance, appInfo, initialResolutionHolder.type, emptyConsumer(), emptyConsumer());
}
@Override
public LookupTarget lookupVirtualDispatchTarget(
LambdaDescriptor lambdaInstance,
AppInfoWithClassHierarchy appInfo,
+ Consumer<DexType> typeCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
DexMethod methodReference = lambdaInstance.implHandle.asMethod();
@@ -665,13 +692,14 @@
return new LookupLambdaTarget(lambdaInstance, method);
}
return lookupMaximallySpecificDispatchTarget(
- lambdaInstance, appInfo, methodCausingFailureConsumer);
+ lambdaInstance, appInfo, typeCausingFailureConsumer, methodCausingFailureConsumer);
}
private LookupMethodTarget lookupVirtualDispatchTarget(
DexClass dynamicInstance,
AppInfoWithClassHierarchy appInfo,
DexType resolutionHolder,
+ Consumer<DexType> typeCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
: dynamicInstance.type + " is not a subtype of " + resolutionHolder;
@@ -714,12 +742,13 @@
return null;
}
return lookupMaximallySpecificDispatchTarget(
- dynamicInstance, appInfo, methodCausingFailureConsumer);
+ dynamicInstance, appInfo, typeCausingFailureConsumer, methodCausingFailureConsumer);
}
private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
DexClass dynamicInstance,
AppInfoWithClassHierarchy appInfo,
+ Consumer<DexType> typeCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
MethodResolutionResult maximallySpecificResolutionResult =
appInfo.resolveMaximallySpecificTarget(dynamicInstance, resolvedMethod.getReference());
@@ -729,7 +758,7 @@
if (maximallySpecificResolutionResult.isFailedResolution()) {
maximallySpecificResolutionResult
.asFailedResolution()
- .forEachFailureDependency(methodCausingFailureConsumer);
+ .forEachFailureDependency(typeCausingFailureConsumer, methodCausingFailureConsumer);
return null;
}
assert maximallySpecificResolutionResult.isArrayCloneMethodResult();
@@ -739,6 +768,7 @@
private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
LambdaDescriptor lambdaDescriptor,
AppInfoWithClassHierarchy appInfo,
+ Consumer<DexType> typeCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
MethodResolutionResult maximallySpecificResolutionResult =
appInfo.resolveMaximallySpecificTarget(lambdaDescriptor, resolvedMethod.getReference());
@@ -748,7 +778,7 @@
if (maximallySpecificResolutionResult.isFailedResolution()) {
maximallySpecificResolutionResult
.asFailedResolution()
- .forEachFailureDependency(methodCausingFailureConsumer);
+ .forEachFailureDependency(typeCausingFailureConsumer, methodCausingFailureConsumer);
return null;
}
assert maximallySpecificResolutionResult.isArrayCloneMethodResult();
@@ -833,7 +863,8 @@
@Override
public void visitMethodResolutionResults(
- Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+ Consumer<? super SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ programOrClasspathConsumer,
Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
@@ -872,7 +903,8 @@
@Override
public void visitMethodResolutionResults(
- Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+ Consumer<? super SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ programOrClasspathConsumer,
Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
@@ -906,7 +938,8 @@
@Override
public void visitMethodResolutionResults(
- Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+ Consumer<? super SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ programOrClasspathConsumer,
Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
@@ -974,6 +1007,7 @@
public LookupTarget lookupVirtualDispatchTarget(
LambdaDescriptor lambdaInstance,
AppInfoWithClassHierarchy appInfo,
+ Consumer<DexType> typeCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
return null;
}
@@ -1007,7 +1041,8 @@
@Override
public void visitMethodResolutionResults(
- Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+ Consumer<? super SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ programOrClasspathConsumer,
Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
@@ -1023,6 +1058,12 @@
/** Base class for all types of failed resolutions. */
public abstract static class FailedResolutionResult extends EmptyResult {
+ protected final Collection<DexType> typesCausingError;
+
+ private FailedResolutionResult(Collection<DexType> typesCausingError) {
+ this.typesCausingError = typesCausingError;
+ }
+
@Override
public boolean isFailedResolution() {
return true;
@@ -1034,8 +1075,11 @@
}
public void forEachFailureDependency(
+ Consumer<DexType> typesCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
- // Default failure has no dependencies.
+ if (typesCausingError != null) {
+ typesCausingError.forEach(typesCausingFailureConsumer);
+ }
}
@Override
@@ -1061,7 +1105,8 @@
@Override
public void visitMethodResolutionResults(
- Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+ Consumer<? super SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ programOrClasspathConsumer,
Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
@@ -1073,7 +1118,7 @@
static final ClassNotFoundResult INSTANCE = new ClassNotFoundResult();
private ClassNotFoundResult() {
- // Intentionally left empty.
+ super(null);
}
@Override
@@ -1087,12 +1132,15 @@
private final Collection<DexEncodedMethod> methodsCausingError;
private FailedResolutionWithCausingMethods(Collection<DexEncodedMethod> methodsCausingError) {
+ super(ListUtils.map(methodsCausingError, DexEncodedMember::getHolderType));
this.methodsCausingError = methodsCausingError;
}
@Override
public void forEachFailureDependency(
+ Consumer<DexType> typesCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+ super.forEachFailureDependency(typesCausingFailureConsumer, methodCausingFailureConsumer);
this.methodsCausingError.forEach(methodCausingFailureConsumer);
}
@@ -1124,7 +1172,11 @@
public static class NoSuchMethodResult extends FailedResolutionResult {
- static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+ static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult(null);
+
+ public NoSuchMethodResult(Collection<DexType> typesCausingError) {
+ super(typesCausingError);
+ }
@Override
public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
@@ -1155,9 +1207,21 @@
}
BooleanBox seenNoAccess = new BooleanBox(false);
forEachFailureDependency(
+ type -> {
+ appInfo
+ .contextIndependentDefinitionForWithResolutionResult(type)
+ .forEachClassResolutionResult(
+ clazz -> {
+ AccessControl.isClassAccessible(
+ clazz,
+ context,
+ appInfo.getClassToFeatureSplitMap(),
+ appInfo.getSyntheticItems());
+ });
+ },
method -> {
- DexClassAndMethod classAndMethod =
- DexClassAndMethod.create(appInfo.definitionFor(method.getHolderType()), method);
+ DexClass holder = appInfo.definitionFor(method.getHolderType());
+ DexClassAndMethod classAndMethod = DexClassAndMethod.create(holder, method);
seenNoAccess.or(
AccessControl.isMemberAccessible(
classAndMethod, initialResolutionHolder, context, appInfo)
@@ -1184,6 +1248,9 @@
private boolean verifyInvalidSymbolicReference() {
BooleanBox invalidSymbolicReference = new BooleanBox(true);
forEachFailureDependency(
+ type -> {
+ // Intentionally empty
+ },
method -> {
invalidSymbolicReference.and(
method.getHolderType() != initialResolutionHolder.getType());
@@ -1197,14 +1264,18 @@
extends MethodResolutionResult {
protected final T programOrClasspathResult;
+ protected final List<SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ otherProgramOrClasspathResults;
protected final List<SingleLibraryResolutionResult> libraryResolutionResults;
protected final List<FailedResolutionResult> failedResolutionResults;
- public MultipleMethodResolutionResult(
+ protected MultipleMethodResolutionResult(
T programOrClasspathResult,
+ List<SingleResolutionResult<? extends ProgramOrClasspathClass>> programOrClasspathResults,
List<SingleLibraryResolutionResult> libraryResolutionResults,
List<FailedResolutionResult> failedResolutionResults) {
this.programOrClasspathResult = programOrClasspathResult;
+ this.otherProgramOrClasspathResults = programOrClasspathResults;
this.libraryResolutionResults = libraryResolutionResults;
this.failedResolutionResults = failedResolutionResults;
}
@@ -1284,22 +1355,32 @@
public LookupTarget lookupVirtualDispatchTarget(
LambdaDescriptor lambdaInstance,
AppInfoWithClassHierarchy appInfo,
+ Consumer<DexType> typeCausingFailureConsumer,
Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
}
@Override
public void visitMethodResolutionResults(
- Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+ Consumer<? super SingleResolutionResult<? extends ProgramOrClasspathClass>>
+ programOrClasspathConsumer,
Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
if (programOrClasspathResult != null) {
programOrClasspathConsumer.accept(programOrClasspathResult);
}
+ if (otherProgramOrClasspathResults != null) {
+ otherProgramOrClasspathResults.forEach(programOrClasspathConsumer);
+ }
libraryResolutionResults.forEach(libraryResultConsumer);
failedResolutionResults.forEach(failedResolutionConsumer);
}
+
+ @Override
+ public boolean isMultiMethodResolutionResult() {
+ return true;
+ }
}
public static class MultipleProgramWithLibraryResolutionResult
@@ -1309,7 +1390,11 @@
SingleProgramResolutionResult programOrClasspathResult,
List<SingleLibraryResolutionResult> libraryResolutionResults,
List<FailedResolutionResult> failedOrUnknownResolutionResults) {
- super(programOrClasspathResult, libraryResolutionResults, failedOrUnknownResolutionResults);
+ super(
+ programOrClasspathResult,
+ null,
+ libraryResolutionResults,
+ failedOrUnknownResolutionResults);
}
}
@@ -1320,7 +1405,11 @@
SingleClasspathResolutionResult programOrClasspathResult,
List<SingleLibraryResolutionResult> libraryResolutionResults,
List<FailedResolutionResult> failedOrUnknownResolutionResults) {
- super(programOrClasspathResult, libraryResolutionResults, failedOrUnknownResolutionResults);
+ super(
+ programOrClasspathResult,
+ null,
+ libraryResolutionResults,
+ failedOrUnknownResolutionResults);
}
}
@@ -1330,7 +1419,18 @@
public MultipleLibraryMethodResolutionResult(
List<SingleLibraryResolutionResult> libraryResolutionResults,
List<FailedResolutionResult> failedOrUnknownResolutionResults) {
- super(null, libraryResolutionResults, failedOrUnknownResolutionResults);
+ super(null, null, libraryResolutionResults, failedOrUnknownResolutionResults);
+ }
+ }
+
+ public static class MultipleMaximallySpecificResolutionResult
+ extends MultipleMethodResolutionResult<DexProgramClass, SingleProgramResolutionResult> {
+
+ public MultipleMaximallySpecificResolutionResult(
+ List<SingleResolutionResult<? extends ProgramOrClasspathClass>> programOrClasspathResult,
+ List<SingleLibraryResolutionResult> libraryResolutionResults,
+ List<FailedResolutionResult> failedResolutionResults) {
+ super(null, programOrClasspathResult, libraryResolutionResults, failedResolutionResults);
}
}
@@ -1342,6 +1442,7 @@
private MethodResolutionResult possiblySingleResult = null;
private List<MethodResolutionResult> allResults = null;
+ private boolean allowMultipleProgramResults = false;
private Builder() {}
@@ -1357,26 +1458,29 @@
allResults.add(result);
}
+ public Builder allowMultipleProgramResults() {
+ allowMultipleProgramResults = true;
+ return this;
+ }
+
public MethodResolutionResult buildOrIfEmpty(MethodResolutionResult emptyResult) {
if (possiblySingleResult == null) {
return emptyResult;
} else if (allResults == null) {
return possiblySingleResult;
}
- Box<SingleResolutionResult<?>> singleResult = new Box<>();
+ List<SingleResolutionResult<? extends ProgramOrClasspathClass>> programOrClasspathResults =
+ new ArrayList<>();
List<SingleLibraryResolutionResult> libraryResults = new ArrayList<>();
List<FailedResolutionResult> failedResults = new ArrayList<>();
allResults.forEach(
otherResult -> {
otherResult.visitMethodResolutionResults(
otherProgramOrClasspathResult -> {
- if (singleResult.isSet()) {
+ if (!programOrClasspathResults.isEmpty() && !allowMultipleProgramResults) {
assert false : "Unexpected multiple results between program and classpath";
- if (singleResult.get().hasProgramResult()) {
- return;
- }
}
- singleResult.set(otherProgramOrClasspathResult);
+ programOrClasspathResults.add(otherProgramOrClasspathResult);
},
newLibraryResult -> {
if (!Iterables.any(
@@ -1396,7 +1500,7 @@
}
});
});
- if (!singleResult.isSet()) {
+ if (programOrClasspathResults.isEmpty()) {
if (libraryResults.size() == 1 && failedResults.isEmpty()) {
return libraryResults.get(0);
} else if (libraryResults.isEmpty() && failedResults.size() == 1) {
@@ -1404,17 +1508,27 @@
} else {
return new MultipleLibraryMethodResolutionResult(libraryResults, failedResults);
}
- } else if (libraryResults.isEmpty() && failedResults.isEmpty()) {
- return singleResult.get();
- } else if (singleResult.get().hasProgramResult()) {
- return new MultipleProgramWithLibraryResolutionResult(
- singleResult.get().asSingleProgramResolutionResult(), libraryResults, failedResults);
+ } else if (libraryResults.isEmpty()
+ && failedResults.isEmpty()
+ && programOrClasspathResults.size() == 1) {
+ return programOrClasspathResults.get(0);
+ } else if (programOrClasspathResults.size() == 1) {
+ SingleResolutionResult<?> singleResult = programOrClasspathResults.get(0);
+ if (singleResult.hasProgramResult()) {
+ return new MultipleProgramWithLibraryResolutionResult(
+ singleResult.asSingleProgramResolutionResult(), libraryResults, failedResults);
+ } else {
+ SingleClasspathResolutionResult classpathResult =
+ singleResult.asSingleClasspathResolutionResult();
+ assert classpathResult != null;
+ return new MultipleClasspathWithLibraryResolutionResult(
+ classpathResult, libraryResults, failedResults);
+ }
} else {
- SingleClasspathResolutionResult classpathResult =
- singleResult.get().asSingleClasspathResolutionResult();
- assert classpathResult != null;
- return new MultipleClasspathWithLibraryResolutionResult(
- classpathResult, libraryResults, failedResults);
+ // This must be a maximally specific result since we have multiple program or classpath
+ // values.
+ return new MultipleMaximallySpecificResolutionResult(
+ programOrClasspathResults, libraryResults, failedResults);
}
}
}
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 0806685..2d360c0 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
@@ -32,6 +32,7 @@
import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
@@ -696,7 +697,8 @@
if (resolutionResult.isFailedResolution()) {
resolutionResult
.asFailedResolution()
- .forEachFailureDependency(target -> staticTarget.and(target.isStatic()));
+ .forEachFailureDependency(
+ ConsumerUtils.emptyConsumer(), target -> staticTarget.and(target.isStatic()));
} else if (resolutionResult.isSuccessfulMemberResolutionResult()) {
staticTarget.set(
resolutionResult.asSuccessfulMemberResolutionResult().getResolvedMember().isStatic());
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 242b6e1..3c53944 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -403,7 +404,9 @@
assert resolutionResult.isFailedResolution();
List<DexEncodedMethod> targets = new ArrayList<>();
- resolutionResult.asFailedResolution().forEachFailureDependency(targets::add);
+ resolutionResult
+ .asFailedResolution()
+ .forEachFailureDependency(ConsumerUtils.emptyConsumer(), targets::add);
if (!targets.isEmpty()) {
DexString newName = renaming.get(targets.get(0).getReference());
assert targets.stream().allMatch(target -> renaming.get(target.getReference()) == newName);
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index ddd882c..d4b20fe 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -137,6 +137,9 @@
resolution
.asFailedResolution()
.forEachFailureDependency(
+ type -> {
+ assert type.descriptor == lookupDescriptor(type);
+ },
failureDependence -> {
assert lookupName(method) == lookupName(failureDependence.getReference());
});
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index a2c0d26..05ad88c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3354,6 +3354,7 @@
KeepReason reason) {
failedMethodResolutionTargets.add(symbolicMethod);
failedResolution.forEachFailureDependency(
+ type -> recordTypeReference(type, context),
method -> {
DexProgramClass clazz = getProgramClassOrNull(method.getHolderType(), context);
if (clazz != null) {
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 42dc619..a17a299 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -363,9 +363,9 @@
resolutionResult
.asFailedResolution()
.forEachFailureDependency(
- methodCausingFailure -> {
- handleRewrittenMethodReference(method, methodCausingFailure);
- });
+ type -> addType(type, referencedFrom),
+ methodCausingFailure ->
+ handleRewrittenMethodReference(method, methodCausingFailure));
return;
}
handleRewrittenMethodReference(method, resolutionResult.getResolutionPair());
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificDifferentParentHierarchyTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificDifferentParentHierarchyTest.java
index 856d3fe..d609491 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificDifferentParentHierarchyTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificDifferentParentHierarchyTest.java
@@ -4,22 +4,27 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,12 +79,23 @@
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isMultiMethodResolutionResult());
+ Set<String> methodResults = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ assertTrue(result.isSingleResolution());
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
});
+ assertEquals(
+ ImmutableSet.of(
+ "Library: void " + typeName(I.class) + ".foo()",
+ "Program: void " + typeName(J.class) + ".foo()"),
+ methodResults);
}
@Test
@@ -96,8 +112,12 @@
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
- runTest(testForD8(parameters.getBackend()))
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ testForD8(parameters.getBackend())
+ .apply(this::setupTestBuilder)
+ .compile()
+ .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/230289235): Extend to support multiple definition results.
.assertFailureWithErrorThatThrowsIf(
!parameters.canUseDefaultAndStaticInterfaceMethods(),
IncompatibleClassChangeError.class)
@@ -107,23 +127,24 @@
@Test
public void testR8() throws Exception {
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
+ // TODO(b/230289235): Extend to support multiple definition results.
+ R8FullTestBuilder testBuilder = testForR8(parameters.getBackend()).addKeepMainRule(Main.class);
+ testBuilder
+ .apply(this::setupTestBuilder)
+ .compile()
+ .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+ .run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
}
- private TestRunResult<?> runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
- throws Exception {
- return testBuilder
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ testBuilder
.addProgramClasses(Main.class)
.addProgramClassFileData(getJProgram())
.addDefaultRuntimeLibrary(parameters)
.addLibraryFiles(libraryClasses)
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(options -> options.loadAllClassDefinitions = true)
- .compile()
- .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
- .run(parameters.getRuntime(), Main.class);
+ .addOptionsModification(options -> options.loadAllClassDefinitions = true);
}
private byte[] getJProgram() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultipleOnCompleteTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultipleOnCompleteTest.java
new file mode 100644
index 0000000..0ba0078
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultipleOnCompleteTest.java
@@ -0,0 +1,177 @@
+// 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.resolution.duplicatedefinitions;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+/**
+ * This is testing resolving Main.f for:
+ *
+ * <pre>
+ * I: I_L { f }
+ * J: J_L extends I { f }, J_P extends I { f }
+ * K: K_P extends J { f }
+ * L: L_L { f }
+ * M: M_L extends L { }, M_P extends L { }
+ * class Main implements I,J,K,M
+ * </pre>
+ */
+public class MaximallySpecificMultipleOnCompleteTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private Path libraryClasses;
+
+ @Before
+ public void setup() throws Exception {
+ libraryClasses = temp.newFile("lib.jar").toPath();
+ ZipBuilder.builder(libraryClasses)
+ .addFilesRelative(
+ ToolHelper.getClassPathForTests(),
+ ToolHelper.getClassFileForTestClass(J.class),
+ ToolHelper.getClassFileForTestClass(I.class),
+ ToolHelper.getClassFileForTestClass(L.class),
+ ToolHelper.getClassFileForTestClass(M.class))
+ .build();
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AndroidApp.Builder builder = AndroidApp.builder();
+ builder
+ .addProgramFiles(
+ ToolHelper.getClassFileForTestClass(K.class),
+ ToolHelper.getClassFileForTestClass(M.class))
+ .addClassProgramData(ImmutableList.of(getJOnProgram(), getMainWithAllImplements()));
+ builder.addLibraryFiles(parameters.getDefaultRuntimeLibrary(), libraryClasses);
+ AppView<AppInfoWithClassHierarchy> appView =
+ computeAppViewWithClassHierarchy(
+ builder.build(), null, options -> options.loadAllClassDefinitions = true);
+ AppInfoWithClassHierarchy appInfo = appView.appInfo();
+ DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isIncompatibleClassChangeErrorResult());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addRunClasspathFiles(libraryClasses)
+ .addProgramClasses(K.class, M.class)
+ .addProgramClassFileData(getJOnProgram(), getMainWithAllImplements())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ runTest(testForD8(parameters.getBackend()));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class));
+ }
+
+ private void runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addProgramClasses(K.class, M.class)
+ .addProgramClassFileData(getJOnProgram(), getMainWithAllImplements())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryFiles(libraryClasses)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.loadAllClassDefinitions = true)
+ .compile()
+ .addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+
+ private byte[] getJOnProgram() throws Exception {
+ return transformer(JProgram.class).setClassDescriptor(descriptor(J.class)).transform();
+ }
+
+ private byte[] getMainWithAllImplements() throws Exception {
+ return transformer(Main.class).setImplements(I.class, J.class, K.class, M.class).transform();
+ }
+
+ public interface I {
+ default void foo() {
+ System.out.println("I::foo");
+ }
+ }
+
+ /* Present on both library and program */
+ public interface JProgram extends I {
+ @Override
+ default void foo() {
+ System.out.println("J_Program::foo");
+ ;
+ }
+ }
+
+ public interface J extends I {
+ @Override
+ default void foo() {
+ System.out.println("J_Library::foo");
+ ;
+ }
+ }
+
+ public interface K extends J {
+
+ @Override
+ default void foo() {
+ System.out.println("J::foo");
+ }
+ }
+
+ public interface L {
+
+ default void foo() {
+ System.out.println("L::foo");
+ }
+ }
+
+ public interface M extends L {}
+
+ public static class Main implements I, J, K /*, M */ {
+
+ public static void main(String[] args) {
+ new Main().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsICCETest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsICCETest.java
index 2e57f9f..2b291f6 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsICCETest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsICCETest.java
@@ -4,21 +4,27 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,12 +79,25 @@
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isMultiMethodResolutionResult());
+ Set<String> methodResults = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ assertTrue(result.isSingleResolution());
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
});
+ assertEquals(
+ ImmutableSet.of(
+ "Library: void " + typeName(I.class) + ".foo()",
+ "Program: void " + typeName(I.class) + ".foo()",
+ "Library: void " + typeName(J.class) + ".foo()",
+ "Program: void " + typeName(J.class) + ".foo()"),
+ methodResults);
}
@Test
@@ -95,20 +114,19 @@
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
- runTest(testForD8(parameters.getBackend()), IncompatibleClassChangeError.class);
+ runTest(testForD8(parameters.getBackend()))
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
}
@Test
public void testR8() throws Exception {
- runTest(
- testForR8(parameters.getBackend()).addKeepMainRule(Main.class),
- IncompatibleClassChangeError.class);
+ runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
}
- private void runTest(
- TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder, Class<? extends Throwable> errorClass)
+ private TestRunResult<?> runTest(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
throws Exception {
- testBuilder
+ return testBuilder
.addProgramClasses(I.class, J.class)
.addProgramClassFileData(getMainWithInterfacesIAndJ())
.addDefaultRuntimeLibrary(parameters)
@@ -117,8 +135,7 @@
.addOptionsModification(options -> options.loadAllClassDefinitions = true)
.compile()
.addBootClasspathFiles(buildOnDexRuntime(parameters, libraryClasses))
- .run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatThrows(errorClass);
+ .run(parameters.getRuntime(), Main.class);
}
private byte[] getMainWithInterfacesIAndJ() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsSuccessTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsSuccessTest.java
index c743ac4..a70390c 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsSuccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsSuccessTest.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -13,13 +14,17 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,7 +45,7 @@
public class MaximallySpecificMultiplePathsSuccessTest extends TestBase {
private static final String EXPECTED = "I::foo";
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend resolution to support multiple definition results.
private static final String D8_R8_RESULT = "J::foo";
@Parameter() public TestParameters parameters;
@@ -77,12 +82,23 @@
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isMultiMethodResolutionResult());
+ Set<String> methodResults = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ assertTrue(result.isSingleResolution());
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
});
+ assertEquals(
+ ImmutableSet.of(
+ "Library: void " + typeName(I.class) + ".foo()",
+ "Program: void " + typeName(J.class) + ".foo()"),
+ methodResults);
}
@Test
@@ -105,14 +121,14 @@
.assertSuccessWithOutputLinesIf(isDalvik, D8_R8_RESULT)
.assertSuccessWithOutputLinesIf(
!isDalvik && !parameters.canUseDefaultAndStaticInterfaceMethods(), D8_R8_RESULT)
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend resolution to support multiple definition results.
.assertSuccessWithOutputLinesIf(
parameters.canUseDefaultAndStaticInterfaceMethods(), EXPECTED);
}
@Test
public void testR8() throws Exception {
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend resolution to support multiple definition results.
runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
.assertFailureWithErrorThatThrowsIf(
parameters.canUseDefaultAndStaticInterfaceMethods(), NoSuchMethodError.class)
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsThroughClassTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsThroughClassTest.java
index a14d33a..65487b4 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsThroughClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificMultiplePathsThroughClassTest.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -12,13 +13,17 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,12 +78,24 @@
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isMultiMethodResolutionResult());
+ Set<String> methodResults = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ assertTrue(result.isSingleResolution());
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
});
+ assertEquals(
+ ImmutableSet.of(
+ "Library: void " + typeName(I.class) + ".foo()",
+ "Program: void " + typeName(I.class) + ".foo()",
+ "Program: void " + typeName(J.class) + ".foo()"),
+ methodResults);
}
@Test
@@ -100,6 +117,7 @@
@Test
public void testR8() throws Exception {
+ // TODO(b/230289235): Extend to support multiple definition results.
runTest(
testForR8(parameters.getBackend()).addKeepMainRule(Main.class),
IncompatibleClassChangeError.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingAfterJoinTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingAfterJoinTest.java
index 3e3b85b..eb02ca8 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingAfterJoinTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingAfterJoinTest.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -12,13 +13,17 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,12 +80,19 @@
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isSingleResolution());
+ Set<String> methodResults = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ assertTrue(result.isSingleResolution());
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
});
+ assertEquals(ImmutableSet.of("Library: void " + typeName(J.class) + ".foo()"), methodResults);
}
@Test
@@ -125,7 +137,7 @@
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLinesIf(
parameters.canUseDefaultAndStaticInterfaceMethods(), EXPECTED)
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend to support multiple definition results.
.assertFailureWithErrorThatThrowsIf(
!parameters.canUseDefaultAndStaticInterfaceMethods(),
errorIfNotSupportingDefaultMethods);
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingSubTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingSubTest.java
index 630c284..3381b31 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingSubTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleDominatingSubTest.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -12,14 +13,18 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,7 +40,7 @@
* I: I_L { f }
* J: J_L extends I { f }, J_P extends I { f }
* K: K_P extends J { f }
- * class Main implements I,K
+ * class Main implements I,J,K
* </pre>
*/
public class MaximallySpecificSingleDominatingSubTest extends TestBase {
@@ -77,12 +82,19 @@
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isSingleResolution());
+ Set<String> methodResults = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ assertTrue(result.isSingleResolution());
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
});
+ assertEquals(ImmutableSet.of("Program: void " + typeName(K.class) + ".foo()"), methodResults);
}
@Test
@@ -156,7 +168,7 @@
}
}
- public static class Main implements I, K {
+ public static class Main implements I, J, K {
public static void main(String[] args) {
new Main().foo();
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleLibraryPartialTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleLibraryPartialTest.java
index c840cc2..bc40110 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleLibraryPartialTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleLibraryPartialTest.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -13,13 +15,20 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,13 +79,34 @@
computeAppViewWithClassHierarchy(
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
- DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ DexItemFactory factory = appInfo.dexItemFactory();
+ DexMethod method = buildNullaryVoidMethod(Main.class, "foo", factory);
+ DexClass mainClass = appInfo.definitionFor(factory.createType(descriptor(Main.class)));
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isMultiMethodResolutionResult());
+ Set<String> methodResults = new HashSet<>();
+ Set<DexType> failingTypesResult = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ if (result.isSingleResolution()) {
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
+ } else {
+ assertTrue(result.isNoSuchMethodErrorResult(mainClass, appInfo));
+ methodResults.add(typeName(NoSuchMethodError.class));
+ result
+ .asFailedResolution()
+ .forEachFailureDependency(failingTypesResult::add, failingMethod -> fail());
+ }
});
+ assertEquals(
+ ImmutableSet.of(
+ "Library: void " + typeName(I.class) + ".foo()", typeName(NoSuchMethodError.class)),
+ methodResults);
+ assertEquals(ImmutableSet.of(factory.createType(descriptor(I.class))), failingTypesResult);
}
@Test
@@ -93,7 +123,7 @@
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend resolution to support multiple definition results.
runTest(testForD8(parameters.getBackend()))
.assertFailureWithErrorThatThrowsIf(
!parameters.canUseDefaultAndStaticInterfaceMethods(),
@@ -106,7 +136,7 @@
@Test
public void testR8() throws Exception {
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend resolution to support multiple definition results.
runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleProgramPartialTest.java b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleProgramPartialTest.java
index de9876c..ad45b89 100644
--- a/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleProgramPartialTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/duplicatedefinitions/MaximallySpecificSingleProgramPartialTest.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.resolution.duplicatedefinitions;
-import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -13,13 +15,20 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -68,13 +77,34 @@
computeAppViewWithClassHierarchy(
builder.build(), null, options -> options.loadAllClassDefinitions = true);
AppInfoWithClassHierarchy appInfo = appView.appInfo();
- DexMethod method = buildNullaryVoidMethod(Main.class, "foo", appInfo.dexItemFactory());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
- assertThrows(
- Unreachable.class,
- () -> {
- appInfo.unsafeResolveMethodDueToDexFormat(method);
+ DexItemFactory factory = appInfo.dexItemFactory();
+ DexMethod method = buildNullaryVoidMethod(Main.class, "foo", factory);
+ DexClass mainClass = appInfo.definitionFor(factory.createType(descriptor(Main.class)));
+ MethodResolutionResult methodResolutionResult =
+ appInfo.unsafeResolveMethodDueToDexFormat(method);
+ assertTrue(methodResolutionResult.isMultiMethodResolutionResult());
+ Set<String> methodResults = new HashSet<>();
+ Set<DexType> failingTypesResult = new HashSet<>();
+ methodResolutionResult.forEachMethodResolutionResult(
+ result -> {
+ if (result.isSingleResolution()) {
+ SingleResolutionResult<?> resolution = result.asSingleResolution();
+ methodResults.add(
+ (resolution.getResolvedHolder().isProgramClass() ? "Program: " : "Library: ")
+ + resolution.getResolvedMethod().getReference().toString());
+ } else {
+ assertTrue(result.isNoSuchMethodErrorResult(mainClass, appInfo));
+ methodResults.add(typeName(NoSuchMethodError.class));
+ result
+ .asFailedResolution()
+ .forEachFailureDependency(failingTypesResult::add, failingMethod -> fail());
+ }
});
+ assertEquals(
+ ImmutableSet.of(
+ "Program: void " + typeName(I.class) + ".foo()", typeName(NoSuchMethodError.class)),
+ methodResults);
+ assertEquals(ImmutableSet.of(factory.createType(descriptor(I.class))), failingTypesResult);
}
@Test
@@ -90,7 +120,7 @@
@Test
public void testD8() throws Exception {
assumeTrue(parameters.isDexRuntime());
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend to support multiple definition results.
runTest(testForD8(parameters.getBackend()))
.assertFailureWithErrorThatThrowsIf(
parameters.canUseDefaultAndStaticInterfaceMethods(), NoSuchMethodError.class)
@@ -100,7 +130,7 @@
@Test
public void testR8() throws Exception {
- // TODO(b/214382176): Extend resolution to support multiple definition results.
+ // TODO(b/230289235): Extend to support multiple definition results.
runTest(testForR8(parameters.getBackend()).addKeepMainRule(Main.class))
.assertFailureWithErrorThatThrowsIf(
parameters.canUseDefaultAndStaticInterfaceMethods(), NoSuchMethodError.class)
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 31e9e9e..7e76e3e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -5,6 +5,7 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestAppViewBuilder;
@@ -57,7 +58,8 @@
Set<String> holders = new HashSet<>();
resolutionResult
.asFailedResolution()
- .forEachFailureDependency(target -> holders.add(target.getHolderType().toSourceString()));
+ .forEachFailureDependency(
+ type -> fail(), target -> holders.add(target.getHolderType().toSourceString()));
assertEquals(ImmutableSet.of(L.class.getTypeName(), R.class.getTypeName()), holders);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index d5aefd7..b3b1f8e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -6,6 +6,7 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestAppViewBuilder;
@@ -81,7 +82,8 @@
Set<String> holders = new HashSet<>();
resolutionResult
.asFailedResolution()
- .forEachFailureDependency(m -> holders.add(m.getHolderType().toSourceString()));
+ .forEachFailureDependency(
+ type -> fail(), m -> holders.add(m.getHolderType().toSourceString()));
assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java
index d0c2f5c..8494f39 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java
@@ -71,6 +71,7 @@
.setMinApi(parameters.getApiLevel())
.addDontWarn(A.class, B.class)
.addKeepMainRule(Main.class)
+ .addOptionsModification(options -> options.loadAllClassDefinitions = true)
.noMinification()
.allowUnusedDontWarnPatterns();
byte[] libraryA = getAFromClassTestParam(this.aInLibrary);
@@ -89,10 +90,10 @@
R8TestRunResult runResult = compileResult.run(parameters.getRuntime(), Main.class);
if (isExpectedToFailWithNoClassDefError()) {
runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
- } else if (isExpectedToFailWithNoSuchMethodError()) {
- runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
} else if (isExpectedToFailWithICCE()) {
runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ } else if (isExpectedToFailWithNoSuchMethodError()) {
+ runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
} else if (isDefinedOnAProgram() || (isDefinedOnALibrary() && !isAInProgram())) {
runResult.assertSuccessWithOutputLines("A::foo");
} else {
@@ -144,7 +145,7 @@
return true;
}
if (notDefinedInProgram) {
- // TODO(b/214382176): Currently, a program definition will shadow the library definition and
+ // TODO(b/230289235): Currently, a program definition will shadow the library definition and
// R8 will optimize the interfaces away.
if (isDefinedOnALibrary() && isDefinedOnBLibrary()) {
return isAInProgram() && isBInProgram();
@@ -154,6 +155,15 @@
assert isDefinedOnBLibrary();
return isBInProgram();
}
+ } else {
+ // if the library definition is overriding the program definition and there is no definition
+ // then we also fail.
+ if (isDefinedOnAProgram() && isAInLibrary() && !isDefinedOnALibrary()) {
+ return true;
+ }
+ if (isDefinedOnBProgram() && isBInLibrary() && !isDefinedOnBLibrary()) {
+ return true;
+ }
}
return false;
}