blob: c44d311c46d15b6d4a41f6a90f81ac5a2a700503 [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.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.util.IdentityHashMap;
import java.util.Map;
class MinifiedRenaming extends NonIdentityNamingLens {
final AppView<? extends AppInfoWithClassHierarchy> appView;
private final Map<String, String> packageRenaming;
private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
MinifiedRenaming(
AppView<? extends AppInfoWithClassHierarchy> appView,
ClassRenaming classRenaming,
MethodRenaming methodRenaming,
FieldRenaming fieldRenaming) {
super(appView.dexItemFactory(), classRenaming.classRenaming);
this.appView = appView;
this.packageRenaming = classRenaming.packageRenaming;
renaming.putAll(classRenaming.classRenaming);
renaming.putAll(methodRenaming.renaming);
renaming.putAll(fieldRenaming.renaming);
}
@Override
public String lookupPackageName(String packageName) {
return packageRenaming.getOrDefault(packageName, packageName);
}
@Override
protected DexString internalLookupClassDescriptor(DexType type) {
return renaming.getOrDefault(type, type.descriptor);
}
@Override
public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) {
if (attribute.getInnerName() == null) {
return null;
}
DexType innerType = attribute.getInner();
String inner = DescriptorUtils.descriptorToInternalName(innerType.descriptor.toString());
// At this point we assume the input was of the form: <OuterType>$<index><InnerName>
// Find the mapped type and if it remains the same return that, otherwise split at
// either the input separator ($, $$, or anything that starts with $) or $ (that we recover
// while minifying, e.g., empty separator, under bar, etc.).
String innerTypeMapped =
DescriptorUtils.descriptorToInternalName(lookupDescriptor(innerType).toString());
if (inner.equals(innerTypeMapped)) {
return attribute.getInnerName();
}
String separator = DescriptorUtils.computeInnerClassSeparator(
attribute.getOuter(), innerType, attribute.getInnerName());
if (separator == null) {
separator = String.valueOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
}
int index = innerTypeMapped.lastIndexOf(separator);
if (index < 0) {
assert !options.keepInnerClassStructure()
|| options.getProguardConfiguration().hasApplyMappingFile()
: innerType + " -> " + innerTypeMapped;
String descriptor = lookupDescriptor(innerType).toString();
return options.itemFactory.createString(
DescriptorUtils.getUnqualifiedClassNameFromDescriptor(descriptor));
}
return options.itemFactory.createString(innerTypeMapped.substring(index + separator.length()));
}
@Override
public DexString lookupName(DexMethod method) {
return renaming.getOrDefault(method, method.getName());
}
@Override
public DexString lookupName(DexField field) {
return renaming.getOrDefault(field, field.getName());
}
/**
* Checks that the renaming of the method reference {@param method} is consistent with the
* renaming of the resolution target of {@param method}.
*/
@Override
public boolean verifyRenamingConsistentWithResolution(DexMethod method) {
if (method.holder.isArrayType()) {
// Array methods are never renamed, so do not bother to check.
return true;
}
ResolutionResult resolution = appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
assert resolution != null;
if (resolution.isSingleResolution()) {
// If we can resolve `item`, then the renaming for `item` and its resolution should be the
// same.
DexEncodedMethod resolvedMethod = resolution.asSingleResolution().getResolvedMethod();
assert lookupName(method) == lookupName(resolvedMethod.getReference());
return true;
}
assert resolution.isFailedResolution();
// If we can't resolve `item` it is questionable to record a renaming for it. However, it can
// be required to preserve errors.
//
// Example:
// class A { private void m() }
// class B extends A {}
// class Main { public static void main() { new B().m(); } }
//
// In this example, the invoke-virtual instruction targeting m() in Main does not resolve,
// since the method is private. On the JVM this fails with an IllegalAccessError.
//
// If A.m() is renamed to A.a(), and the invoke-virtual instruction in Main is not changed to
// target a(), then the program will start failing with a NoSuchMethodError instead of an
// IllegalAccessError.
resolution
.asFailedResolution()
.forEachFailureDependency(
failureDependence -> {
assert lookupName(method) == lookupName(failureDependence.getReference());
});
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
renaming.forEach(
(item, str) -> {
if (item instanceof DexType) {
builder.append("[c] ");
} else if (item instanceof DexMethod) {
builder.append("[m] ");
} else if (item instanceof DexField) {
builder.append("[f] ");
}
builder.append(item.toSourceString());
builder.append(" -> ");
builder.append(str.toSourceString());
builder.append('\n');
});
return builder.toString();
}
}