blob: bdea2653bff7261955d3ff564bf69c99b94eacd4 [file] [log] [blame]
// 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 static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
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.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);
public abstract boolean isValidVirtualTarget(InternalOptions options);
public abstract boolean isValidVirtualTargetForDynamicDispatch();
/** Lookup the single target of an invoke-super on this resolution result if possible. */
public abstract DexEncodedMethod lookupInvokeSuperTarget(DexType 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 static boolean isValidVirtualTarget(InternalOptions options, DexEncodedMethod target) {
return options.canUseNestBasedAccess()
? (!target.accessFlags.isStatic() && !target.accessFlags.isConstructor())
: target.isVirtualMethod();
}
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, initialResolutionHolder, context, appInfo);
}
@Override
public boolean isAccessibleForVirtualDispatchFrom(
DexProgramClass context, AppInfoWithSubtyping appInfo) {
// If a private method is accessible (which implies it is via its nest), then it is a valid
// virtual dispatch target if non-static.
return isAccessibleFrom(context, appInfo)
&& (resolvedMethod.isVirtualMethod()
|| (resolvedMethod.isPrivateMethod() && !resolvedMethod.isStatic()));
}
@Override
public boolean isValidVirtualTarget(InternalOptions options) {
return isValidVirtualTarget(options, resolvedMethod);
}
@Override
public boolean isValidVirtualTargetForDynamicDispatch() {
return resolvedMethod.isVirtualMethod();
}
/**
* Lookup super method following the super chain from the holder of {@code method}.
*
* <p>This method will resolve the method on the holder of {@code method} and only return a
* non-null value if the result of resolution was an instance (i.e. non-static) method.
*
* <p>Additionally, this will also verify that the invoke super is valid, i.e., it is on the
* same type or a super type of the current context. The spec says that it has invoke super
* semantics, if the type is a supertype of the current class. If it is the same or a subtype,
* it has invoke direct semantics. The latter case is illegal, so we map it to a super call
* here. In R8, we abort at a later stage (see. See also <a href=
* "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" </a>
* for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/"
* "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In
* particular, the requirement isAssignable(class(CurrentClassName, L), class(MethodClassName,
* L)). com.android.tools.r8.cf.code.CfInvoke#isInvokeSuper(DexType)}.
*
* @param context the 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 none found.
*/
@Override
public DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo) {
DexMethod method = resolvedMethod.method;
// TODO(b/145775365): Check the requirements for an invoke-special to a protected method.
// See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial
if (appInfo.hasSubtyping()
&& !appInfo.withSubtyping().isSubtype(context, initialResolutionHolder.type)) {
DexClass contextClass = appInfo.definitionFor(context);
throw new CompilationError(
"Illegal invoke-super to " + method.toSourceString() + " from class " + context,
contextClass != null ? contextClass.getOrigin() : Origin.unknown());
}
// According to
// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
// the "symbolic reference" if the "symbolic reference" does not name a class.
// TODO(b/145775365): This looks like the exact opposite of what the spec says, second item is
// - is-class(sym-ref) => is-super(sym-ref, current-class)
// this implication trivially holds for !is-class(sym-ref) == is-inteface(sym-ref), thus
// the resolution should specifically *not* use the "symbolic reference".
if (initialResolutionHolder.isInterface()) {
// TODO(b/145775365): This does not consider a static method!
return appInfo.resolveMethodOnInterface(initialResolutionHolder, method).getSingleTarget();
}
// Then, resume on the search, but this time, starting from the holder of the caller.
DexClass contextClass = appInfo.definitionFor(context);
if (contextClass == null || contextClass.superType == null) {
return null;
}
SingleResolutionResult resolution =
appInfo.resolveMethodOnClass(contextClass.superType, method).asSingleResolution();
return resolution != null && !resolution.resolvedMethod.isStatic()
? resolution.resolvedMethod
: null;
}
@Override
// TODO(b/140204899): Leverage refined receiver type if available.
public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
assert isValidVirtualTarget(appInfo.app().options);
// First add the target for receiver type method.type.
DexEncodedMethod encodedMethod = getSingleTarget();
Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
// Add all matching targets from the subclass hierarchy.
DexMethod method = encodedMethod.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.isVirtualMethod()) {
result.add(target);
}
}
}
return result;
}
@Override
// TODO(b/140204899): Leverage refined receiver type if available.
public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
assert isValidVirtualTarget(appInfo.app().options);
Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
if (isSingleResolution()) {
// 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() { }
// }
//
DexEncodedMethod singleTarget = getSingleTarget();
if (singleTarget.hasCode()) {
DexProgramClass holder =
asProgramClassOrNull(appInfo.definitionFor(singleTarget.method.holder));
if (appInfo.hasAnyInstantiatedLambdas(holder)) {
result.add(singleTarget);
}
}
}
DexEncodedMethod encodedMethod = getSingleTarget();
DexMethod method = encodedMethod.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()) {
addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
}
} else {
ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
if (targetMethods.isSingleResolution()) {
addIfNotAbstract.accept(targetMethods.getSingleTarget());
}
}
}
return result;
}
}
abstract static class EmptyResult extends ResolutionResult {
@Override
public final DexEncodedMethod lookupInvokeSuperTarget(DexType 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 isValidVirtualTarget(InternalOptions options) {
return true;
}
@Override
public boolean isValidVirtualTargetForDynamicDispatch() {
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 isValidVirtualTarget(InternalOptions options) {
return false;
}
@Override
public boolean isValidVirtualTargetForDynamicDispatch() {
return false;
}
}
public static class ClassNotFoundResult extends FailedResolutionResult {
static final ClassNotFoundResult INSTANCE = new ClassNotFoundResult();
private ClassNotFoundResult() {
// Intentionally left empty.
}
}
public static class IncompatibleClassResult extends FailedResolutionResult {
static final IncompatibleClassResult INSTANCE =
new IncompatibleClassResult(Collections.emptyList());
private final Collection<DexEncodedMethod> methodsCausingError;
private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
this.methodsCausingError = methodsCausingError;
}
static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
return methodsCausingError.isEmpty()
? INSTANCE
: new IncompatibleClassResult(methodsCausingError);
}
@Override
public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
this.methodsCausingError.forEach(methodCausingFailureConsumer);
}
}
public static class NoSuchMethodResult extends FailedResolutionResult {
static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
private NoSuchMethodResult() {
// Intentionally left empty.
}
}
}