blob: 618e1f592ed8f5170e86bde3cdce58ef3a02ec02 [file] [log] [blame]
// Copyright (c) 2017, 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.naming;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
class FieldNameMinifier {
private final AppView<AppInfoWithLiveness> appView;
private final SubtypingInfo subtypingInfo;
private final Map<DexField, DexString> renaming = new IdentityHashMap<>();
private final Map<DexType, ReservedFieldNamingState> reservedNamingStates =
new IdentityHashMap<>();
private final MemberNamingStrategy strategy;
private final Map<DexType, DexType> frontiers = new IdentityHashMap<>();
private final Map<DexType, Set<ReservedFieldNamingState>> frontierStatesForInterfaces =
new IdentityHashMap<>();
FieldNameMinifier(
AppView<AppInfoWithLiveness> appView,
SubtypingInfo subtypingInfo,
MemberNamingStrategy strategy) {
this.appView = appView;
this.subtypingInfo = subtypingInfo;
this.strategy = strategy;
}
FieldRenaming computeRenaming(Collection<DexClass> interfaces, Timing timing) {
// Reserve names in all classes first. We do this in subtyping order so we do not
// shadow a reserved field in subclasses. While there is no concept of virtual field
// dispatch in Java, field resolution still traverses the super type chain and external
// code might use a subtype to reference the field.
timing.begin("reserve-names");
reserveFieldNames();
timing.end();
// Rename the definitions.
timing.begin("rename-definitions");
renameFieldsInInterfaces(interfaces);
renameFieldsInClasses();
renameFieldsInUnrelatedClasspathClasses();
timing.end();
// Rename the references that are not rebound to definitions for some reasons.
timing.begin("rename-references");
renameNonReboundReferences();
timing.end();
return new FieldRenaming(renaming);
}
static class FieldRenaming {
final Map<DexField, DexString> renaming;
private FieldRenaming(Map<DexField, DexString> renaming) {
this.renaming = renaming;
}
public static FieldRenaming empty() {
return new FieldRenaming(ImmutableMap.of());
}
}
private ReservedFieldNamingState getReservedFieldNamingState(DexType type) {
return reservedNamingStates.get(type);
}
private ReservedFieldNamingState getOrCreateReservedFieldNamingState(DexType type) {
return reservedNamingStates.computeIfAbsent(
type, ignore -> new ReservedFieldNamingState(appView));
}
private void reserveFieldNames() {
// Build up all reservations in the class hierarchy such that all reserved names are placed
// at the boundary between a library class and a program class - referred to as the frontier.
// Special handling is done for interfaces by always considering them to be roots. When
// traversing down the hierarchy we built up a map from interface to reservation states:
// - when we reach a frontier find all directly and indirectly implemented interfaces and
// add the current reservation state
// - when we see a program class that implements a direct super type that is an interface also
// add the current reservation state. Note that even though we do not visit super interfaces
// here, this still works because a super interface will be in the same partition.
// For an in depth description see MethodNameMinifier.
TopDownClassHierarchyTraversal.forAllClasses(appView)
.visit(
appView.appInfo().classes(),
clazz -> {
DexType frontier =
clazz.superType == null
? appView.dexItemFactory().objectType
: frontiers.getOrDefault(clazz.superType, clazz.type);
// If frontier != clazz.type we have seen a program class that is on the boundary.
// Otherwise, if we are visiting a program class then that is the frontier.
if (frontier != clazz.type || clazz.isProgramClass()) {
DexType existingValue = frontiers.put(clazz.type, frontier);
assert existingValue == null;
}
ReservedFieldNamingState reservationState =
getOrCreateReservedFieldNamingState(frontier);
for (DexEncodedField field : clazz.fields()) {
DexString reservedName = strategy.getReservedName(field, clazz);
if (reservedName != null) {
reservationState.markReserved(
reservedName, field.getReference().name, field.getReference().type);
// TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
if (reservedName != field.getReference().name) {
renaming.put(field.getReference(), reservedName);
}
}
}
if (clazz.isInterface()) {
frontierStatesForInterfaces.put(
clazz.type, SetUtils.newIdentityHashSet(reservationState));
}
// Include all reservations from super frontier states. This will propagate reserved
// names for interfaces down to implementing subtypes.
for (DexType superType : clazz.allImmediateSupertypes()) {
// No need to visit object since there are no fields there.
if (superType != appView.dexItemFactory().objectType) {
ReservedFieldNamingState superReservationState =
getOrCreateReservedFieldNamingState(
frontiers.getOrDefault(superType, superType));
if (superReservationState != reservationState) {
reservationState.includeReservations(superReservationState);
}
if (clazz.isProgramClass()) {
DexClass superClass = appView.definitionFor(superType, clazz.asProgramClass());
if (superClass != null && superClass.isInterface()) {
frontierStatesForInterfaces.get(superType).add(reservationState);
}
}
}
}
if (frontier == clazz.type && clazz.isProgramClass()) {
patchUpAllIndirectlyImplementingInterfacesFromLibraryAndClassPath(
clazz.asProgramClass(), reservationState);
}
});
}
private void patchUpAllIndirectlyImplementingInterfacesFromLibraryAndClassPath(
DexProgramClass clazz, ReservedFieldNamingState reservationState) {
appView
.appInfo()
.traverseSuperTypes(
clazz,
(superType, superClass, isInterface) -> {
if (isInterface && superClass.isNotProgramClass()) {
Set<ReservedFieldNamingState> reservedNamingState =
frontierStatesForInterfaces.get(superType);
if (reservedNamingState != null) {
reservedNamingState.add(reservationState);
}
}
return TraversalContinuation.doContinue();
});
}
private void renameFieldsInClasses() {
Map<DexType, FieldNamingState> states = new IdentityHashMap<>();
TopDownClassHierarchyTraversal.forAllClasses(appView)
.excludeInterfaces()
.visit(
appView.appInfo().classes(),
clazz -> {
assert !clazz.isInterface();
FieldNamingState parentState =
clazz.superType == null
? new FieldNamingState(appView, strategy)
: states
.computeIfAbsent(
clazz.superType, key -> new FieldNamingState(appView, strategy))
.clone();
ReservedFieldNamingState reservedNames =
getReservedFieldNamingState(frontiers.getOrDefault(clazz.type, clazz.type));
FieldNamingState state = parentState.createChildState(reservedNames);
if (clazz.isProgramClass()) {
clazz.asProgramClass().forEachProgramField(field -> renameField(field, state));
}
assert !states.containsKey(clazz.type);
states.put(clazz.type, state);
});
}
private void renameFieldsInUnrelatedClasspathClasses() {
if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
appView
.appInfo()
.forEachReferencedClasspathClass(
clazz -> {
for (DexEncodedField field : clazz.fields()) {
DexString reservedName = strategy.getReservedName(field, clazz);
if (reservedName != null && reservedName != field.getReference().name) {
renaming.put(field.getReference(), reservedName);
}
}
});
}
}
private void renameFieldsInInterfaces(Collection<DexClass> interfaces) {
// TODO(b/213415674): Only consider interfaces in the hierarchy of classes.
InterfacePartitioning partitioning = new InterfacePartitioning(this);
for (Set<DexClass> partition : partitioning.sortedPartitions(interfaces)) {
renameFieldsInInterfacePartition(partition);
}
}
private void renameFieldsInInterfacePartition(Set<DexClass> partition) {
ReservedFieldNamingState namesToBeReservedInImplementsSubclasses =
new ReservedFieldNamingState(appView);
ReservedFieldNamingState reservedNamesInPartition = new ReservedFieldNamingState(appView);
for (DexClass clazz : partition) {
ReservedFieldNamingState reservedNamesInInterface =
getReservedFieldNamingState(frontiers.getOrDefault(clazz.type, clazz.type));
if (reservedNamesInInterface != null) {
reservedNamesInPartition.includeReservations(reservedNamesInInterface);
Set<ReservedFieldNamingState> reservedFieldNamingStates =
frontierStatesForInterfaces.get(clazz.type);
assert reservedFieldNamingStates != null;
reservedFieldNamingStates.forEach(
reservedStates -> {
reservedNamesInPartition.includeReservations(reservedStates);
reservedStates.setInterfaceMinificationState(namesToBeReservedInImplementsSubclasses);
});
}
}
FieldNamingState state = new FieldNamingState(appView, strategy, reservedNamesInPartition);
for (DexClass clazz : partition) {
if (clazz.isProgramClass()) {
assert clazz.isInterface();
clazz
.asProgramClass()
.forEachProgramField(
field -> {
DexString newName = renameField(field, state);
namesToBeReservedInImplementsSubclasses.markReserved(
newName, field.getReference().name, field.getReference().type);
});
}
}
}
private DexString renameField(ProgramField field, FieldNamingState state) {
DexString newName = state.getOrCreateNameFor(field);
if (newName != field.getReference().name) {
renaming.put(field.getReference(), newName);
}
return newName;
}
private void renameNonReboundReferences() {
FieldAccessInfoCollection<?> fieldAccessInfoCollection =
appView.appInfo().getFieldAccessInfoCollection();
fieldAccessInfoCollection.forEach(this::renameNonReboundAccessesToField);
}
private void renameNonReboundAccessesToField(FieldAccessInfo fieldAccessInfo) {
fieldAccessInfo.forEachIndirectAccess(this::renameNonReboundAccessToField);
}
private void renameNonReboundAccessToField(DexField field) {
// If the given field reference is a non-rebound reference to a program field, then assign the
// same name as the resolved field.
if (renaming.containsKey(field)) {
return;
}
DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(field));
if (holder == null) {
return;
}
DexEncodedField definition = appView.appInfo().resolveFieldOn(holder, field).getResolvedField();
if (definition != null
&& definition.getReference() != field
&& renaming.containsKey(definition.getReference())) {
renaming.put(field, renaming.get(definition.getReference()));
}
}
static class InterfacePartitioning {
private final FieldNameMinifier minifier;
private final AppView<AppInfoWithLiveness> appView;
private final Set<DexType> visited = Sets.newIdentityHashSet();
InterfacePartitioning(FieldNameMinifier minifier) {
this.minifier = minifier;
appView = minifier.appView;
}
private List<Set<DexClass>> sortedPartitions(Collection<DexClass> interfaces) {
List<Set<DexClass>> partitions = new ArrayList<>();
for (DexClass clazz : interfaces) {
if (clazz != null && visited.add(clazz.type)) {
Set<DexClass> partition = buildSortedPartition(clazz);
assert !partition.isEmpty();
assert partition.stream().allMatch(DexClass::isInterface);
assert partition.stream().map(DexClass::getType).allMatch(visited::contains);
partitions.add(partition);
}
}
return partitions;
}
private Set<DexClass> buildSortedPartition(DexClass src) {
Set<DexClass> partition = new TreeSet<>(Comparator.comparing(DexClass::getType));
Deque<DexType> worklist = new ArrayDeque<>();
worklist.add(src.type);
while (!worklist.isEmpty()) {
DexType type = worklist.removeFirst();
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
continue;
}
for (DexType superinterface : clazz.interfaces.values) {
if (visited.add(superinterface)) {
worklist.add(superinterface);
}
}
if (clazz.isInterface()) {
partition.add(clazz);
for (DexType subtype : minifier.subtypingInfo.allImmediateSubtypes(type)) {
if (visited.add(subtype)) {
worklist.add(subtype);
}
}
} else if (clazz.type != appView.dexItemFactory().objectType) {
if (visited.add(clazz.superType)) {
worklist.add(clazz.superType);
}
for (DexType subclass : minifier.subtypingInfo.allImmediateExtendsSubtypes(type)) {
if (visited.add(subclass)) {
worklist.add(subclass);
}
}
}
}
return partition;
}
}
}