blob: 9f70f37ebe20a6d0c572e732b4dbef4c8ecbeac2 [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 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.DexString;
import com.android.tools.r8.graph.DexType;
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.io.PrintStream;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
class FieldNameMinifier extends MemberNameMinifier<DexField, DexType> {
FieldNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) {
super(appView, strategy);
}
@Override
Function<DexType, ?> getKeyTransform() {
if (overloadAggressively) {
// Use the type as the key, hence reuse names per type.
return Function.identity();
} else {
// Always use the same key, hence do not reuse names per type.
return a -> Void.class;
}
}
FieldRenaming computeRenaming(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-classes");
reserveNamesInSubtypes(appView.dexItemFactory().objectType, globalState);
timing.end();
// Next, reserve field names in interfaces. These should only be static.
timing.begin("reserve-interfaces");
appView
.appInfo()
.forAllInterfaces(
appView.dexItemFactory(), iface -> reserveNamesInSubtypes(iface, globalState));
timing.end();
// Rename the definitions.
timing.begin("rename-definitions");
renameFieldsInClasses();
renameFieldsInInterfaces();
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 void reserveNamesInSubtypes(DexType type, NamingState<DexType, ?> state) {
DexClass holder = appView.definitionFor(type);
if (holder == null) {
return;
}
// If there is a mapping file, it can be that fields in libraries should be renamed and
// therefore not reserved.
NamingState<DexType, ?> newState = computeStateIfAbsent(type, t -> state.createChild());
holder.forEachField(
field -> reserveFieldName(field, newState, alwaysReserveMemberNames(holder)));
appView
.appInfo()
.forAllExtendsSubtypes(type, subtype -> reserveNamesInSubtypes(subtype, newState));
}
private void reserveFieldName(
DexEncodedField encodedField, NamingState<DexType, ?> state, boolean alwaysReserve) {
DexField field = encodedField.field;
if (alwaysReserve || appView.rootSet().noObfuscation.contains(field)) {
state.reserveName(field.name, field.type);
}
}
private void renameFieldsInClasses() {
renameFieldsInSubclasses(appView.dexItemFactory().objectType, null);
}
private void renameFieldsInSubclasses(DexType type, DexType parent) {
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
return;
}
assert !clazz.isInterface();
assert clazz.superType == parent;
NamingState<DexType, ?> state = minifierState.getState(clazz.type);
assert state != null;
for (DexEncodedField field : clazz.fields()) {
renameField(field, state);
}
for (DexType subclass : appView.appInfo().allExtendsSubtypes(type)) {
renameFieldsInSubclasses(subclass, type);
}
}
private void renameFieldsInInterfaces() {
for (DexType interfaceType : appView.appInfo().allInterfaces(appView.dexItemFactory())) {
renameFieldsInInterface(interfaceType);
}
}
private void renameFieldsInInterface(DexType type) {
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
return;
}
assert clazz.isInterface();
NamingState<DexType, ?> state = minifierState.getState(clazz.type);
assert state != null;
for (DexEncodedField field : clazz.fields()) {
renameField(field, state);
}
}
private void renameField(DexEncodedField encodedField, NamingState<DexType, ?> state) {
DexField field = encodedField.field;
Set<String> loggingFilter = appView.options().extensiveFieldMinifierLoggingFilter;
if (!loggingFilter.isEmpty()) {
if (loggingFilter.contains(field.toSourceString())) {
print(field, state, System.out);
}
}
if (!state.isReserved(field.name, field.type)) {
renaming.put(
field, state.assignNewNameFor(field, field.name, field.type, useUniqueMemberNames));
}
}
private void renameNonReboundReferences() {
// TODO(b/123068484): Collect non-rebound references instead of visiting all references.
AppInfoWithLiveness appInfo = appView.appInfo();
Sets.union(
Sets.union(appInfo.staticFieldReads.keySet(), appInfo.staticFieldWrites.keySet()),
Sets.union(appInfo.instanceFieldReads.keySet(), appInfo.instanceFieldWrites.keySet()))
.forEach(this::renameNonReboundReference);
}
private void renameNonReboundReference(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));
}
}
private void print(DexField field, NamingState<DexType, ?> state, PrintStream out) {
out.println("--------------------------------------------------------------------------------");
out.println("FieldNameMinifier(`" + field.toSourceString() + "`)");
out.println("--------------------------------------------------------------------------------");
state.printState(field.type, minifierState::getStateKey, "", out);
out.println();
}
}