| // 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 com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal; |
| 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.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.TopDownClassHierarchyTraversal; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.Timing; |
| 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.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 Map<DexField, DexString> renaming = new IdentityHashMap<>(); |
| private Map<DexType, ReservedFieldNamingState> reservedNamingStates = new IdentityHashMap<>(); |
| private final MemberNamingStrategy strategy; |
| |
| FieldNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) { |
| this.appView = appView; |
| 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); |
| propagateReservedFieldNamesUpwards(); |
| renameFieldsInClasses(); |
| 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() { |
| // Reserve all field names that need to be reserved. |
| for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) { |
| ReservedFieldNamingState reservedNames = null; |
| for (DexEncodedField field : clazz.fields()) { |
| DexString reservedName = strategy.getReservedName(field, clazz); |
| if (reservedName != null) { |
| if (reservedNames == null) { |
| reservedNames = getOrCreateReservedFieldNamingState(clazz.type); |
| } |
| reservedNames.markReservedDirectly(reservedName, field.field.name, field.field.type); |
| if (reservedName != field.field.name) { |
| renaming.put(field.field, reservedName); |
| } |
| } |
| } |
| |
| // For interfaces, propagate reserved names to all implementing classes. |
| if (clazz.isInterface() && reservedNames != null) { |
| for (DexType implementationType : |
| appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) { |
| DexClass implementation = appView.definitionFor(implementationType); |
| if (implementation != null) { |
| assert !implementation.isInterface(); |
| getOrCreateReservedFieldNamingState(implementationType) |
| .includeReservations(reservedNames); |
| } |
| } |
| } |
| } |
| |
| propagateReservedFieldNamesUpwards(); |
| } |
| |
| private void propagateReservedFieldNamesUpwards() { |
| BottomUpClassHierarchyTraversal.forProgramClasses(appView) |
| .visit( |
| appView.appInfo().classes(), |
| clazz -> { |
| ReservedFieldNamingState reservedNames = getReservedFieldNamingState(clazz.type); |
| if (reservedNames != null) { |
| for (DexType supertype : clazz.allImmediateSupertypes()) { |
| if (supertype.isProgramType(appView)) { |
| getOrCreateReservedFieldNamingState(supertype) |
| .includeReservationsFromBelow(reservedNames); |
| } |
| } |
| } |
| }); |
| } |
| |
| 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 = |
| getOrCreateReservedFieldNamingState(clazz.type); |
| FieldNamingState state = parentState.createChildState(reservedNames); |
| if (clazz.isProgramClass()) { |
| for (DexEncodedField field : clazz.fields()) { |
| renameField(field, state); |
| } |
| } |
| |
| assert !states.containsKey(clazz.type); |
| states.put(clazz.type, state); |
| }); |
| } |
| |
| private void renameFieldsInInterfaces(Collection<DexClass> interfaces) { |
| InterfacePartitioning partioning = new InterfacePartitioning(appView); |
| for (Set<DexClass> partition : partioning.sortedPartitions(interfaces)) { |
| renameFieldsInInterfacePartition(partition); |
| } |
| } |
| |
| private void renameFieldsInInterfacePartition(Set<DexClass> partition) { |
| ReservedFieldNamingState reservedNamesInPartition = new ReservedFieldNamingState(appView); |
| for (DexClass clazz : partition) { |
| ReservedFieldNamingState reservedNamesInInterface = getReservedFieldNamingState(clazz.type); |
| if (reservedNamesInInterface != null) { |
| reservedNamesInPartition.includeReservations(reservedNamesInInterface); |
| reservedNamesInPartition.includeReservationsFromBelow(reservedNamesInInterface); |
| } |
| } |
| |
| ReservedFieldNamingState namesToBeReservedInImplementsSubclasses = |
| new ReservedFieldNamingState(appView); |
| |
| FieldNamingState state = new FieldNamingState(appView, strategy, reservedNamesInPartition); |
| for (DexClass clazz : partition) { |
| if (clazz.isProgramClass()) { |
| assert clazz.isInterface(); |
| for (DexEncodedField field : clazz.fields()) { |
| DexString newName = renameField(field, state); |
| namesToBeReservedInImplementsSubclasses.markReservedDirectly( |
| newName, field.field.name, field.field.type); |
| } |
| } |
| } |
| |
| Set<DexType> visited = Sets.newIdentityHashSet(); |
| for (DexClass clazz : partition) { |
| for (DexType implementationType : |
| appView.appInfo().allImmediateImplementsSubtypes(clazz.type)) { |
| if (!visited.add(implementationType)) { |
| continue; |
| } |
| |
| DexClass implementation = appView.definitionFor(implementationType); |
| if (implementation != null) { |
| getOrCreateReservedFieldNamingState(implementationType) |
| .includeReservations(namesToBeReservedInImplementsSubclasses); |
| } |
| } |
| } |
| } |
| |
| private DexString renameField(DexEncodedField encodedField, FieldNamingState state) { |
| DexField field = encodedField.field; |
| DexString newName = state.getOrCreateNameFor(field); |
| if (newName != field.name) { |
| renaming.put(field, 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) { |
| // Already renamed |
| if (renaming.containsKey(field)) { |
| return; |
| } |
| DexEncodedField definition = appView.definitionFor(field); |
| if (definition != null) { |
| assert definition.field == field; |
| return; |
| } |
| // Now, `field` is reference. Find its definition and check if it's renamed. |
| DexClass holder = appView.definitionFor(field.holder); |
| // We don't care pruned types or library classes. |
| if (holder == null || holder.isNotProgramClass()) { |
| return; |
| } |
| definition = appView.appInfo().resolveField(field); |
| if (definition == null) { |
| // The program is already broken in the sense that it has an unresolvable field reference. |
| // Leave it as-is. |
| return; |
| } |
| assert definition.field != field; |
| assert definition.field.holder != field.holder; |
| // If the definition is renamed, |
| if (renaming.containsKey(definition.field)) { |
| // Assign the same, renamed name as the definition to the reference. |
| renaming.put(field, renaming.get(definition.field)); |
| } |
| } |
| |
| static class InterfacePartitioning { |
| |
| private final AppView<AppInfoWithLiveness> appView; |
| private final Set<DexType> visited = Sets.newIdentityHashSet(); |
| |
| InterfacePartitioning(AppView<AppInfoWithLiveness> appView) { |
| this.appView = 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<>((x, y) -> x.type.slowCompareTo(y.type)); |
| |
| 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 : appView.appInfo().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 : appView.appInfo().allImmediateExtendsSubtypes(type)) { |
| if (visited.add(subclass)) { |
| worklist.add(subclass); |
| } |
| } |
| } |
| } |
| |
| return partition; |
| } |
| } |
| } |