blob: 086b81fbb0f14a3d198ae8a2d23109236d6d9b81 [file] [log] [blame]
// Copyright (c) 2023, 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.accessmodification;
import static com.android.tools.r8.dex.Constants.ACC_PRIVATE;
import static com.android.tools.r8.dex.Constants.ACC_PROTECTED;
import static com.android.tools.r8.dex.Constants.ACC_PUBLIC;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.optimize.accessmodification.AccessModifierTraversal.BottomUpTraversalState;
import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection;
import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class AccessModifier {
private final AppView<AppInfoWithLiveness> appView;
private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
private final AccessModifierLens.Builder lensBuilder = AccessModifierLens.builder();
private final NonProgramMethodsCollection nonProgramMethodsCollection;
private final InternalOptions options;
private AccessModifier(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.createWithDeterministicOrder(appView);
this.nonProgramMethodsCollection =
ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView);
this.options = appView.options();
}
public static void run(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
timing.begin("Access modification");
AccessModifierOptions accessModifierOptions = appView.options().getAccessModifierOptions();
if (accessModifierOptions.isAccessModificationEnabled()) {
new AccessModifier(appView)
.processStronglyConnectedComponents(executorService)
.installLens(executorService, timing);
}
timing.end();
}
private AccessModifier processStronglyConnectedComponents(ExecutorService executorService)
throws ExecutionException {
// Compute the connected program classes and process the components in parallel.
List<Set<DexProgramClass>> stronglyConnectedComponents =
new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
.computeStronglyConnectedComponents();
ThreadUtils.processItems(
stronglyConnectedComponents,
this::processStronglyConnectedComponent,
appView.options().getThreadingModule(),
executorService);
return this;
}
private void processStronglyConnectedComponent(Set<DexProgramClass> stronglyConnectedComponent) {
// Perform a top-down traversal over the class hierarchy.
new AccessModifierTraversal(
appView,
immediateSubtypingInfo,
this,
AccessModifierNamingState.createInitialNamingState(
appView, stronglyConnectedComponent, nonProgramMethodsCollection))
.run(ListUtils.sort(stronglyConnectedComponent, Comparator.comparing(DexClass::getType)));
}
private void installLens(ExecutorService executorService, Timing timing)
throws ExecutionException {
if (!lensBuilder.isEmpty()) {
appView.rewriteWithLens(lensBuilder.build(appView), executorService, timing);
}
}
// Publicizing of classes and members.
void processClass(
DexProgramClass clazz,
AccessModifierNamingState namingState,
BottomUpTraversalState traversalState) {
publicizeClass(clazz, traversalState);
publicizeFields(clazz, traversalState);
publicizeMethods(clazz, namingState, traversalState);
}
private void publicizeClass(DexProgramClass clazz, BottomUpTraversalState traversalState) {
if (isAccessModificationAllowed(clazz, traversalState) && !clazz.getAccessFlags().isPublic()) {
clazz.getAccessFlags().promoteToPublic();
}
// Update inner class attribute.
// TODO(b/285494837): Carry-over from the legacy access modifier. We should never publicize
// items unconditionally, but account for keep info.
InnerClassAttribute attr = clazz.getInnerClassAttributeForThisClass();
if (attr != null) {
int accessFlags = ((attr.getAccess() | ACC_PUBLIC) & ~ACC_PRIVATE) & ~ACC_PROTECTED;
clazz.replaceInnerClassAttributeForThisClass(
new InnerClassAttribute(
accessFlags, attr.getInner(), attr.getOuter(), attr.getInnerName()));
}
}
private void publicizeFields(DexProgramClass clazz, BottomUpTraversalState traversalState) {
clazz.forEachProgramField(field -> publicizeField(field, traversalState));
}
private void publicizeField(ProgramField field, BottomUpTraversalState traversalState) {
if (isAccessModificationAllowed(field, traversalState) && !field.getAccessFlags().isPublic()) {
field.getAccessFlags().promoteToPublic();
}
}
private void publicizeMethods(
DexProgramClass clazz,
AccessModifierNamingState namingState,
BottomUpTraversalState traversalState) {
// Create a local naming state to keep track of the methods present on the current class.
// Start by reserving the pinned method signatures on the current class.
BiMap<DexMethod, DexMethod> localNamingState = HashBiMap.create();
clazz.forEachProgramMethod(
method -> {
if (!method.getDefinition().isInitializer() && !isRenamingAllowed(method)) {
localNamingState.put(method.getReference(), method.getReference());
}
});
clazz
.getMethodCollection()
.<ProgramMethod>replaceClassAndMethods(
method -> publicizeMethod(method, localNamingState, namingState, traversalState));
}
@SuppressWarnings("ReferenceEquality")
private DexEncodedMethod publicizeMethod(
ProgramMethod method,
BiMap<DexMethod, DexMethod> localNamingState,
AccessModifierNamingState namingState,
BottomUpTraversalState traversalState) {
MethodAccessFlags accessFlags = method.getAccessFlags();
if (accessFlags.isPublic() || !isAccessModificationAllowed(method, traversalState)) {
return commitMethod(method, localNamingState, namingState);
}
if (method.getDefinition().isClassInitializer()) {
return commitMethod(method, localNamingState, namingState);
}
if (method.getDefinition().isInstanceInitializer()
|| (accessFlags.isPackagePrivate()
&& !traversalState.hasIllegalOverrideOfPackagePrivateMethod(method))
|| accessFlags.isProtected()) {
accessFlags.promoteToPublic();
return commitMethod(method, localNamingState, namingState);
}
if (accessFlags.isPrivate()) {
if (isRenamingAllowed(method)) {
accessFlags
.promoteToPublic()
.applyIf(
!method.getHolder().isInterface() && accessFlags.belongsToVirtualPool(),
MethodAccessFlags::promoteToFinal);
return commitMethod(method, localNamingState, namingState);
}
assert localNamingState.containsKey(method.getReference());
assert localNamingState.get(method.getReference()) == method.getReference();
if (namingState.isFree(method.getMethodSignature())) {
accessFlags
.promoteToPublic()
.applyIf(
!method.getHolder().isInterface() && accessFlags.belongsToVirtualPool(),
MethodAccessFlags::promoteToFinal);
namingState.addBlockedMethodSignature(method.getMethodSignature());
}
return commitMethod(method, method.getReference());
}
// TODO(b/279126633): Add support for publicizing package-private methods by renaming.
assert accessFlags.isPackagePrivate();
assert traversalState.hasIllegalOverrideOfPackagePrivateMethod(method);
return commitMethod(method, localNamingState, namingState);
}
@SuppressWarnings("ReferenceEquality")
private DexMethod getAndReserveNewMethodReference(
ProgramMethod method,
BiMap<DexMethod, DexMethod> localNamingState,
AccessModifierNamingState namingState) {
if (method.getDefinition().isInitializer()) {
return method.getReference();
}
if (!isRenamingAllowed(method)) {
assert localNamingState.containsKey(method.getReference());
assert localNamingState.get(method.getReference()) == method.getReference();
assert method.getAccessFlags().isPrivate()
|| method
.getMethodSignature()
.equals(namingState.getReservedSignature(method.getMethodSignature()));
return method.getReference();
}
DexItemFactory dexItemFactory = appView.dexItemFactory();
if (method.getAccessFlags().isPrivate()) {
// Find a fresh method name and reserve it for the current class.
DexMethod newMethodReference =
dexItemFactory.createFreshMethodNameWithoutHolder(
method.getName().toString(),
method.getProto(),
method.getHolderType(),
candidate ->
!localNamingState.containsValue(candidate)
&& namingState.isFree(candidate.getSignature()));
localNamingState.put(method.getReference(), newMethodReference);
return newMethodReference;
}
// Check if a mapping already exists for this method signature.
if (!method.getAccessFlags().isPromotedFromPrivateToPublic()) {
DexMethodSignature reservedSignature =
namingState.getReservedSignature(method.getMethodSignature());
if (reservedSignature != null) {
return reservedSignature.withHolder(method, appView.dexItemFactory());
}
}
// Find a fresh method name and block/reserve it globally.
DexMethod newMethodReference =
dexItemFactory.createFreshMethodNameWithoutHolder(
method.getName().toString(),
method.getProto(),
method.getHolderType(),
candidate ->
!localNamingState.containsValue(candidate)
&& namingState.isFree(candidate.getSignature()));
if (method.getAccessFlags().belongsToVirtualPool()) {
if (method.getAccessFlags().isPromotedFromPrivateToPublic()) {
namingState.addBlockedMethodSignature(newMethodReference.getSignature());
} else {
namingState.addRenaming(method.getMethodSignature(), newMethodReference.getSignature());
}
}
return newMethodReference;
}
private boolean isAccessModificationAllowed(
ProgramDefinition definition, BottomUpTraversalState traversalState) {
if (!appView.getKeepInfo(definition).isAccessModificationAllowed(options)) {
if (!options.getAccessModifierOptions().isForceModifyingPackagePrivateAndProtectedMethods()
|| !definition.isMethod()
|| definition.getAccessFlags().isPrivate()) {
return false;
}
}
if (!appView.getKeepInfo(definition).isAccessModificationAllowedForTesting(options)) {
return false;
}
if (isFailedResolutionTarget(definition)) {
return false;
}
return definition.isClass()
|| options.getAccessModifierOptions().canPollutePublicApi()
|| !traversalState.isKeptOrHasKeptSubclass;
}
private boolean isFailedResolutionTarget(ProgramDefinition definition) {
if (definition.isClass()) {
return appView.appInfo().isFailedClassResolutionTarget(definition.asClass().getType());
}
if (definition.isField()) {
return appView.appInfo().isFailedFieldResolutionTarget(definition.asField().getReference());
}
assert definition.isMethod();
return appView.appInfo().isFailedMethodResolutionTarget(definition.asMethod().getReference());
}
private boolean isRenamingAllowed(ProgramMethod method) {
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
return keepInfo.isOptimizationAllowed(options) && keepInfo.isShrinkingAllowed(options);
}
private DexEncodedMethod commitMethod(
ProgramMethod method,
BiMap<DexMethod, DexMethod> localNamingState,
AccessModifierNamingState namingState) {
return commitMethod(
method, getAndReserveNewMethodReference(method, localNamingState, namingState));
}
@SuppressWarnings("ReferenceEquality")
private DexEncodedMethod commitMethod(ProgramMethod method, DexMethod newMethodReference) {
DexProgramClass holder = method.getHolder();
if (newMethodReference != method.getReference()) {
lensBuilder.recordMove(method.getReference(), newMethodReference);
method =
new ProgramMethod(
holder,
method
.getDefinition()
.toTypeSubstitutedMethodAsInlining(newMethodReference, appView.dexItemFactory()));
}
if (method.getAccessFlags().isPromotedFromPrivateToPublic()
&& method.getAccessFlags().belongsToVirtualPool()) {
lensBuilder.addPublicizedPrivateVirtualMethod(method.getHolder(), newMethodReference);
method.getDefinition().setLibraryMethodOverride(OptionalBool.FALSE);
}
return method.getDefinition();
}
}