| // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.graph; |
| |
| import com.android.tools.r8.errors.CompilationError; |
| import com.android.tools.r8.utils.SetUtils; |
| import com.google.common.collect.Sets; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Set; |
| import java.util.function.BiPredicate; |
| import java.util.function.Consumer; |
| |
| public abstract class ResolutionResult { |
| |
| /** |
| * Returns true if resolution succeeded *and* the resolved method has a known definition. |
| * |
| * <p>Note that {@code !isSingleResolution() && !isFailedResolution()} can be true. In that case |
| * that resolution has succeeded, but the definition of the resolved method is unknown. In |
| * particular this is the case for the clone() method on arrays. |
| */ |
| public boolean isSingleResolution() { |
| return false; |
| } |
| |
| /** Returns non-null if isSingleResolution() is true, otherwise null. */ |
| public SingleResolutionResult asSingleResolution() { |
| return null; |
| } |
| |
| /** |
| * Returns true if resolution failed. |
| * |
| * <p>Note the disclaimer in the doc of {@code isSingleResolution()}. |
| */ |
| public boolean isFailedResolution() { |
| return false; |
| } |
| |
| /** Returns non-null if isFailedResolution() is true, otherwise null. */ |
| public FailedResolutionResult asFailedResolution() { |
| return null; |
| } |
| |
| /** Short-hand to get the single resolution method if resolution finds it, null otherwise. */ |
| public final DexEncodedMethod getSingleTarget() { |
| return isSingleResolution() ? asSingleResolution().getResolvedMethod() : null; |
| } |
| |
| public abstract boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo); |
| |
| public abstract boolean isAccessibleForVirtualDispatchFrom( |
| DexProgramClass context, AppInfoWithSubtyping appInfo); |
| |
| // TODO(b/145187573): Remove this and use proper access checks. |
| @Deprecated |
| public abstract boolean isVirtualTarget(); |
| |
| /** Lookup the single target of an invoke-special on this resolution result if possible. */ |
| public abstract DexEncodedMethod lookupInvokeSpecialTarget( |
| DexProgramClass context, AppInfoWithSubtyping appInfo); |
| |
| /** Lookup the single target of an invoke-super on this resolution result if possible. */ |
| public abstract DexEncodedMethod lookupInvokeSuperTarget( |
| DexProgramClass context, AppInfoWithSubtyping appInfo); |
| |
| @Deprecated |
| public abstract DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo); |
| |
| public final Set<DexEncodedMethod> lookupVirtualDispatchTargets( |
| boolean isInterface, AppInfoWithSubtyping appInfo) { |
| return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo); |
| } |
| |
| public abstract Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo); |
| |
| public abstract Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo); |
| |
| /** Result for a resolution that succeeds with a known declaration/definition. */ |
| public static class SingleResolutionResult extends ResolutionResult { |
| private final DexClass initialResolutionHolder; |
| private final DexClass resolvedHolder; |
| private final DexEncodedMethod resolvedMethod; |
| |
| public SingleResolutionResult( |
| DexClass initialResolutionHolder, |
| DexClass resolvedHolder, |
| DexEncodedMethod resolvedMethod) { |
| assert initialResolutionHolder != null; |
| assert resolvedHolder != null; |
| assert resolvedMethod != null; |
| assert resolvedHolder.type == resolvedMethod.method.holder; |
| this.resolvedHolder = resolvedHolder; |
| this.resolvedMethod = resolvedMethod; |
| this.initialResolutionHolder = initialResolutionHolder; |
| } |
| |
| public DexClass getResolvedHolder() { |
| return resolvedHolder; |
| } |
| |
| public DexEncodedMethod getResolvedMethod() { |
| return resolvedMethod; |
| } |
| |
| @Override |
| public boolean isSingleResolution() { |
| return true; |
| } |
| |
| @Override |
| public SingleResolutionResult asSingleResolution() { |
| return this; |
| } |
| |
| @Override |
| public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return AccessControl.isMethodAccessible( |
| resolvedMethod, resolvedHolder, initialResolutionHolder, context, appInfo); |
| } |
| |
| @Override |
| public boolean isAccessibleForVirtualDispatchFrom( |
| DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return resolvedMethod.isVirtualMethod() && isAccessibleFrom(context, appInfo); |
| } |
| |
| @Override |
| public boolean isVirtualTarget() { |
| return resolvedMethod.isVirtualMethod(); |
| } |
| |
| /** |
| * This is intended to model the actual behavior of invoke-special on a JVM. |
| * |
| * <p>See https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokespecial |
| * and comments below for deviations due to diverging behavior on actual JVMs. |
| */ |
| @Override |
| public DexEncodedMethod lookupInvokeSpecialTarget( |
| DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| // If the resolution is non-accessible then no target exists. |
| if (isAccessibleFrom(context, appInfo)) { |
| return internalInvokeSpecialOrSuper( |
| context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo)); |
| } |
| return null; |
| } |
| |
| /** |
| * Lookup the target of an invoke-super. |
| * |
| * <p>This will return the target iff the resolution succeeded and the target is valid (i.e., |
| * non-static and non-initializer) and accessible from {@code context}. |
| * |
| * <p>Additionally, this will also verify that the invoke-super is valid, i.e., it is on the a |
| * super type of the current context. Any invoke-special targeting the same type should have |
| * been mapped to an invoke-direct, but could change due to merging so we need to still allow |
| * the context to be equal to the targeted (symbolically referenced) type. |
| * |
| * @param context Class the invoke is contained in, i.e., the holder of the caller. |
| * @param appInfo Application info. |
| * @return The actual target for the invoke-super or {@code null} if no valid target is found. |
| */ |
| @Override |
| public DexEncodedMethod lookupInvokeSuperTarget( |
| DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| if (isAccessibleFrom(context, appInfo)) { |
| return lookupInvokeSuperTarget(context.asDexClass(), appInfo); |
| } |
| return null; |
| } |
| |
| @Override |
| public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) { |
| assert context != null; |
| if (resolvedMethod.isInstanceInitializer() |
| || (appInfo.hasSubtyping() |
| && initialResolutionHolder != context |
| && !isSuperclass(initialResolutionHolder, context, appInfo.withSubtyping()))) { |
| throw new CompilationError( |
| "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin()); |
| } |
| return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true); |
| } |
| |
| private DexEncodedMethod internalInvokeSpecialOrSuper( |
| DexClass context, AppInfo appInfo, BiPredicate<DexClass, DexClass> isSuperclass) { |
| |
| // Statics cannot be targeted by invoke-special/super. |
| if (getResolvedMethod().isStatic()) { |
| return null; |
| } |
| |
| // The symbolic reference is the holder type that resolution was initiated at. |
| DexClass symbolicReference = initialResolutionHolder; |
| |
| // First part of the spec is to determine the starting point for lookup for invoke special. |
| // Notice that the specification indicates that the immediate super type should |
| // be used when three items hold, the second being: |
| // is-class(sym-ref) => is-super(sym-ref, context) |
| // in the case of an interface that is trivially satisfied, which would lead the initial type |
| // to be java.lang.Object. However in practice the lookup appears to start at the symbolic |
| // reference in the case of interfaces, so the second condition should likely be interpreted: |
| // is-class(sym-ref) *and* is-super(sym-ref, context). |
| final DexClass initialType; |
| if (!resolvedMethod.isInstanceInitializer() |
| && !symbolicReference.isInterface() |
| && isSuperclass.test(symbolicReference, context)) { |
| // If reference is a super type of the context then search starts at the immediate super. |
| initialType = context.superType == null ? null : appInfo.definitionFor(context.superType); |
| } else { |
| // Otherwise it starts at the reference itself. |
| initialType = symbolicReference; |
| } |
| // Abort if for some reason the starting point could not be found. |
| if (initialType == null) { |
| return null; |
| } |
| // 1-3. Search the initial class and its supers in order for a matching instance method. |
| DexMethod method = getResolvedMethod().method; |
| DexEncodedMethod target = null; |
| DexClass current = initialType; |
| while (current != null) { |
| target = current.lookupMethod(method); |
| if (target != null) { |
| break; |
| } |
| current = current.superType == null ? null : appInfo.definitionFor(current.superType); |
| } |
| // 4. Otherwise, it is the single maximally specific method: |
| if (target == null) { |
| target = appInfo.resolveMaximallySpecificMethods(initialType, method).getSingleTarget(); |
| } |
| if (target == null) { |
| return null; |
| } |
| // Linking exceptions: |
| // A non-instance method throws IncompatibleClassChangeError. |
| if (target.isStatic()) { |
| return null; |
| } |
| // An instance initializer that is not to the symbolic reference throws NoSuchMethodError. |
| // It appears as if this check is also in place for non-initializer methods too. |
| // See NestInvokeSpecialMethodAccessWithIntermediateTest. |
| if ((target.isInstanceInitializer() || target.isPrivateMethod()) |
| && target.method.holder != symbolicReference.type) { |
| return null; |
| } |
| // Runtime exceptions: |
| // An abstract method throws AbstractMethodError. |
| if (target.isAbstract()) { |
| return null; |
| } |
| return target; |
| } |
| |
| private static boolean isSuperclass(DexClass sup, DexClass sub, AppInfoWithSubtyping appInfo) { |
| return sup != sub && appInfo.isSubtype(sub.type, sup.type); |
| } |
| |
| @Override |
| // TODO(b/140204899): Leverage refined receiver type if available. |
| public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) { |
| if (resolvedMethod.isPrivateMethod()) { |
| // If the resolved reference is private there is no dispatch. |
| // This is assuming that the method is accessible, which implies self/nest access. |
| return Collections.singleton(resolvedMethod); |
| } |
| assert resolvedMethod.isNonPrivateVirtualMethod(); |
| // First add the target for receiver type method.type. |
| Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(resolvedMethod); |
| // Add all matching targets from the subclass hierarchy. |
| DexMethod method = resolvedMethod.method; |
| // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined |
| // receiver type if available. |
| for (DexType type : appInfo.subtypes(method.holder)) { |
| DexClass clazz = appInfo.definitionFor(type); |
| if (!clazz.isInterface()) { |
| ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method); |
| DexEncodedMethod target = methods.getSingleTarget(); |
| if (target != null && target.isNonPrivateVirtualMethod()) { |
| result.add(target); |
| } |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| // TODO(b/140204899): Leverage refined receiver type if available. |
| public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) { |
| if (resolvedMethod.isPrivateMethod()) { |
| // If the resolved reference is private there is no dispatch. |
| // This is assuming that the method is accessible, which implies self/nest access. |
| assert resolvedMethod.hasCode(); |
| return Collections.singleton(resolvedMethod); |
| } |
| assert resolvedMethod.isNonPrivateVirtualMethod(); |
| Set<DexEncodedMethod> result = Sets.newIdentityHashSet(); |
| // Add default interface methods to the list of targets. |
| // |
| // This helps to make sure we take into account synthesized lambda classes |
| // that we are not aware of. Like in the following example, we know that all |
| // classes, XX in this case, override B::bar(), but there are also synthesized |
| // classes for lambda which don't, so we still need default method to be live. |
| // |
| // public static void main(String[] args) { |
| // X x = () -> {}; |
| // x.bar(); |
| // } |
| // |
| // interface X { |
| // void foo(); |
| // default void bar() { } |
| // } |
| // |
| // class XX implements X { |
| // public void foo() { } |
| // public void bar() { } |
| // } |
| // |
| addIfDefaultMethodWithLambdaInstantiations(appInfo, resolvedMethod, result); |
| |
| DexMethod method = resolvedMethod.method; |
| Consumer<DexEncodedMethod> addIfNotAbstract = |
| m -> { |
| if (!m.accessFlags.isAbstract()) { |
| result.add(m); |
| } |
| }; |
| // Default methods are looked up when looking at a specific subtype that does not override |
| // them. Otherwise, we would look up default methods that are actually never used. However, we |
| // have to add bridge methods, otherwise we can remove a bridge that will be used. |
| Consumer<DexEncodedMethod> addIfNotAbstractAndBridge = |
| m -> { |
| if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) { |
| result.add(m); |
| } |
| }; |
| |
| // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined |
| // receiver type if available. |
| for (DexType type : appInfo.subtypes(method.holder)) { |
| DexClass clazz = appInfo.definitionFor(type); |
| if (clazz.isInterface()) { |
| ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method); |
| if (targetMethods.isSingleResolution()) { |
| // Sub-interfaces can have default implementations that override the resolved method. |
| // Therefore we have to add default methods in sub interfaces. |
| DexEncodedMethod singleTarget = targetMethods.getSingleTarget(); |
| addIfDefaultMethodWithLambdaInstantiations(appInfo, singleTarget, result); |
| addIfNotAbstractAndBridge.accept(singleTarget); |
| } |
| } else { |
| ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method); |
| if (targetMethods.isSingleResolution()) { |
| addIfNotAbstract.accept(targetMethods.getSingleTarget()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private void addIfDefaultMethodWithLambdaInstantiations( |
| AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexEncodedMethod> result) { |
| if (method == null) { |
| return; |
| } |
| if (method.hasCode()) { |
| DexProgramClass holder = appInfo.definitionForProgramType(method.method.holder); |
| if (appInfo.hasAnyInstantiatedLambdas(holder)) { |
| result.add(method); |
| } |
| } |
| } |
| } |
| |
| abstract static class EmptyResult extends ResolutionResult { |
| |
| @Override |
| public final DexEncodedMethod lookupInvokeSpecialTarget( |
| DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return null; |
| } |
| |
| @Override |
| public DexEncodedMethod lookupInvokeSuperTarget( |
| DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return null; |
| } |
| |
| @Override |
| public final DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) { |
| return null; |
| } |
| |
| @Override |
| public final Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) { |
| return null; |
| } |
| |
| @Override |
| public final Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) { |
| return null; |
| } |
| } |
| |
| /** Singleton result for the special case resolving the array clone() method. */ |
| public static class ArrayCloneMethodResult extends EmptyResult { |
| |
| static final ArrayCloneMethodResult INSTANCE = new ArrayCloneMethodResult(); |
| |
| private ArrayCloneMethodResult() { |
| // Intentionally left empty. |
| } |
| |
| @Override |
| public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return true; |
| } |
| |
| @Override |
| public boolean isAccessibleForVirtualDispatchFrom( |
| DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return true; |
| } |
| |
| @Override |
| public boolean isVirtualTarget() { |
| return true; |
| } |
| } |
| |
| /** Base class for all types of failed resolutions. */ |
| public abstract static class FailedResolutionResult extends EmptyResult { |
| |
| @Override |
| public boolean isFailedResolution() { |
| return true; |
| } |
| |
| @Override |
| public FailedResolutionResult asFailedResolution() { |
| return this; |
| } |
| |
| public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) { |
| // Default failure has no dependencies. |
| } |
| |
| @Override |
| public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return false; |
| } |
| |
| @Override |
| public boolean isAccessibleForVirtualDispatchFrom( |
| DexProgramClass context, AppInfoWithSubtyping appInfo) { |
| return false; |
| } |
| |
| @Override |
| public boolean isVirtualTarget() { |
| return false; |
| } |
| } |
| |
| public static class ClassNotFoundResult extends FailedResolutionResult { |
| static final ClassNotFoundResult INSTANCE = new ClassNotFoundResult(); |
| |
| private ClassNotFoundResult() { |
| // Intentionally left empty. |
| } |
| } |
| |
| abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult { |
| |
| private final Collection<DexEncodedMethod> methodsCausingError; |
| |
| private FailedResolutionWithCausingMethods(Collection<DexEncodedMethod> methodsCausingError) { |
| this.methodsCausingError = methodsCausingError; |
| } |
| |
| @Override |
| public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) { |
| this.methodsCausingError.forEach(methodCausingFailureConsumer); |
| } |
| } |
| |
| public static class IncompatibleClassResult extends FailedResolutionWithCausingMethods { |
| static final IncompatibleClassResult INSTANCE = |
| new IncompatibleClassResult(Collections.emptyList()); |
| |
| private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) { |
| super(methodsCausingError); |
| } |
| |
| static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) { |
| return methodsCausingError.isEmpty() |
| ? INSTANCE |
| : new IncompatibleClassResult(methodsCausingError); |
| } |
| } |
| |
| public static class NoSuchMethodResult extends FailedResolutionResult { |
| |
| static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult(); |
| } |
| |
| public static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods { |
| |
| public IllegalAccessOrNoSuchMethodResult(DexEncodedMethod methodCausingError) { |
| super(Collections.singletonList(methodCausingError)); |
| assert methodCausingError != null; |
| } |
| } |
| } |