blob: 60b1176fb185a7dc61b5b5b208dc0bdd9ee8828b [file] [log] [blame]
// Copyright (c) 2024, 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.optimize;
import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiSafeForMemberRebinding;
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.LibraryMethod;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Iterables;
import java.util.function.BiFunction;
public class MemberRebindingHelper {
protected final AndroidApiLevelCompute androidApiLevelCompute;
protected final AppView<AppInfoWithLiveness> appView;
protected final InternalOptions options;
public MemberRebindingHelper(AppView<AppInfoWithLiveness> appView) {
this.androidApiLevelCompute = appView.apiLevelCompute();
this.appView = appView;
this.options = appView.options();
}
public DexMethod validMemberRebindingTargetForNonProgramMethod(
DexClassAndMethod resolvedMethod,
SingleResolutionResult<?> resolutionResult,
ProgramMethodSet contexts,
InvokeType invokeType,
DexMethod original) {
assert !resolvedMethod.isProgramMethod();
if (invokeType.isDirect()) {
return original;
}
if (invokeType.isSuper() && options.canHaveSuperInvokeBug()) {
// To preserve semantics we should find the first library method on the boundary.
DexType firstLibraryTarget =
firstLibraryClassOrFirstInterfaceTarget(
resolutionResult.getResolvedHolder(),
appView,
resolvedMethod.getReference(),
original.getHolderType(),
DexClass::lookupMethod);
if (firstLibraryTarget == null) {
return original;
}
DexClass libraryHolder = appView.definitionFor(firstLibraryTarget);
if (libraryHolder == null) {
return original;
}
if (libraryHolder == resolvedMethod.getHolder()) {
return resolvedMethod.getReference();
}
return resolvedMethod.getReference().withHolder(libraryHolder, appView.dexItemFactory());
}
LibraryMethod eligibleLibraryMethod = null;
SingleResolutionResult<?> currentResolutionResult = resolutionResult;
while (currentResolutionResult != null) {
DexClassAndMethod currentResolvedMethod = currentResolutionResult.getResolutionPair();
if (canRebindDirectlyToLibraryMethod(
currentResolvedMethod,
currentResolutionResult.withInitialResolutionHolder(
currentResolutionResult.getResolvedHolder()),
contexts,
invokeType,
original)) {
eligibleLibraryMethod = currentResolvedMethod.asLibraryMethod();
}
if (appView.getAssumeInfoCollection().contains(currentResolvedMethod)) {
break;
}
DexClass currentResolvedHolder = currentResolvedMethod.getHolder();
if (resolvedMethod.getDefinition().belongsToVirtualPool()
&& !currentResolvedHolder.isInterface()
&& currentResolvedHolder.getSuperType() != null) {
currentResolutionResult =
appView
.appInfo()
.resolveMethodOnClassLegacy(currentResolvedHolder.getSuperType(), original)
.asSingleResolution();
} else {
break;
}
}
if (eligibleLibraryMethod != null) {
return eligibleLibraryMethod.getReference();
}
DexType newHolder =
firstLibraryClassOrFirstInterfaceTarget(
resolvedMethod.getHolder(),
appView,
resolvedMethod.getReference(),
original.getHolderType(),
DexClass::lookupMethod);
return newHolder != null ? original.withHolder(newHolder, appView.dexItemFactory()) : original;
}
private boolean canRebindDirectlyToLibraryMethod(
DexClassAndMethod resolvedMethod,
SingleResolutionResult<?> resolutionResult,
ProgramMethodSet contexts,
InvokeType invokeType,
DexMethod original) {
// TODO(b/194422791): It could potentially be that `original.holder` is not a subtype of
// `original.holder` on all API levels, in which case it is not OK to rebind to the resolved
// method.
return resolvedMethod.isLibraryMethod()
&& isAccessibleInAllContexts(resolvedMethod, resolutionResult, contexts)
&& !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType)
&& !isInvokeSuperToAbstractMethod(resolvedMethod, invokeType)
&& isApiSafeForMemberRebinding(
resolvedMethod.asLibraryMethod(), original, androidApiLevelCompute, options);
}
private boolean isAccessibleInAllContexts(
DexClassAndMethod resolvedMethod,
SingleResolutionResult<?> resolutionResult,
ProgramMethodSet contexts) {
if (resolvedMethod.getHolder().isPublic() && resolvedMethod.getDefinition().isPublic()) {
return true;
}
return Iterables.all(
contexts,
context -> resolutionResult.isAccessibleFrom(context, appView, appView.appInfo()).isTrue());
}
private boolean isInvokeSuperToInterfaceMethod(DexClassAndMethod method, InvokeType invokeType) {
return method.getHolder().isInterface() && invokeType.isSuper();
}
private boolean isInvokeSuperToAbstractMethod(DexClassAndMethod method, InvokeType invokeType) {
return options.canHaveSuperInvokeToAbstractMethodBug()
&& method.getAccessFlags().isAbstract()
&& invokeType.isSuper();
}
public static DexField validMemberRebindingTargetFor(
DexDefinitionSupplier definitions, DexClassAndField field, DexField original) {
if (field.isProgramField()) {
return field.getReference();
}
DexClass fieldHolder = field.getHolder();
DexType newHolder =
firstLibraryClassOrFirstInterfaceTarget(
fieldHolder,
definitions,
field.getReference(),
original.getHolderType(),
DexClass::lookupField);
return newHolder != null
? original.withHolder(newHolder, definitions.dexItemFactory())
: original;
}
private static <T> DexType firstLibraryClassOrFirstInterfaceTarget(
DexClass holder,
DexDefinitionSupplier definitions,
T target,
DexType current,
BiFunction<DexClass, T, DexEncodedMember<?, ?>> lookup) {
return holder.isInterface()
? firstLibraryClassForInterfaceTarget(definitions, target, current, lookup)
: firstLibraryClass(definitions, current);
}
private static <T> DexType firstLibraryClassForInterfaceTarget(
DexDefinitionSupplier definitions,
T target,
DexType current,
BiFunction<DexClass, T, DexEncodedMember<?, ?>> lookup) {
DexClass clazz = definitions.definitionFor(current);
if (clazz == null) {
return null;
}
if (!clazz.isProgramClass()) {
DexEncodedMember<?, ?> potential = lookup.apply(clazz, target);
if (potential != null) {
// Found, return type.
return current;
}
}
if (clazz.hasSuperType()) {
DexType matchingSuper =
firstLibraryClassForInterfaceTarget(definitions, target, clazz.getSuperType(), lookup);
if (matchingSuper != null) {
// Found in supertype, return first library class.
return clazz.isNotProgramClass() ? current : matchingSuper;
}
}
for (DexType iface : clazz.getInterfaces()) {
DexType matchingIface =
firstLibraryClassForInterfaceTarget(definitions, target, iface, lookup);
if (matchingIface != null) {
// Found in interface, return first library class.
return clazz.isNotProgramClass() ? current : matchingIface;
}
}
return null;
}
private static DexType firstLibraryClass(DexDefinitionSupplier definitions, DexType bottom) {
DexClass searchClass = definitions.contextIndependentDefinitionFor(bottom);
while (searchClass != null && searchClass.isProgramClass()) {
searchClass =
definitions.definitionFor(searchClass.getSuperType(), searchClass.asProgramClass());
}
return searchClass != null ? searchClass.getType() : null;
}
}