| // 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.graph; |
| |
| 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; |
| import com.android.tools.r8.graph.MethodResolutionResult.IncompatibleClassResult; |
| import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult; |
| 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; |
| |
| /** |
| * Implements resolution of a method descriptor against a type. |
| * |
| * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3"> |
| * Section 5.4.3.3 of the JVM Spec</a>. |
| */ |
| public class MethodResolution { |
| |
| 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 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, true); |
| } |
| |
| private ClassResolutionResult definitionFor(DexType type) { |
| return definitionFor.apply(type); |
| } |
| |
| /** |
| * This method will query the definition of the holder to decide on which resolution to use. If |
| * the holder is an interface, it delegates to {@link #resolveMethodOnInterface(DexClass, |
| * DexProto, DexString)}, otherwise {@link #resolveMethodOnClass(DexClass, DexProto, DexString)} |
| * is used. |
| * |
| * <p>This is to overcome the shortcoming of the DEX file format that does not allow to encode the |
| * kind of a method reference. |
| */ |
| public MethodResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) { |
| DexType holder = method.holder; |
| if (holder.isArrayType()) { |
| return resolveMethodOnArray(holder, method.getProto(), method.getName()); |
| } |
| MethodResolutionResult.Builder builder = MethodResolutionResult.builder(); |
| definitionFor(holder) |
| .forEachClassResolutionResult( |
| clazz -> { |
| builder.addResolutionResult( |
| clazz.isInterface() |
| ? resolveMethodOnInterface(clazz, method.getProto(), method.getName()) |
| : resolveMethodOnClass(clazz, method.getProto(), method.getName())); |
| }); |
| return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, holder); |
| } |
| |
| /** |
| * Implements resolution of a method descriptor against an array type. |
| * |
| * <p>See <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.7">Section |
| * 10.7 of the Java Language Specification</a>. All invokations will have target java.lang.Object |
| * except clone which has no target. |
| */ |
| private MethodResolutionResult resolveMethodOnArray( |
| DexType holder, DexProto methodProto, DexString methodName) { |
| assert holder.isArrayType(); |
| if (methodName == factory.cloneMethodName) { |
| return ArrayCloneMethodResult.INSTANCE; |
| } else { |
| return resolveMethodOnClass(factory.objectType, methodProto, methodName); |
| } |
| } |
| |
| /** |
| * Implements resolution of a method descriptor against a class type. |
| * |
| * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3"> |
| * Section 5.4.3.3 of the JVM Spec</a>. |
| * |
| * <p>The resolved method is not the method that will actually be invoked. Which methods gets |
| * invoked depends on the invoke instruction used. However, it is always safe to rewrite any |
| * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the |
| * resolved method is used as basis for dispatch. |
| */ |
| public MethodResolutionResult resolveMethodOnClass( |
| DexType holder, DexProto methodProto, DexString methodName) { |
| if (holder.isArrayType()) { |
| return resolveMethodOnArray(holder, methodProto, methodName); |
| } |
| MethodResolutionResult.Builder builder = MethodResolutionResult.builder(); |
| definitionFor(holder) |
| .forEachClassResolutionResult( |
| clazz -> { |
| // Step 1: If holder is an interface, resolution fails with an ICCE. |
| if (clazz.isInterface()) { |
| builder.addResolutionResult(IncompatibleClassResult.INSTANCE); |
| } else { |
| builder.addResolutionResult(resolveMethodOnClass(clazz, methodProto, methodName)); |
| } |
| }); |
| return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, holder); |
| } |
| |
| public MethodResolutionResult resolveMethodOnClass( |
| DexClass clazz, DexProto methodProto, DexString methodName) { |
| assert !clazz.isInterface(); |
| // Step 2: |
| MethodResolutionResult result = |
| resolveMethodOnClassStep2(clazz, methodProto, methodName, clazz); |
| if (result != null) { |
| return result; |
| } |
| // Finally Step 3: |
| return resolveMethodStep3(clazz, methodProto, methodName); |
| } |
| |
| /** |
| * Implements step 2 of method resolution on classes as per <a |
| * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section |
| * 5.4.3.3 of the JVM Spec</a>. |
| */ |
| private MethodResolutionResult resolveMethodOnClassStep2( |
| DexClass clazz, |
| DexProto methodProto, |
| DexString methodName, |
| DexClass initialResolutionHolder) { |
| // Pt. 1: Signature polymorphic method check. |
| // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9"> |
| // Section 2.9 of the JVM Spec</a>. |
| DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(methodName, factory); |
| if (result != null) { |
| return MethodResolutionResult.createSingleResolutionResult( |
| initialResolutionHolder, clazz, result); |
| } |
| // Pt 2: Find a method that matches the descriptor. |
| result = clazz.lookupMethod(methodProto, methodName); |
| if (result != null) { |
| // If the resolved method is private, then it can only be accessed if the symbolic reference |
| // that initiated the resolution was the type at which the method resolved on. If that is not |
| // the case, then the error is either an IllegalAccessError, or in the case where access is |
| // allowed because of nests, a NoSuchMethodError. Which error cannot be determined without |
| // knowing the calling context. |
| if (result.isPrivateMethod() && clazz != initialResolutionHolder) { |
| return new IllegalAccessOrNoSuchMethodResult(initialResolutionHolder, result); |
| } |
| return MethodResolutionResult.createSingleResolutionResult( |
| initialResolutionHolder, clazz, result); |
| } |
| // Pt 3: Apply step two to direct superclass of holder. |
| if (clazz.superType != null) { |
| MethodResolutionResult.Builder builder = MethodResolutionResult.builder(); |
| definitionFor(clazz.superType) |
| .forEachClassResolutionResult( |
| superClass -> { |
| // Guard against going back into the program for resolution. |
| if (escapeIfLibraryHasProgramSuperType |
| && clazz.isLibraryClass() |
| && !superClass.isLibraryClass()) { |
| return; |
| } |
| builder.addResolutionResult( |
| resolveMethodOnClassStep2( |
| superClass, methodProto, methodName, initialResolutionHolder)); |
| }); |
| return builder.buildOrIfEmpty(null, clazz.superType); |
| } |
| return null; |
| } |
| |
| /** |
| * Implements step 3 of <a |
| * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section |
| * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one |
| * implementation. |
| */ |
| private MethodResolutionResult resolveMethodStep3( |
| DexClass clazz, DexProto methodProto, DexString methodName) { |
| MaximallySpecificMethodsBuilder builder = |
| new MaximallySpecificMethodsBuilder(definitionFor, factory); |
| resolveMethodStep3Helper(methodProto, methodName, clazz, builder); |
| return builder.resolve(clazz); |
| } |
| |
| MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) { |
| return resolveMaximallySpecificTargetHelper(clazz, method).resolve(clazz); |
| } |
| |
| MethodResolutionResult resolveMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) { |
| return resolveMaximallySpecificTargetHelper(lambda, method).internalResolve(null); |
| } |
| |
| // Non-private lookup (ie, not resolution) to find interface targets. |
| DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) { |
| return resolveMaximallySpecificTargetHelper(clazz, method).lookup(); |
| } |
| |
| private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper( |
| DexClass clazz, DexMethod method) { |
| MaximallySpecificMethodsBuilder builder = |
| new MaximallySpecificMethodsBuilder(definitionFor, factory); |
| resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder); |
| return builder; |
| } |
| |
| private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper( |
| LambdaDescriptor lambda, DexMethod method) { |
| MaximallySpecificMethodsBuilder builder = |
| new MaximallySpecificMethodsBuilder(definitionFor, factory); |
| resolveMethodStep3Helper( |
| method.getProto(), method.getName(), null, builder, factory.objectType, lambda.interfaces); |
| 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, |
| DexString methodName, |
| DexClass clazz, |
| MaximallySpecificMethodsBuilder builder) { |
| resolveMethodStep3Helper( |
| methodProto, |
| methodName, |
| clazz, |
| builder, |
| clazz.superType, |
| Arrays.asList(clazz.interfaces.values)); |
| } |
| |
| private void resolveMethodStep3Helper( |
| DexProto methodProto, |
| DexString methodName, |
| DexClass clazz, |
| 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); |
| 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, uniquePathOracle.onIncompletePath(definition)); |
| } else { |
| // Look at the super-interfaces of this class and keep searching. |
| resolveMethodStep3Helper( |
| methodProto, |
| methodName, |
| definition, |
| builder, |
| definition.superType, |
| Arrays.asList(definition.interfaces.values), |
| uniquePathOracle); |
| } |
| }); |
| } |
| // Now look at indirect super interfaces. |
| if (superType != null) { |
| definitionFor(superType) |
| .forEachClassResolutionResult( |
| superClass -> { |
| // Guard against going back into the program for resolution. |
| if (escapeIfLibraryHasProgramSuperType |
| && clazz != null |
| && clazz.isLibraryClass() |
| && !superClass.isLibraryClass()) { |
| return; |
| } |
| resolveMethodStep3Helper( |
| methodProto, |
| methodName, |
| superClass, |
| builder, |
| superClass.superType, |
| Arrays.asList(superClass.interfaces.values), |
| uniquePathOracle); |
| }); |
| } |
| } |
| |
| /** |
| * A candidate for being a maximally specific method must have neither its private, nor its static |
| * flag set. A candidate may still not be maximally specific, which entails that no subinterfaces |
| * from also contribute with a candidate to the type. That is not determined by this method. |
| */ |
| private boolean isMaximallySpecificCandidate(DexEncodedMethod method) { |
| return method != null && !method.accessFlags.isPrivate() && !method.accessFlags.isStatic(); |
| } |
| |
| /** |
| * Implements resolution of a method descriptor against an interface type. |
| * |
| * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3"> |
| * Section 5.4.3.4 of the JVM Spec</a>. |
| * |
| * <p>The resolved method is not the method that will actually be invoked. Which methods gets |
| * invoked depends on the invoke instruction used. However, it is always save to rewrite any |
| * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the |
| * resolved method is used as basis for dispatch. |
| */ |
| public MethodResolutionResult resolveMethodOnInterface( |
| DexType holder, DexProto proto, DexString methodName) { |
| if (holder.isArrayType()) { |
| return IncompatibleClassResult.INSTANCE; |
| } |
| MethodResolutionResult.Builder builder = MethodResolutionResult.builder(); |
| // Step 1: Lookup interface. |
| definitionFor(holder) |
| .forEachClassResolutionResult( |
| definition -> { |
| // If the definition is not an interface, resolution fails with an ICCE. |
| if (!definition.isInterface()) { |
| builder.addResolutionResult(IncompatibleClassResult.INSTANCE); |
| } else { |
| builder.addResolutionResult( |
| resolveMethodOnInterface(definition, proto, methodName)); |
| } |
| }); |
| return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, holder); |
| } |
| |
| public MethodResolutionResult resolveMethodOnInterface( |
| DexClass definition, DexProto methodProto, DexString methodName) { |
| assert definition.isInterface(); |
| // Step 2: Look for exact method on interface. |
| DexEncodedMethod result = definition.lookupMethod(methodProto, methodName); |
| if (result != null) { |
| return MethodResolutionResult.createSingleResolutionResult(definition, definition, result); |
| } |
| // Step 3: Look for matching method on object class. |
| MethodResolutionResult.Builder builder = MethodResolutionResult.builder(); |
| definitionFor(factory.objectType) |
| .forEachClassResolutionResult( |
| objectClass -> { |
| DexEncodedMethod objectResult = objectClass.lookupMethod(methodProto, methodName); |
| if (objectResult != null |
| && objectResult.accessFlags.isPublic() |
| && !objectResult.accessFlags.isAbstract()) { |
| builder.addResolutionResult( |
| MethodResolutionResult.createSingleResolutionResult( |
| definition, objectClass, objectResult)); |
| } else { |
| // Step 3: Look for maximally-specific superinterface methods or any interface |
| // definition. This is the same for classes and interfaces. |
| builder.addResolutionResult( |
| resolveMethodStep3(definition, methodProto, methodName)); |
| } |
| }); |
| return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, Collections.emptySet()); |
| } |
| |
| static class MaximallySpecificMethodsBuilder { |
| |
| // The set of actual maximally specific methods. |
| // This set is linked map so that in the case where a number of methods remain a deterministic |
| // choice can be made. The map is from definition classes to their maximally specific method, or |
| // 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> |
| 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( |
| Function<DexType, ClassResolutionResult> definitionFor, DexItemFactory factory) { |
| this.definitionFor = definitionFor; |
| this.factory = factory; |
| } |
| |
| 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 (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; |
| } |
| maximallySpecificMethodsOnCompletePaths.put(holder, method); |
| // Prune exiting candidates and prohibit future candidates in the super hierarchy. |
| assert holder.isInterface(); |
| assert holder.superType == factory.objectType; |
| for (DexType iface : holder.interfaces.values) { |
| markShadowed(iface); |
| } |
| } |
| |
| private void markShadowed(DexType type) { |
| if (type == null) { |
| return; |
| } |
| 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() { |
| return internalResolve(null).getResolutionPair(); |
| } |
| |
| MethodResolutionResult resolve(DexClass initialResolutionHolder) { |
| assert initialResolutionHolder != null; |
| return internalResolve(initialResolutionHolder); |
| } |
| |
| private MethodResolutionResult internalResolve(DexClass initialResolutionHolder) { |
| if (maximallySpecificMethodsOnCompletePaths.isEmpty() |
| && maximallySpecificMethodsOnIncompletePaths.isEmpty()) { |
| return NoSuchMethodResult.INSTANCE; |
| } |
| List<Entry<DexClass, DexEncodedMethod>> nonAbstractOnComplete = |
| getNonAbstractMethods(maximallySpecificMethodsOnCompletePaths); |
| if (nonAbstractOnComplete.size() > 1) { |
| return IncompatibleClassResult.create( |
| ListUtils.map(nonAbstractOnComplete, Entry::getValue)); |
| } |
| 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()) { |
| Entry<DexClass, DexEncodedMethod> abstractMethod = |
| firstNonNullEntry(maximallySpecificMethodsOnCompletePaths); |
| if (abstractMethod == null) { |
| abstractMethod = firstNonNullEntry(maximallySpecificMethodsOnIncompletePaths); |
| } |
| assert abstractMethod != null && abstractMethod.getValue().isAbstract(); |
| return singleResultHelper(initialResolutionHolder, abstractMethod); |
| } 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(NoSuchMethodResult.INSTANCE); |
| } else { |
| nonAbstractOnComplete.forEach( |
| entry -> |
| builder.addResolutionResult(singleResultHelper(initialResolutionHolder, entry))); |
| } |
| nonAbstractOnIncomplete.forEach( |
| entry -> |
| builder.addResolutionResult(singleResultHelper(initialResolutionHolder, entry))); |
| return builder.buildOrIfEmpty(NoSuchMethodResult.INSTANCE, typesWithMultipleDefinitions); |
| } |
| } |
| |
| 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 (method.isNonAbstractVirtualMethod()) { |
| nonAbstractMethods.add(entry); |
| } |
| } |
| return nonAbstractMethods; |
| } |
| |
| private static SingleResolutionResult<?> singleResultHelper( |
| DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) { |
| return MethodResolutionResult.createSingleResolutionResult( |
| initialResolutionResult != null ? initialResolutionResult : entry.getKey(), |
| 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; |
| } |
| } |
| return null; |
| } |
| } |
| } |