blob: 895b8c5c134b1379b8085e80c324f961069fbf71 [file] [log] [blame]
// Copyright (c) 2019, 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.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.naming.Minifier.MinificationClassNamingStrategy;
import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
import com.android.tools.r8.naming.Minifier.MinifierMemberNamingStrategy;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* The ProguardMapMinifier will assign names to classes and members following the initial naming
* seed given by the mapping files.
*
* <p>First the object hierarchy is traversed maintaining a collection of all program classes and
* classes that needs to be renamed in {@link #mappedClasses}. For each level we keep track of all
* renamed members and propagate all non-private items to descendants. This is necessary to ensure
* that virtual methods are renamed when there are "gaps" in the hierarchy. We keep track of all
* namings such that future renaming of non-private members will not collide or fail with an error.
*
* <p>Second, we compute desugared default interface methods and companion classes to ensure these
* can be referred to by clients.
*
* <p>Third, we traverse all reachable interfaces for class mappings and add them to our tracking
* maps. Otherwise, the minification follows the ordinary minification.
*/
public class ProguardMapMinifier {
private final AppView<AppInfoWithLiveness> appView;
private final SeedMapper seedMapper;
private final Set<DexCallSite> desugaredCallSites;
private final BiMap<DexType, DexString> mappedNames = HashBiMap.create();
private final List<DexClass> mappedClasses = new ArrayList<>();
private final Map<DexReference, MemberNaming> memberNames = new IdentityHashMap<>();
private final Map<DexType, DexString> syntheticCompanionClasses = new IdentityHashMap<>();
private final Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames =
new IdentityHashMap<>();
private final Map<DexMethod, DexString> additionalMethodNamings = new IdentityHashMap<>();
private final Map<DexField, DexString> additionalFieldNamings = new IdentityHashMap<>();
public ProguardMapMinifier(
AppView<AppInfoWithLiveness> appView,
SeedMapper seedMapper,
Set<DexCallSite> desugaredCallSites) {
this.appView = appView;
this.seedMapper = seedMapper;
this.desugaredCallSites = desugaredCallSites;
}
public NamingLens run(Timing timing) {
timing.begin("MappingClasses");
computeMapping(appView.dexItemFactory().objectType, new ArrayDeque<>());
timing.end();
timing.begin("MappingDefaultInterfaceMethods");
computeDefaultInterfaceMethodMethods();
timing.end();
timing.begin("ComputeInterfaces");
// We have to compute interfaces
Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
for (DexClass dexClass : appView.appInfo().computeReachableInterfaces(desugaredCallSites)) {
ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(dexClass.type);
if (classNaming != null) {
DexString mappedName = appView.dexItemFactory().createString(classNaming.renamedName);
checkAndAddMappedNames(dexClass.type, mappedName, classNaming.position);
}
mappedClasses.add(dexClass);
interfaces.add(dexClass);
}
timing.end();
appView.options().reporter.failIfPendingErrors();
// To keep the order deterministic, we sort the classes by their type, which is a unique key.
mappedClasses.sort((a, b) -> a.type.slowCompareTo(b.type));
timing.begin("MinifyClasses");
ClassNameMinifier classNameMinifier =
new ClassNameMinifier(
appView,
new ApplyMappingClassNamingStrategy(appView, mappedNames),
// The package naming strategy will actually not be used since all classes and methods
// will be output with identity name if not found in mapping. However, there is a check
// in the ClassNameMinifier that the strategy should produce a "fresh" name so we just
// use the existing strategy.
new MinificationPackageNamingStrategy(appView),
mappedClasses);
ClassRenaming classRenaming =
classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
timing.end();
ApplyMappingMemberNamingStrategy nameStrategy =
new ApplyMappingMemberNamingStrategy(appView, memberNames);
timing.begin("MinifyMethods");
MethodRenaming methodRenaming =
new MethodNameMinifier(appView, nameStrategy)
.computeRenaming(interfaces, desugaredCallSites, timing);
// Amend the method renamings with the default interface methods.
methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
methodRenaming.renaming.putAll(additionalMethodNamings);
timing.end();
timing.begin("MinifyFields");
FieldRenaming fieldRenaming =
new FieldNameMinifier(appView, nameStrategy).computeRenaming(interfaces, timing);
fieldRenaming.renaming.putAll(additionalFieldNamings);
timing.end();
appView.options().reporter.failIfPendingErrors();
NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
timing.begin("MinifyIdentifiers");
new IdentifierMinifier(appView, lens).run();
timing.end();
return lens;
}
private void computeMapping(DexType type, Deque<Map<DexReference, MemberNaming>> buildUpNames) {
ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
DexClass dexClass = appView.definitionFor(type);
// Keep track of classes that needs to get renamed.
if (dexClass != null && (classNaming != null || dexClass.isProgramClass())) {
mappedClasses.add(dexClass);
}
Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
if (classNaming != null) {
// TODO(b/133091438) assert that !dexClass.isLibaryClass();
DexString mappedName = appView.dexItemFactory().createString(classNaming.renamedName);
checkAndAddMappedNames(type, mappedName, classNaming.position);
classNaming.forAllMethodNaming(
memberNaming -> {
Signature signature = memberNaming.getOriginalSignature();
assert !signature.isQualified();
DexMethod originalMethod =
((MethodSignature) signature).toDexMethod(appView.dexItemFactory(), type);
assert !memberNames.containsKey(originalMethod);
memberNames.put(originalMethod, memberNaming);
DexEncodedMethod encodedMethod = appView.definitionFor(originalMethod);
if (encodedMethod == null || !encodedMethod.accessFlags.isPrivate()) {
nonPrivateMembers.put(originalMethod, memberNaming);
}
});
classNaming.forAllFieldNaming(
memberNaming -> {
Signature signature = memberNaming.getOriginalSignature();
assert !signature.isQualified();
DexField originalField =
((FieldSignature) signature).toDexField(appView.dexItemFactory(), type);
assert !memberNames.containsKey(originalField);
memberNames.put(originalField, memberNaming);
DexEncodedField encodedField = appView.definitionFor(originalField);
if (encodedField == null || !encodedField.accessFlags.isPrivate()) {
nonPrivateMembers.put(originalField, memberNaming);
}
});
} else {
// We have to ensure we do not rename to an existing member, that cannot be renamed.
if (dexClass == null || !appView.options().isMinifying()) {
checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
} else if (appView.options().isMinifying()
&& appView.rootSet().mayNotBeMinified(type, appView)) {
checkAndAddMappedNames(type, type.descriptor, Position.UNKNOWN);
}
}
for (Map<DexReference, MemberNaming> parentMembers : buildUpNames) {
for (DexReference key : parentMembers.keySet()) {
if (key.isDexMethod()) {
DexMethod parentReference = key.asDexMethod();
DexMethod parentReferenceOnCurrentType =
appView
.dexItemFactory()
.createMethod(type, parentReference.proto, parentReference.name);
addMemberNaming(
key, parentReferenceOnCurrentType, parentMembers, additionalMethodNamings);
} else {
DexField parentReference = key.asDexField();
DexField parentReferenceOnCurrentType =
appView
.dexItemFactory()
.createField(type, parentReference.type, parentReference.name);
addMemberNaming(key, parentReferenceOnCurrentType, parentMembers, additionalFieldNamings);
}
}
}
if (nonPrivateMembers.size() > 0) {
buildUpNames.addLast(nonPrivateMembers);
appView
.appInfo()
.forAllExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
buildUpNames.removeLast();
} else {
appView
.appInfo()
.forAllExtendsSubtypes(type, subType -> computeMapping(subType, buildUpNames));
}
}
private <T extends DexReference> void addMemberNaming(
DexReference key,
T member,
Map<DexReference, MemberNaming> parentMembers,
Map<T, DexString> additionalMemberNamings) {
// We might have overridden a naming in the direct class namings above.
if (!memberNames.containsKey(member)) {
DexString renamedName =
appView.dexItemFactory().createString(parentMembers.get(key).getRenamedName());
memberNames.put(member, parentMembers.get(key));
additionalMemberNamings.put(member, renamedName);
}
}
private void checkAndAddMappedNames(DexType type, DexString mappedName, Position position) {
if (mappedNames.inverse().containsKey(mappedName)) {
appView
.options()
.reporter
.error(
ApplyMappingError.mapToExistingClass(
type.toString(), mappedName.toString(), position));
} else {
mappedNames.put(type, mappedName);
}
}
private void computeDefaultInterfaceMethodMethods() {
for (String key : seedMapper.getKeyset()) {
ClassNamingForMapApplier classNaming = seedMapper.getMapping(key);
DexType type =
appView.dexItemFactory().lookupType(appView.dexItemFactory().createString(key));
if (type == null) {
// The map contains additional mapping of classes compared to what we have seen. This should
// have no effect.
continue;
}
DexClass dexClass = appView.definitionFor(type);
if (dexClass == null) {
computeDefaultInterfaceMethodMappingsForType(
type,
classNaming,
syntheticCompanionClasses,
defaultInterfaceMethodImplementationNames);
continue;
}
}
}
private void computeDefaultInterfaceMethodMappingsForType(
DexType type,
ClassNamingForMapApplier classNaming,
Map<DexType, DexString> syntheticCompanionClasses,
Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames) {
// If the class does not resolve, then check if it is a companion class for an interface on
// the class path.
if (!InterfaceMethodRewriter.isCompanionClassType(type)) {
return;
}
DexClass interfaceType =
appView.definitionFor(
InterfaceMethodRewriter.getInterfaceClassType(type, appView.dexItemFactory()));
if (interfaceType == null || !interfaceType.isClasspathClass()) {
return;
}
syntheticCompanionClasses.put(
type, appView.dexItemFactory().createString(classNaming.renamedName));
for (List<MemberNaming> namings : classNaming.getQualifiedMethodMembers().values()) {
// If the qualified name has been mapped to multiple names we can't compute a mapping (and it
// should not be possible that this is a default interface method in that case.)
if (namings.size() != 1) {
continue;
}
MemberNaming naming = namings.get(0);
MethodSignature signature = (MethodSignature) naming.getOriginalSignature();
if (signature.name.startsWith(interfaceType.type.toSourceString())) {
DexMethod defaultMethod =
InterfaceMethodRewriter.defaultAsMethodOfCompanionClass(
signature.toUnqualified().toDexMethod(appView.dexItemFactory(), interfaceType.type),
appView.dexItemFactory());
assert defaultMethod.holder == type;
defaultInterfaceMethodImplementationNames.put(
defaultMethod, appView.dexItemFactory().createString(naming.getRenamedName()));
}
}
}
static class ApplyMappingClassNamingStrategy extends MinificationClassNamingStrategy {
private final Map<DexType, DexString> mappings;
private final boolean isMinifying;
ApplyMappingClassNamingStrategy(AppView<?> appView, Map<DexType, DexString> mappings) {
super(appView);
this.mappings = mappings;
this.isMinifying = appView.options().isMinifying();
}
@Override
public DexString next(DexType type, char[] packagePrefix, InternalNamingState state) {
DexString nextName = mappings.get(type);
if (nextName != null) {
return nextName;
}
assert !(isMinifying && noObfuscation(type));
return isMinifying ? super.next(type, packagePrefix, state) : type.descriptor;
}
@Override
public boolean noObfuscation(DexType type) {
if (mappings.containsKey(type)) {
return false;
}
DexClass dexClass = appView.definitionFor(type);
if (dexClass == null || dexClass.isNotProgramClass()) {
return true;
}
return super.noObfuscation(type);
}
}
static class ApplyMappingMemberNamingStrategy extends MinifierMemberNamingStrategy {
private final Map<DexReference, MemberNaming> mappedNames;
private final DexItemFactory factory;
private final Reporter reporter;
public ApplyMappingMemberNamingStrategy(
AppView<?> appView, Map<DexReference, MemberNaming> mappedNames) {
super(appView);
this.mappedNames = mappedNames;
this.factory = appView.dexItemFactory();
this.reporter = appView.options().reporter;
}
@Override
public DexString next(DexMethod method, InternalNamingState internalState) {
assert !mappedNames.containsKey(method);
return canMinify(method, method.holder) ? super.next(method, internalState) : method.name;
}
@Override
public DexString next(DexField field, InternalNamingState internalState) {
assert !mappedNames.containsKey(field);
return canMinify(field, field.holder) ? super.next(field, internalState) : field.name;
}
private boolean canMinify(DexReference reference, DexType type) {
if (!appView.options().isMinifying()) {
return false;
}
DexClass dexClass = appView.definitionFor(type);
if (dexClass == null || dexClass.isNotProgramClass()) {
return false;
}
return appView.rootSet().mayBeMinified(reference, appView);
}
@Override
public DexString getReservedNameOrDefault(
DexEncodedMethod method, DexClass holder, DexString nullValue) {
if (mappedNames.containsKey(method.method)) {
return factory.createString(mappedNames.get(method.method).getRenamedName());
}
return nullValue;
}
@Override
public DexString getReservedNameOrDefault(
DexEncodedField field, DexClass holder, DexString nullValue) {
if (mappedNames.containsKey(field.field)) {
return factory.createString(mappedNames.get(field.field).getRenamedName());
}
return nullValue;
}
@Override
public boolean allowMemberRenaming(DexClass holder) {
return true;
}
@Override
public void reportReservationError(DexReference source, DexString name) {
MemberNaming memberNaming = mappedNames.get(source);
assert source.isDexMethod() || source.isDexField();
ApplyMappingError applyMappingError =
ApplyMappingError.mapToExistingMember(
source.toSourceString(),
name.toString(),
memberNaming == null ? Position.UNKNOWN : memberNaming.position);
reporter.error(applyMappingError);
}
}
}