Version 1.5.35
Cherry pick: Add tests for class and package obfuscation dictionary
CL: https://r8-review.googlesource.com/c/r8/+/38554
Cherry pick: Unify reservation of field and method names in strategy
CL: https://r8-review.googlesource.com/c/r8/+/38555
Cherry pick: Add test for proguard map parsing error regarding
inlining frames
CL: https://r8-review.googlesource.com/c/r8/+/38160
Cherry pick: Rewrite parser to remember last added entry
CL: https://r8-review.googlesource.com/c/r8/+/38580
Cherry pick: Add test for rotating names when using applymapping
CL: https://r8-review.googlesource.com/c/r8/+/38540
Cherry pick: Change ProguardMapMinifier to handle applymapping use
cases
CL: https://r8-review.googlesource.com/c/r8/+/38556
Cherry pick: Allow for using minification with applymapping
https://r8-review.googlesource.com/c/r8/+/38583
Cherry pick: Enable -dontusemixedcaseclassnames and allow numbers in
identifiers
CL: https://r8-review.googlesource.com/c/r8/+/38703
Bug: 121305642
Bug: 126503704
Bug: 128516926
Bug: 128868424
Bug: 129493704
Bug: 130736358
Bug: 131349062
Bug: 131532229
Bug: 132666215
Bug: 132812927
Bug: 132927261
Change-Id: I5d21d0a81ed2a44282889a64ec7c3975d2db187c
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 16b75f5..ae8d4ae 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -681,8 +681,8 @@
timing.end();
} else {
if (appView.appInfo().hasLiveness()) {
- // TODO(124726014): Rewrite signature annotations in lens rewriting instead of here?
- new GenericSignatureRewriter(appView.withLiveness()).run();
+ // TODO(b/124726014): Rewrite signature annotations in lens rewriting instead of here?
+ new GenericSignatureRewriter(appView.withLiveness()).run(appView.appInfo().classes());
}
namingLens = NamingLens.getIdentityLens();
}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 1027b71..375cc0b 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.5.34";
+ public static final String LABEL = "1.5.35";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 166ec88..a3e71e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -21,7 +21,8 @@
import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence.Wrapper;
@@ -148,8 +149,10 @@
appView
.dexItemFactory()
.createString(
- StringUtils.numberToIdentifier(
- count, method.method.name.toSourceString().toCharArray()));
+ SymbolGenerationUtils.numberToIdentifier(
+ count,
+ MixedCasing.USE_MIXED_CASE,
+ method.method.name.toSourceString().toCharArray()));
} else {
// Constructors must be named `<init>`.
return null;
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 1b50601..8018a2d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -30,8 +30,6 @@
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -50,8 +48,6 @@
private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
private final Map<String, Namespace> states = new HashMap<>();
- private final List<String> packageDictionary;
- private final List<String> classDictionary;
private final boolean keepInnerClassStructure;
private final Namespace topLevelState;
@@ -69,8 +65,6 @@
this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
this.isAccessModificationAllowed =
options.getProguardConfiguration().isAccessModificationAllowed();
- this.packageDictionary = options.getProguardConfiguration().getPackageObfuscationDictionary();
- this.classDictionary = options.getProguardConfiguration().getClassObfuscationDictionary();
this.keepInnerClassStructure =
options.getProguardConfiguration().getKeepAttributes().signature
|| options.getProguardConfiguration().getKeepAttributes().innerClasses;
@@ -134,7 +128,7 @@
timing.end();
timing.begin("rename-generic");
- new GenericSignatureRewriter(appView, renaming).run();
+ new GenericSignatureRewriter(appView, renaming).run(classes);
timing.end();
timing.begin("rename-arrays");
@@ -356,12 +350,12 @@
}
}
- protected class Namespace {
+ protected class Namespace implements InternalNamingState {
private final String packageName;
private final char[] packagePrefix;
- private final Iterator<String> packageDictionaryIterator;
- private final Iterator<String> classDictionaryIterator;
+ private int dictionaryIndex = 0;
+ private int nameIndex = 1;
Namespace(String packageName) {
this(packageName, String.valueOf(DESCRIPTOR_PACKAGE_SEPARATOR));
@@ -373,8 +367,6 @@
// L or La/b/ (or La/b/C$)
+ (packageName.isEmpty() ? "" : separator))
.toCharArray();
- this.packageDictionaryIterator = packageDictionary.iterator();
- this.classDictionaryIterator = classDictionary.iterator();
// R.class in Android, which contains constant IDs to assets, can be bundled at any time.
// Insert `R` immediately so that the class name minifier can skip that name by default.
@@ -386,63 +378,49 @@
return packageName;
}
- private DexString nextSuggestedNameForClass(DexType type) {
- StringBuilder nextName = new StringBuilder();
- if (!classNamingStrategy.bypassDictionary() && classDictionaryIterator.hasNext()) {
- nextName.append(packagePrefix).append(classDictionaryIterator.next()).append(';');
- return appView.dexItemFactory().createString(nextName.toString());
- } else {
- return classNamingStrategy.next(this, type, packagePrefix);
- }
- }
-
DexString nextTypeName(DexType type) {
DexString candidate;
do {
- candidate = nextSuggestedNameForClass(type);
+ candidate = classNamingStrategy.next(type, packagePrefix, this);
} while (usedTypeNames.contains(candidate));
usedTypeNames.add(candidate);
return candidate;
}
- private String nextSuggestedNameForSubpackage() {
- // Note that the differences between this method and the other variant for class renaming are
- // 1) this one uses the different dictionary and counter,
- // 2) this one does not append ';' at the end, and
- // 3) this one removes 'L' at the beginning to make the return value a binary form.
- if (!packageNamingStrategy.bypassDictionary() && packageDictionaryIterator.hasNext()) {
- StringBuilder nextName = new StringBuilder();
- nextName.append(packagePrefix).append(packageDictionaryIterator.next());
- return nextName.toString().substring(1);
- } else {
- return packageNamingStrategy.next(this, packagePrefix);
- }
- }
-
String nextPackagePrefix() {
String candidate;
do {
- candidate = nextSuggestedNameForSubpackage();
+ candidate = packageNamingStrategy.next(packagePrefix, this);
} while (usedPackagePrefixes.contains(candidate));
usedPackagePrefixes.add(candidate);
return candidate;
}
+
+ @Override
+ public int getDictionaryIndex() {
+ return dictionaryIndex;
+ }
+
+ @Override
+ public int incrementDictionaryIndex() {
+ return dictionaryIndex++;
+ }
+
+ @Override
+ public int incrementNameIndex(boolean isDirectMethodCall) {
+ assert !isDirectMethodCall;
+ return nameIndex++;
+ }
}
protected interface ClassNamingStrategy {
-
- DexString next(Namespace namespace, DexType type, char[] packagePrefix);
-
- boolean bypassDictionary();
+ DexString next(DexType type, char[] packagePrefix, InternalNamingState state);
boolean noObfuscation(DexType type);
}
protected interface PackageNamingStrategy {
-
- String next(Namespace namespace, char[] packagePrefix);
-
- boolean bypassDictionary();
+ String next(char[] packagePrefix, InternalNamingState state);
}
/**
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index aeac240..437d78b 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -85,11 +85,15 @@
for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) {
ReservedFieldNamingState reservedNames = null;
for (DexEncodedField field : clazz.fields()) {
- if (shouldReserveName(clazz, field)) {
+ DexString reservedName = strategy.getReservedNameOrDefault(field, clazz, null);
+ if (reservedName != null) {
if (reservedNames == null) {
reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
}
- reservedNames.markReservedDirectly(field.field);
+ reservedNames.markReservedDirectly(reservedName, field.field.type);
+ if (reservedName != field.field.name) {
+ renaming.put(field.field, reservedName);
+ }
}
}
@@ -109,17 +113,6 @@
propagateReservedFieldNamesUpwards();
}
- private boolean shouldReserveName(DexClass clazz, DexEncodedField field) {
- if (clazz.isLibraryClass()) {
- return true;
- }
- if (!appView.options().getProguardConfiguration().hasApplyMappingFile()
- && appView.rootSet().mayNotBeMinified(field.field, appView)) {
- return true;
- }
- return false;
- }
-
private void propagateReservedFieldNamesUpwards() {
BottomUpClassHierarchyTraversal.forProgramClasses(appView)
.visit(
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index b713582..d8cb81d 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.FieldNamingState.InternalState;
-import com.android.tools.r8.naming.MemberNamingStrategy.MemberNamingInternalState;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -51,14 +50,15 @@
DexEncodedField encodedField = appView.appInfo().resolveField(field);
if (encodedField != null) {
DexClass clazz = appView.definitionFor(encodedField.field.holder);
- if (clazz == null || clazz.isLibraryClass()) {
+ if (clazz == null) {
return field.name;
}
- if (!appView.options().getProguardConfiguration().hasApplyMappingFile()
- && appView.rootSet().mayNotBeMinified(encodedField.field, appView)) {
- return field.name;
+ DexString reservedName = strategy.getReservedNameOrDefault(encodedField, clazz, null);
+ if (reservedName != null) {
+ return reservedName;
}
}
+ // TODO(b/133208730) If we cannot resolve the field, are we then allowed to rename it?
return getOrCreateInternalState(field).createNewName(field);
}
@@ -80,7 +80,7 @@
return new FieldNamingState(appView, strategy, reservedNames, internalStatesClone);
}
- class InternalState implements MemberNamingInternalState, Cloneable {
+ class InternalState implements InternalNamingState, Cloneable {
private int dictionaryIndex;
private int nextNameIndex;
@@ -98,8 +98,7 @@
DexString name;
do {
name = strategy.next(field, this);
- } while (reservedNames.isReserved(name, field.type)
- && !strategy.breakOnNotAvailable(field, name));
+ } while (reservedNames.isReserved(name, field.type));
return name;
}
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index a43dcb8..4c751c6 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -289,7 +289,7 @@
for (MethodNamingState<?> namingState : globalStateMap.get(wrapper)) {
if (!namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
- namingState.reserveName(unifiedMethod.name, unifiedMethod.proto);
+ namingState.reserveName(unifiedMethod.name, unifiedMethod.proto, unifiedMethod.name);
changed = true;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/InternalNamingState.java b/src/main/java/com/android/tools/r8/naming/InternalNamingState.java
new file mode 100644
index 0000000..e5749c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/InternalNamingState.java
@@ -0,0 +1,14 @@
+// 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;
+
+interface InternalNamingState {
+
+ int getDictionaryIndex();
+
+ int incrementDictionaryIndex();
+
+ int incrementNameIndex(boolean isDirectMethodCall);
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index 260b176..f33027b 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.naming;
+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.DexMethod;
import com.android.tools.r8.graph.DexReference;
@@ -11,20 +14,17 @@
public interface MemberNamingStrategy {
- DexString next(DexMethod method, MemberNamingInternalState internalState);
+ DexString next(DexMethod method, InternalNamingState internalState);
- DexString next(DexField field, MemberNamingInternalState internalState);
+ DexString next(DexField field, InternalNamingState internalState);
- boolean breakOnNotAvailable(DexReference source, DexString name);
+ DexString getReservedNameOrDefault(
+ DexEncodedMethod method, DexClass holder, DexString defaultValue);
- boolean noObfuscation(DexReference reference);
+ DexString getReservedNameOrDefault(
+ DexEncodedField field, DexClass holder, DexString defaultValue);
- interface MemberNamingInternalState {
+ boolean allowMemberRenaming(DexClass holder);
- int getDictionaryIndex();
-
- int incrementDictionaryIndex();
-
- int incrementNameIndex(boolean isDirectMethodCall);
- }
+ void reportReservationError(DexReference source, DexString name);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 029f65a..9b07115 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -37,7 +37,8 @@
* <p>As in the Dalvik VM method dispatch takes argument and return types of methods into account,
* we can further reuse names if the prototypes of two methods differ. For this, we store the above
* state separately for each proto using a map from protos to {@link
- * MethodNamingState.InternalState} objects. These internal state objects are also linked.
+ * MethodNamingState.InternalReservationState} objects. These internal state objects are also
+ * linked.
*
* <p>Name assignment happens in 4 stages. In the first stage, we record all names that are used by
* library classes or are flagged using a keep rule as reserved. This step also allocates the {@link
@@ -72,9 +73,9 @@
* states of classes. Hence, skipping over names during interface naming does not impact their
* availability in the next phase.
*
- * <p>In the final stage, we assign names to methods by traversing the subtype tree, now allocating
- * separate naming states for each class starting from the frontier. In the first swoop, we allocate
- * all non-private methods, updating naming states accordingly.
+ * <p>In stage 4, we assign names to methods by traversing the subtype tree, now allocating separate
+ * naming states for each class starting from the frontier. In the first swoop, we allocate all
+ * non-private methods, updating naming states accordingly.
*
* <p>Finally, the computed renamings are returned as a map from {@link DexMethod} to {@link
* DexString}. The MethodNameMinifier object should not be retained to ensure all intermediate state
@@ -139,11 +140,6 @@
return states.computeIfAbsent(type, f);
}
- private boolean alwaysReserveMemberNames(DexClass holder) {
- return !appView.options().getProguardConfiguration().hasApplyMappingFile()
- && holder.isNotProgramClass();
- }
-
private Function<DexProto, ?> getKeyTransform() {
if (appView.options().getProguardConfiguration().isOverloadAggressively()) {
// Use the full proto as key, hence reuse names based on full signature.
@@ -184,10 +180,12 @@
InterfaceMethodNameMinifier interfaceMethodNameMinifier =
new InterfaceMethodNameMinifier(
appView, desugaredCallSites, equivalence, frontierState, minifierState);
+ timing.end();
+ timing.begin("Phase 3");
interfaceMethodNameMinifier.assignNamesToInterfaceMethods(timing, interfaces);
timing.end();
- // Phase 3: Assign names top-down by traversing the subtype hierarchy.
- timing.begin("Phase 3");
+ // Phase 4: Assign names top-down by traversing the subtype hierarchy.
+ timing.begin("Phase 4");
assignNamesToClassesMethods(appView.dexItemFactory().objectType);
timing.end();
@@ -207,31 +205,42 @@
//
// A simple way to ensure this is to process virtual methods first and then direct methods.
DexClass holder = appView.definitionFor(type);
- boolean shouldAssignName = holder != null && !alwaysReserveMemberNames(holder);
+ boolean shouldAssignName = holder != null && strategy.allowMemberRenaming(holder);
if (shouldAssignName) {
MethodNamingState<?> state =
computeStateIfAbsent(type, k -> minifierState.getState(holder.superType).createChild());
for (DexEncodedMethod method : holder.virtualMethodsSorted()) {
- assignNameToMethod(method, state);
+ assignNameToMethod(method, state, holder);
}
for (DexEncodedMethod method : holder.directMethodsSorted()) {
- assignNameToMethod(method, state);
+ assignNameToMethod(method, state, holder);
}
}
- appView.appInfo().forAllExtendsSubtypes(type, subtype -> assignNamesToClassesMethods(subtype));
+
+ appView.appInfo().forAllExtendsSubtypes(type, this::assignNamesToClassesMethods);
}
- private void assignNameToMethod(DexEncodedMethod encodedMethod, MethodNamingState<?> state) {
+ private void assignNameToMethod(
+ DexEncodedMethod encodedMethod, MethodNamingState<?> state, DexClass holder) {
if (encodedMethod.accessFlags.isConstructor()) {
return;
}
DexMethod method = encodedMethod.method;
- if (!state.isReserved(method.name, method.proto)) {
- DexString renamedName = state.assignNewNameFor(method, method.name, method.proto);
- renaming.put(method, renamedName);
- if (!encodedMethod.accessFlags.isPrivate()) {
- state.addRenaming(method.name, method.proto, renamedName);
- }
+ DexString reservedName = strategy.getReservedNameOrDefault(encodedMethod, holder, method.name);
+ if (state.isReserved(reservedName, method.proto)) {
+ return;
+ }
+ DexString newName = state.assignNewNameFor(method, method.name, method.proto);
+ if (newName != method.name) {
+ addRenaming(encodedMethod, state, newName);
+ }
+ }
+
+ private void addRenaming(
+ DexEncodedMethod encodedMethod, MethodNamingState<?> state, DexString renamedName) {
+ renaming.put(encodedMethod.method, renamedName);
+ if (!encodedMethod.accessFlags.isPrivate()) {
+ state.addRenaming(encodedMethod.method.name, encodedMethod.method.proto, renamedName);
}
}
@@ -276,14 +285,38 @@
DexClass holder = appView.definitionFor(type);
if (holder != null) {
- boolean keepAll = alwaysReserveMemberNames(holder) || holder.accessFlags.isAnnotation();
for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) {
- // TODO(christofferqa): Wouldn't it be sufficient only to reserve names for non-private
- // methods?
- if (keepAll
- || method.accessFlags.isConstructor()
- || strategy.noObfuscation(method.method)) {
- reserveNamesForMethod(method.method, state);
+ DexString reservedName = strategy.getReservedNameOrDefault(method, holder, null);
+ if (reservedName != null) {
+ // If the mapping is incorrect, we might try to map a method to an existing name.
+ // class A {
+ // int m1(String foo) { ... }
+ // }
+ //
+ // class B extends A {
+ // int m2(String bar) { ... }
+ // }
+ //
+ // and the following reservations (mapping):
+ // A -> a:
+ // int m1(String foo) -> a
+ // B -> b:
+ // int m2(String foo) -> a <- ERROR
+ //
+ // In the example, the mapping file specifies that A.m1() should become a.a() and that
+ // B.m2() should become b.a(). This should not be allowed, since b.a() now overrides
+ // a.a(), unlike in the original program.
+ DexString previouslyReservedOriginalName =
+ state.getReservedOriginalName(reservedName, method.method.proto);
+ if (previouslyReservedOriginalName != null
+ && previouslyReservedOriginalName != method.method.name) {
+ strategy.reportReservationError(method.method, reservedName);
+ }
+ state.reserveName(reservedName, method.method.proto, method.method.name);
+ globalState.reserveName(reservedName, method.method.proto, method.method.name);
+ if (reservedName != method.method.name) {
+ addRenaming(method, state, reservedName);
+ }
}
}
}
@@ -291,11 +324,6 @@
return state;
}
- private void reserveNamesForMethod(DexMethod method, MethodNamingState<?> state) {
- state.reserveName(method.name, method.proto);
- globalState.reserveName(method.name, method.proto);
- }
-
public DexType get(DexType type) {
return frontiers.getOrDefault(type, type);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 7b0c5fb..adbc1c3 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -7,19 +7,16 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.naming.MemberNamingStrategy.MemberNamingInternalState;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.Sets;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
import java.util.function.Function;
class MethodNamingState<KeyType> {
private final MethodNamingState<KeyType> parent;
- private final Map<KeyType, InternalState> usedNames = new HashMap<>();
+ private final Map<KeyType, InternalReservationState> usedNames = new HashMap<>();
+ private final Map<KeyType, InternalNewNameState> newNameStates = new HashMap<>();
private final Function<DexProto, KeyType> keyTransform;
private final MemberNamingStrategy strategy;
@@ -37,74 +34,104 @@
this.strategy = strategy;
}
- public MethodNamingState<KeyType> createChild() {
+ MethodNamingState<KeyType> createChild() {
return new MethodNamingState<>(this, keyTransform, strategy);
}
- private InternalState findInternalStateFor(KeyType key) {
- InternalState result = usedNames.get(key);
+ private InternalReservationState findInternalReservationStateFor(KeyType key) {
+ InternalReservationState result = usedNames.get(key);
if (result == null && parent != null) {
- result = parent.findInternalStateFor(key);
+ result = parent.findInternalReservationStateFor(key);
}
return result;
}
- private InternalState getOrCreateInternalStateFor(KeyType key) {
- // TODO(herhut): Maybe allocate these sparsely and search via state chain.
- InternalState result = usedNames.get(key);
+ private InternalReservationState getOrCreateInternalReservationStateFor(KeyType key) {
+ InternalReservationState result = usedNames.get(key);
if (result == null) {
- InternalState parentState = parent != null ? parent.getOrCreateInternalStateFor(key) : null;
- result = new InternalState(parentState);
+ InternalReservationState parentState =
+ parent != null ? parent.getOrCreateInternalReservationStateFor(key) : null;
+ result = new InternalReservationState(parentState);
usedNames.put(key, result);
}
return result;
}
+ private InternalNewNameState findInternalNewNameStateFor(KeyType key) {
+ InternalNewNameState result = newNameStates.get(key);
+ if (result == null && parent != null) {
+ result = parent.findInternalNewNameStateFor(key);
+ }
+ return result;
+ }
+
+ private InternalNewNameState getOrCreateNewNameStateFor(KeyType key) {
+ InternalNewNameState result = newNameStates.get(key);
+ if (result == null) {
+ InternalReservationState reservationState = getOrCreateInternalReservationStateFor(key);
+ assert reservationState != null;
+ InternalNewNameState parentState =
+ parent != null ? parent.getOrCreateNewNameStateFor(key) : null;
+ result = new InternalNewNameState(parentState, reservationState);
+ newNameStates.put(key, result);
+ }
+ return result;
+ }
+
private DexString getAssignedNameFor(DexString name, KeyType key) {
- InternalState state = findInternalStateFor(key);
+ InternalReservationState state = findInternalReservationStateFor(key);
if (state == null) {
return null;
}
return state.getAssignedNameFor(name);
}
- public DexString assignNewNameFor(DexMethod source, DexString original, DexProto proto) {
+ DexString assignNewNameFor(DexMethod source, DexString original, DexProto proto) {
KeyType key = keyTransform.apply(proto);
DexString result = getAssignedNameFor(original, key);
if (result == null) {
- InternalState state = getOrCreateInternalStateFor(key);
+ InternalNewNameState state = getOrCreateNewNameStateFor(key);
result = state.getNewNameFor(source);
}
return result;
}
- public void reserveName(DexString name, DexProto proto) {
+ void reserveName(DexString name, DexProto proto, DexString originalName) {
KeyType key = keyTransform.apply(proto);
- InternalState state = getOrCreateInternalStateFor(key);
- state.reserveName(name);
+ InternalReservationState state = getOrCreateInternalReservationStateFor(key);
+ state.reserveName(name, originalName);
}
- public boolean isReserved(DexString name, DexProto proto) {
+ boolean isReserved(DexString name, DexProto proto) {
KeyType key = keyTransform.apply(proto);
- InternalState state = findInternalStateFor(key);
+ InternalReservationState state = findInternalReservationStateFor(key);
if (state == null) {
return false;
}
return state.isReserved(name);
}
- public boolean isAvailable(DexProto proto, DexString candidate) {
+ DexString getReservedOriginalName(DexString name, DexProto proto) {
KeyType key = keyTransform.apply(proto);
- InternalState state = findInternalStateFor(key);
+ InternalReservationState state = findInternalReservationStateFor(key);
+ if (state == null) {
+ return null;
+ }
+ return state.getReservedOriginalName(name);
+ }
+
+ boolean isAvailable(DexProto proto, DexString candidate) {
+ KeyType key = keyTransform.apply(proto);
+ InternalReservationState state = findInternalReservationStateFor(key);
if (state == null) {
return true;
}
return state.isAvailable(candidate);
}
- public void addRenaming(DexString original, DexProto proto, DexString newName) {
+ void addRenaming(DexString original, DexProto proto, DexString newName) {
KeyType key = keyTransform.apply(proto);
- InternalState state = getOrCreateInternalStateFor(key);
+ InternalReservationState state = getOrCreateInternalReservationStateFor(key);
state.addRenaming(original, newName);
}
@@ -114,7 +141,7 @@
String indentation,
PrintStream out) {
KeyType key = keyTransform.apply(proto);
- InternalState state = getOrCreateInternalStateFor(key);
+ InternalNewNameState state = getOrCreateNewNameStateFor(key);
out.print(indentation);
out.print("NamingState(node=`");
out.print(stateKeyGetter.apply(this).toSourceString());
@@ -131,76 +158,29 @@
}
}
- class InternalState implements MemberNamingInternalState {
-
- private static final int INITIAL_NAME_COUNT = 1;
- private static final int INITIAL_DICTIONARY_INDEX = 0;
-
- private final InternalState parentInternalState;
- private Set<DexString> reservedNames = null;
+ class InternalReservationState {
+ private final InternalReservationState parentInternalState;
+ private Map<DexString, DexString> reservedNames = null;
private Map<DexString, DexString> renamings = null;
- private int virtualNameCount;
- private int directNameCount = 0;
- private int dictionaryIndex;
- private InternalState(InternalState parentInternalState) {
+ private InternalReservationState(InternalReservationState parentInternalState) {
this.parentInternalState = parentInternalState;
- this.dictionaryIndex =
- parentInternalState == null
- ? INITIAL_DICTIONARY_INDEX
- : parentInternalState.dictionaryIndex;
- this.virtualNameCount =
- parentInternalState == null ? INITIAL_NAME_COUNT : parentInternalState.virtualNameCount;
}
- private boolean isReserved(DexString name) {
- return (reservedNames != null && reservedNames.contains(name))
+ boolean isReserved(DexString name) {
+ return (reservedNames != null && reservedNames.containsKey(name))
|| (parentInternalState != null && parentInternalState.isReserved(name));
}
- private boolean isAvailable(DexString name) {
- return !(renamings != null && renamings.containsValue(name))
- && !(reservedNames != null && reservedNames.contains(name))
- && (parentInternalState == null || parentInternalState.isAvailable(name));
- }
-
- void reserveName(DexString name) {
- if (reservedNames == null) {
- reservedNames = Sets.newIdentityHashSet();
+ DexString getReservedOriginalName(DexString name) {
+ DexString result = null;
+ if (reservedNames != null) {
+ result = reservedNames.get(name);
}
- reservedNames.add(name);
- }
-
- @Override
- public int getDictionaryIndex() {
- return dictionaryIndex;
- }
-
- @Override
- public int incrementDictionaryIndex() {
- return dictionaryIndex++;
- }
-
- private boolean checkParentPublicNameCountIsLessThanOrEqual() {
- int maxParentCount = 0;
- InternalState tmp = parentInternalState;
- while (tmp != null) {
- maxParentCount = Math.max(tmp.virtualNameCount, maxParentCount);
- tmp = tmp.parentInternalState;
+ if (result == null && parentInternalState != null) {
+ result = parentInternalState.getReservedOriginalName(name);
}
- assert maxParentCount <= virtualNameCount;
- return true;
- }
-
- @Override
- public int incrementNameIndex(boolean isDirectMethodCall) {
- assert checkParentPublicNameCountIsLessThanOrEqual();
- if (isDirectMethodCall) {
- return virtualNameCount + directNameCount++;
- } else {
- assert directNameCount == 0;
- return virtualNameCount++;
- }
+ return result;
}
DexString getAssignedNameFor(DexString original) {
@@ -214,12 +194,17 @@
return result;
}
- private DexString getNewNameFor(DexMethod source) {
- DexString name;
- do {
- name = strategy.next(source, this);
- } while (!isAvailable(name) && !strategy.breakOnNotAvailable(source, name));
- return name;
+ private boolean isAvailable(DexString name) {
+ return !(renamings != null && renamings.containsValue(name))
+ && !(reservedNames != null && reservedNames.containsKey(name))
+ && (parentInternalState == null || parentInternalState.isAvailable(name));
+ }
+
+ void reserveName(DexString name, DexString originalName) {
+ if (reservedNames == null) {
+ reservedNames = new HashMap<>();
+ }
+ reservedNames.put(name, originalName);
}
void addRenaming(DexString original, DexString newName) {
@@ -229,58 +214,17 @@
renamings.put(original, newName);
}
- void printInternalState(
- MethodNamingState<?> expectedNamingState,
- Function<MethodNamingState<?>, DexType> stateKeyGetter,
- String indentation,
- PrintStream out) {
- assert expectedNamingState == MethodNamingState.this;
-
- DexType stateKey = stateKeyGetter.apply(expectedNamingState);
- out.print(indentation);
- out.print("InternalState(node=`");
- out.print(stateKey != null ? stateKey.toSourceString() : "<GLOBAL>");
- out.println("`)");
-
- printLastName(indentation + " ", out);
- printReservedNames(indentation + " ", out);
- printRenamings(indentation + " ", out);
-
- if (parentInternalState != null) {
- parentInternalState.printInternalState(
- expectedNamingState.parent, stateKeyGetter, indentation + " ", out);
- }
- }
-
- void printLastName(String indentation, PrintStream out) {
- out.print(indentation);
- out.print("Last name: ");
- int index = virtualNameCount + directNameCount;
- if (index > 1) {
- out.print(StringUtils.numberToIdentifier(index - 1));
- out.print(" (public name count: ");
- out.print(virtualNameCount);
- out.print(")");
- out.print(" (direct name count: ");
- out.print(directNameCount);
- out.print(")");
- } else {
- out.print("<NONE>");
- }
- out.println();
- }
-
void printReservedNames(String indentation, PrintStream out) {
out.print(indentation);
out.print("Reserved names:");
if (reservedNames == null || reservedNames.isEmpty()) {
out.print(" <NO RESERVED NAMES>");
} else {
- for (DexString reservedName : reservedNames) {
+ for (DexString reservedName : reservedNames.keySet()) {
out.print(System.lineSeparator());
out.print(indentation);
out.print(" ");
- out.print(reservedName.toSourceString());
+ out.print(reservedName.toSourceString() + "(by " + reservedNames.get(reservedName) + ")");
}
}
out.println();
@@ -304,4 +248,102 @@
out.println();
}
}
+
+ class InternalNewNameState implements InternalNamingState {
+
+ private final InternalNewNameState parentInternalState;
+ private final InternalReservationState reservationState;
+
+ private static final int INITIAL_NAME_COUNT = 1;
+ private static final int INITIAL_DICTIONARY_INDEX = 0;
+
+ private int virtualNameCount;
+ private int directNameCount = 0;
+ private int dictionaryIndex;
+
+ private InternalNewNameState(
+ InternalNewNameState parentInternalState, InternalReservationState reservationState) {
+ this.parentInternalState = parentInternalState;
+ this.reservationState = reservationState;
+ this.dictionaryIndex =
+ parentInternalState == null
+ ? INITIAL_DICTIONARY_INDEX
+ : parentInternalState.dictionaryIndex;
+ this.virtualNameCount =
+ parentInternalState == null ? INITIAL_NAME_COUNT : parentInternalState.virtualNameCount;
+ assert reservationState != null;
+ }
+
+ @Override
+ public int getDictionaryIndex() {
+ return dictionaryIndex;
+ }
+
+ @Override
+ public int incrementDictionaryIndex() {
+ return dictionaryIndex++;
+ }
+
+ private boolean checkParentPublicNameCountIsLessThanOrEqual() {
+ int maxParentCount = 0;
+ InternalNewNameState tmp = parentInternalState;
+ while (tmp != null) {
+ maxParentCount = Math.max(tmp.virtualNameCount, maxParentCount);
+ tmp = tmp.parentInternalState;
+ }
+ assert maxParentCount <= virtualNameCount;
+ return true;
+ }
+
+ @Override
+ public int incrementNameIndex(boolean isDirectMethodCall) {
+ assert checkParentPublicNameCountIsLessThanOrEqual();
+ if (isDirectMethodCall) {
+ return virtualNameCount + directNameCount++;
+ } else {
+ assert directNameCount == 0;
+ return virtualNameCount++;
+ }
+ }
+
+ private DexString getNewNameFor(DexMethod source) {
+ DexString name;
+ do {
+ name = strategy.next(source, this);
+ } while (!reservationState.isAvailable(name));
+ return name;
+ }
+
+ void printInternalState(
+ MethodNamingState<?> expectedNamingState,
+ Function<MethodNamingState<?>, DexType> stateKeyGetter,
+ String indentation,
+ PrintStream out) {
+ assert expectedNamingState == MethodNamingState.this;
+
+ DexType stateKey = stateKeyGetter.apply(expectedNamingState);
+ out.print(indentation);
+ out.print("InternalState(node=`");
+ out.print(stateKey != null ? stateKey.toSourceString() : "<GLOBAL>");
+ out.println("`)");
+
+ printLastName(indentation + " ", out);
+ reservationState.printReservedNames(indentation + " ", out);
+ reservationState.printRenamings(indentation + " ", out);
+
+ if (parentInternalState != null) {
+ parentInternalState.printInternalState(
+ expectedNamingState.parent, stateKeyGetter, indentation + " ", out);
+ }
+ }
+
+ void printLastName(String indentation, PrintStream out) {
+ out.print(indentation);
+ out.print("public name count: ");
+ out.print(virtualNameCount);
+ out.print(", ");
+ out.print("direct name count: ");
+ out.println(directNameCount);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 4bdf87c..6906a06 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,9 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
+
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;
@@ -15,15 +18,13 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
-import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
import com.android.tools.r8.naming.ClassNameMinifier.PackageNamingStrategy;
import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
import com.android.tools.r8.utils.Timing;
-import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -50,7 +51,7 @@
new ClassNameMinifier(
appView,
new MinificationClassNamingStrategy(appView),
- new MinificationPackageNamingStrategy(),
+ new MinificationPackageNamingStrategy(appView),
// Use deterministic class order to make sure renaming is deterministic.
appView.appInfo().classesWithDeterministicOrder());
ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
@@ -84,29 +85,57 @@
return lens;
}
- static class MinificationClassNamingStrategy implements ClassNamingStrategy {
+ abstract static class BaseMinificationNamingStrategy {
- private final AppView<?> appView;
- private final Object2IntMap<Namespace> namespaceCounters = new Object2IntLinkedOpenHashMap<>();
+ // We have to ensure that the names proposed by the minifier is not used in the obfuscation
+ // dictionary. We use a list for direct indexing based on a number and a set for looking up.
+ private final List<String> obfuscationDictionary;
+ private final Set<String> obfuscationDictionaryForLookup;
+ private final MixedCasing mixedCasing;
+
+ BaseMinificationNamingStrategy(List<String> obfuscationDictionary, boolean dontUseMixedCasing) {
+ this.obfuscationDictionary = obfuscationDictionary;
+ this.obfuscationDictionaryForLookup = new HashSet<>(this.obfuscationDictionary);
+ this.mixedCasing =
+ dontUseMixedCasing ? MixedCasing.DONT_USE_MIXED_CASE : MixedCasing.USE_MIXED_CASE;
+ assert obfuscationDictionary != null;
+ }
+
+ String nextName(char[] packagePrefix, InternalNamingState state, boolean isDirectMethodCall) {
+ StringBuilder nextName = new StringBuilder();
+ nextName.append(packagePrefix);
+ if (state.getDictionaryIndex() < obfuscationDictionary.size()) {
+ nextName.append(obfuscationDictionary.get(state.incrementDictionaryIndex()));
+ } else {
+ String nextString;
+ do {
+ nextString =
+ SymbolGenerationUtils.numberToIdentifier(
+ state.incrementNameIndex(isDirectMethodCall), mixedCasing);
+ } while (obfuscationDictionaryForLookup.contains(nextString));
+ nextName.append(nextString);
+ }
+ return nextName.toString();
+ }
+ }
+
+ static class MinificationClassNamingStrategy extends BaseMinificationNamingStrategy
+ implements ClassNamingStrategy {
+
+ final AppView<?> appView;
+ private final DexItemFactory factory;
MinificationClassNamingStrategy(AppView<?> appView) {
+ super(
+ appView.options().getProguardConfiguration().getClassObfuscationDictionary(),
+ appView.options().getProguardConfiguration().hasDontUseMixedCaseClassnames());
this.appView = appView;
- namespaceCounters.defaultReturnValue(1);
+ factory = appView.dexItemFactory();
}
@Override
- public DexString next(Namespace namespace, DexType type, char[] packagePrefix) {
- int counter = namespaceCounters.put(namespace, namespaceCounters.getInt(namespace) + 1);
- DexString string =
- appView
- .dexItemFactory()
- .createString(StringUtils.numberToIdentifier(counter, packagePrefix, true));
- return string;
- }
-
- @Override
- public boolean bypassDictionary() {
- return false;
+ public DexString next(DexType type, char[] packagePrefix, InternalNamingState state) {
+ return factory.createString(nextName(packagePrefix, state, false) + ";");
}
@Override
@@ -115,84 +144,91 @@
}
}
- static class MinificationPackageNamingStrategy implements PackageNamingStrategy {
+ static class MinificationPackageNamingStrategy extends BaseMinificationNamingStrategy
+ implements PackageNamingStrategy {
- private final Object2IntMap<Namespace> namespaceCounters = new Object2IntLinkedOpenHashMap<>();
-
- public MinificationPackageNamingStrategy() {
- namespaceCounters.defaultReturnValue(1);
+ MinificationPackageNamingStrategy(AppView<?> appView) {
+ super(
+ appView.options().getProguardConfiguration().getPackageObfuscationDictionary(),
+ appView.options().getProguardConfiguration().hasDontUseMixedCaseClassnames());
}
@Override
- public String next(Namespace namespace, char[] packagePrefix) {
+ public String next(char[] packagePrefix, InternalNamingState state) {
// Note that the differences between this method and the other variant for class renaming are
// 1) this one uses the different dictionary and counter,
// 2) this one does not append ';' at the end, and
// 3) this one removes 'L' at the beginning to make the return value a binary form.
- int counter = namespaceCounters.put(namespace, namespaceCounters.getInt(namespace) + 1);
- return StringUtils.numberToIdentifier(counter, packagePrefix, false).substring(1);
- }
-
- @Override
- public boolean bypassDictionary() {
- return false;
+ return nextName(packagePrefix, state, false).substring(1);
}
}
- static class MinifierMemberNamingStrategy implements MemberNamingStrategy {
+ static class MinifierMemberNamingStrategy extends BaseMinificationNamingStrategy
+ implements MemberNamingStrategy {
+ final AppView<?> appView;
private final DexItemFactory factory;
- // We have to ensure that the names proposed by the minifier is not used in the obfuscation
- // dictionary. We use a list for direct indexing based on a number and a set for looking up.
- private final List<String> obfuscationDictionary;
- private final Set<String> obfuscationDictionaryForLookup;
-
- private final AppView<?> appView;
public MinifierMemberNamingStrategy(AppView<?> appView) {
+ super(appView.options().getProguardConfiguration().getObfuscationDictionary(), false);
this.appView = appView;
this.factory = appView.dexItemFactory();
- this.obfuscationDictionary =
- appView.options().getProguardConfiguration().getObfuscationDictionary();
- this.obfuscationDictionaryForLookup = new HashSet<>(this.obfuscationDictionary);
- assert this.obfuscationDictionary != null;
}
@Override
- public DexString next(DexMethod method, MemberNamingInternalState internalState) {
+ public DexString next(DexMethod method, InternalNamingState internalState) {
+ assert checkAllowMemberRenaming(method.holder);
DexEncodedMethod encodedMethod = appView.definitionFor(method);
boolean isDirectOrStatic = encodedMethod.isDirectMethod() || encodedMethod.isStatic();
return getNextName(internalState, isDirectOrStatic);
}
@Override
- public DexString next(DexField field, MemberNamingInternalState internalState) {
+ public DexString next(DexField field, InternalNamingState internalState) {
+ assert checkAllowMemberRenaming(field.holder);
return getNextName(internalState, false);
}
- private DexString getNextName(
- MemberNamingInternalState internalState, boolean isDirectOrStatic) {
- if (internalState.getDictionaryIndex() < obfuscationDictionary.size()) {
- return factory.createString(
- obfuscationDictionary.get(internalState.incrementDictionaryIndex()));
- } else {
- String nextString;
- do {
- int counter = internalState.incrementNameIndex(isDirectOrStatic);
- nextString = StringUtils.numberToIdentifier(counter);
- } while (obfuscationDictionaryForLookup.contains(nextString));
- return factory.createString(nextString);
+ private DexString getNextName(InternalNamingState internalState, boolean isDirectOrStatic) {
+ return factory.createString(nextName(EMPTY_CHAR_ARRAY, internalState, isDirectOrStatic));
+ }
+
+ @Override
+ public DexString getReservedNameOrDefault(
+ DexEncodedMethod method, DexClass holder, DexString defaultValue) {
+ if (!allowMemberRenaming(holder)
+ || holder.accessFlags.isAnnotation()
+ || method.accessFlags.isConstructor()
+ || appView.rootSet().mayNotBeMinified(method.method, appView)) {
+ return method.method.name;
}
+ return defaultValue;
}
@Override
- public boolean breakOnNotAvailable(DexReference source, DexString name) {
- return false;
+ public DexString getReservedNameOrDefault(
+ DexEncodedField field, DexClass holder, DexString defaultValue) {
+ if (holder.isLibraryClass() || appView.rootSet().mayNotBeMinified(field.field, appView)) {
+ return field.field.name;
+ }
+ return defaultValue;
}
@Override
- public boolean noObfuscation(DexReference reference) {
- return appView.rootSet().mayNotBeMinified(reference, appView);
+ public boolean allowMemberRenaming(DexClass holder) {
+ return holder.isProgramClass();
+ }
+
+ public boolean checkAllowMemberRenaming(DexType holder) {
+ DexClass clazz = appView.definitionFor(holder);
+ assert clazz != null && allowMemberRenaming(clazz);
+ return true;
+ }
+
+ @Override
+ public void reportReservationError(DexReference source, DexString name) {
+ assert false;
+ // This should only happen when applymapping is used and will be caught in that strategy.
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 4c2c8cf..895b8c5 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -7,6 +7,8 @@
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;
@@ -14,31 +16,59 @@
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.ClassNamingStrategy;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
-import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
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,
@@ -50,101 +80,63 @@
}
public NamingLens run(Timing timing) {
- timing.begin("mapping classes");
+ timing.begin("MappingClasses");
+ computeMapping(appView.dexItemFactory().objectType, new ArrayDeque<>());
+ timing.end();
- Map<DexType, DexString> mappedNames = new IdentityHashMap<>();
- List<DexClass> mappedClasses = new ArrayList<>();
- Map<DexReference, MemberNaming> memberNames = new IdentityHashMap<>();
- Map<DexType, DexString> syntheticCompanionClasses = new IdentityHashMap<>();
- Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames = new IdentityHashMap<>();
- 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;
+ 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);
}
- DexClass dexClass = appView.definitionFor(type);
- if (dexClass == null) {
- computeDefaultInterfaceMethodMappings(
- type,
- classNaming,
- syntheticCompanionClasses,
- defaultInterfaceMethodImplementationNames);
- continue;
- }
- DexString mappedName = appView.dexItemFactory().createString(classNaming.renamedName);
- DexType mappedType = appView.dexItemFactory().lookupType(mappedName);
- // The mappedType has to be available:
- // - If it is null we have not seen it.
- // - If the mapped type is itself the name is already reserved (by itself).
- // - If the there is no definition for the mapped type we will not get a naming clash.
- // Otherwise, there will be a naming conflict.
- if (mappedType != null && type != mappedType && appView.definitionFor(mappedType) != null) {
- appView
- .options()
- .reporter
- .error(
- ApplyMappingError.mapToExistingClass(
- type.toString(), mappedType.toString(), classNaming.position));
- }
- mappedNames.put(type, mappedName);
mappedClasses.add(dexClass);
- 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);
- });
- 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);
- });
+ 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(mappedNames),
+ 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(),
+ new MinificationPackageNamingStrategy(appView),
mappedClasses);
ClassRenaming classRenaming =
classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
timing.end();
- Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
- interfaces.addAll(appView.appInfo().computeReachableInterfaces(desugaredCallSites));
-
ApplyMappingMemberNamingStrategy nameStrategy =
- new ApplyMappingMemberNamingStrategy(
- memberNames, appView.dexItemFactory(), appView.options().reporter);
+ 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();
@@ -158,7 +150,142 @@
return lens;
}
- private void computeDefaultInterfaceMethodMappings(
+ 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,
@@ -196,88 +323,110 @@
}
}
- static class ApplyMappingClassNamingStrategy implements ClassNamingStrategy {
+ static class ApplyMappingClassNamingStrategy extends MinificationClassNamingStrategy {
private final Map<DexType, DexString> mappings;
+ private final boolean isMinifying;
- ApplyMappingClassNamingStrategy(Map<DexType, DexString> mappings) {
+ ApplyMappingClassNamingStrategy(AppView<?> appView, Map<DexType, DexString> mappings) {
+ super(appView);
this.mappings = mappings;
+ this.isMinifying = appView.options().isMinifying();
}
@Override
- public DexString next(Namespace namespace, DexType type, char[] packagePrefix) {
- return mappings.getOrDefault(type, type.descriptor);
- }
-
- @Override
- public boolean bypassDictionary() {
- return true;
+ 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) {
- // We have an explicit mapping from the proguard map thus everything might have to be renamed.
- return false;
+ 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 implements MemberNamingStrategy {
+ static class ApplyMappingMemberNamingStrategy extends MinifierMemberNamingStrategy {
private final Map<DexReference, MemberNaming> mappedNames;
private final DexItemFactory factory;
private final Reporter reporter;
public ApplyMappingMemberNamingStrategy(
- Map<DexReference, MemberNaming> mappedNames, DexItemFactory factory, Reporter reporter) {
+ AppView<?> appView, Map<DexReference, MemberNaming> mappedNames) {
+ super(appView);
this.mappedNames = mappedNames;
- this.factory = factory;
- this.reporter = reporter;
+ this.factory = appView.dexItemFactory();
+ this.reporter = appView.options().reporter;
}
@Override
- public DexString next(DexMethod method, MemberNamingInternalState internalState) {
- return next(method);
+ 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, MemberNamingInternalState internalState) {
- return next(field);
+ public DexString next(DexField field, InternalNamingState internalState) {
+ assert !mappedNames.containsKey(field);
+ return canMinify(field, field.holder) ? super.next(field, internalState) : field.name;
}
- private DexString next(DexReference reference) {
- if (mappedNames.containsKey(reference)) {
- return factory.createString(mappedNames.get(reference).getRenamedName());
- } else if (reference.isDexMethod()) {
- return reference.asDexMethod().name;
- } else {
- assert reference.isDexField();
- return reference.asDexField().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 boolean breakOnNotAvailable(DexReference source, DexString name) {
- // If we renamed a member to a name that exists in a subtype we should warn that potentially
- // a member lookup may no longer visit its parent.
- MemberNaming memberNaming = mappedNames.get(source);
- assert source.isDexMethod() || source.isDexField();
- ApplyMappingError applyMappingError = ApplyMappingError.mapToExistingMember(
- source.toSourceString(),
- name.toString(),
- memberNaming == null ? Position.UNKNOWN : memberNaming.position);
- if (source.isDexMethod()) {
- reporter.error(applyMappingError);
- } else {
- // TODO(b/128868424) Check if we can remove this warning for fields.
- reporter.warning(applyMappingError);
+ 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 boolean noObfuscation(DexReference reference) {
- // We have an explicit mapping from the proguard map thus everything might have to be renamed.
- return false;
+ 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);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 81b745c..ef9aedd 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -196,114 +196,76 @@
}
private void parseMemberMappings(ClassNaming.Builder classNamingBuilder) throws IOException {
+ MemberNaming lastAddedNaming = null;
MemberNaming activeMemberNaming = null;
- Range previousObfuscatedRange = null;
- boolean previousWasPotentiallySynthesized = false;
- Signature previousSignature = null;
- String previousRenamedName = null;
- boolean lastRound = false;
- for (; ; ) {
- Signature signature = null;
+ Range previousMappedRange = null;
+ do {
Object originalRange = null;
- String renamedName = null;
- Range obfuscatedRange = null;
+ Range mappedRange = null;
- // In the last round we're only here to flush the last line read (which may trigger adding a
- // new MemberNaming) and flush activeMemberNaming, so skip parsing.
- if (!lastRound) {
- if (!Character.isWhitespace(peekCodePoint())) {
- lastRound = true;
- continue;
- }
- skipWhitespace();
- Object maybeRangeOrInt = maybeParseRangeOrInt();
- if (maybeRangeOrInt != null) {
- if (!(maybeRangeOrInt instanceof Range)) {
- throw new ParseException(
- String.format("Invalid obfuscated line number range (%s).", maybeRangeOrInt));
- }
- obfuscatedRange = (Range) maybeRangeOrInt;
- expect(':');
- }
- signature = parseSignature();
- if (peekChar(0) == ':') {
- // This is a mapping or inlining definition
- nextChar();
- originalRange = maybeParseRangeOrInt();
- if (originalRange == null) {
- throw new ParseException("No number follows the colon after the method signature.");
- }
- }
- skipWhitespace();
- skipArrow();
- skipWhitespace();
- renamedName = parseMethodName();
- }
-
- // If this line refers to a member that should be added to classNamingBuilder (as opposed to
- // an inner inlined callee) and it's different from the activeMemberNaming, then flush (add)
- // the current activeMemberNaming and create a new one.
- // We're also entering this in the last round when there's no current line.
- if (previousRenamedName != null
- && (!Objects.equals(previousObfuscatedRange, obfuscatedRange)
- || !Objects.equals(previousRenamedName, renamedName)
- || (originalRange != null && originalRange instanceof Range))) {
- // Flush activeMemberNaming if it's for a different member.
- if (activeMemberNaming != null) {
- if (!activeMemberNaming.getOriginalSignature().equals(previousSignature)) {
- classNamingBuilder.addMemberEntry(activeMemberNaming);
- activeMemberNaming = null;
- } else {
- if (activeMemberNaming.getRenamedName().equals(previousRenamedName)) {
- // The method was potentially synthesized.
- previousWasPotentiallySynthesized = previousObfuscatedRange == null;
- } else {
- assert previousWasPotentiallySynthesized;
- }
- }
- }
- if (activeMemberNaming == null) {
- activeMemberNaming =
- new MemberNaming(previousSignature, previousRenamedName, getPosition());
- }
- }
-
- if (lastRound) {
- if (activeMemberNaming != null) {
- classNamingBuilder.addMemberEntry(activeMemberNaming);
- }
+ // Parse the member line ' x:y:name:z:q -> renamedName'.
+ if (!Character.isWhitespace(peekCodePoint())) {
break;
}
-
- // Interpret what we've just parsed.
- if (obfuscatedRange == null) {
- if (originalRange != null) {
- throw new ParseException("No mapping for original range " + originalRange + ".");
+ skipWhitespace();
+ Object maybeRangeOrInt = maybeParseRangeOrInt();
+ if (maybeRangeOrInt != null) {
+ if (!(maybeRangeOrInt instanceof Range)) {
+ throw new ParseException(
+ String.format("Invalid obfuscated line number range (%s).", maybeRangeOrInt));
}
- // Here we have a line like 'a() -> b' or a field like 'a -> b'
- if (activeMemberNaming != null) {
- classNamingBuilder.addMemberEntry(activeMemberNaming);
- }
- activeMemberNaming = new MemberNaming(signature, renamedName, getPosition());
- } else {
-
- // Note that at this point originalRange may be null which either means, it's the same as
- // the obfuscatedRange (identity mapping) or that it's unknown (source line number
- // information was not available).
- assert signature instanceof MethodSignature;
+ mappedRange = (Range) maybeRangeOrInt;
+ expect(':');
}
+ Signature signature = parseSignature();
+ if (peekChar(0) == ':') {
+ // This is a mapping or inlining definition
+ nextChar();
+ originalRange = maybeParseRangeOrInt();
+ if (originalRange == null) {
+ throw new ParseException("No number follows the colon after the method signature.");
+ }
+ }
+ if (mappedRange == null && originalRange != null) {
+ throw new ParseException("No mapping for original range " + originalRange + ".");
+ }
+
+ skipWhitespace();
+ skipArrow();
+ skipWhitespace();
+ String renamedName = parseMethodName();
if (signature instanceof MethodSignature) {
classNamingBuilder.addMappedRange(
- obfuscatedRange, (MethodSignature) signature, originalRange, renamedName);
+ mappedRange, (MethodSignature) signature, originalRange, renamedName);
}
- previousRenamedName = renamedName;
- previousObfuscatedRange = obfuscatedRange;
- previousSignature = signature;
+ assert mappedRange == null || signature instanceof MethodSignature;
- if (!nextLine()) {
- lastRound = true;
+ // If this line refers to a member that should be added to classNamingBuilder (as opposed to
+ // an inner inlined callee) and it's different from the the previous activeMemberNaming, then
+ // flush (add) the current activeMemberNaming.
+ if (activeMemberNaming != null) {
+ boolean changedName = !activeMemberNaming.getRenamedName().equals(renamedName);
+ boolean changedMappedRange = !Objects.equals(previousMappedRange, mappedRange);
+ boolean notAdded =
+ lastAddedNaming == null
+ || !lastAddedNaming.getOriginalSignature().equals(activeMemberNaming.signature);
+ if (changedName || previousMappedRange == null || (changedMappedRange && notAdded)) {
+ classNamingBuilder.addMemberEntry(activeMemberNaming);
+ lastAddedNaming = activeMemberNaming;
+ }
+ }
+ activeMemberNaming = new MemberNaming(signature, renamedName, getPosition());
+ previousMappedRange = mappedRange;
+ } while (nextLine());
+
+ if (activeMemberNaming != null) {
+ boolean notAdded =
+ lastAddedNaming == null
+ || !lastAddedNaming.getOriginalSignature().equals(activeMemberNaming.signature);
+ if (previousMappedRange == null || notAdded) {
+ classNamingBuilder.addMemberEntry(activeMemberNaming);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
index 9885681..3e733fb 100644
--- a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.naming;
import com.android.tools.r8.graph.AppView;
-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.naming.ReservedFieldNamingState.InternalState;
@@ -25,10 +24,6 @@
return internalState != null && internalState.isReserved(name);
}
- public void markReservedDirectly(DexField field) {
- markReservedDirectly(field.name, field.type);
- }
-
public void markReservedDirectly(DexString name, DexType type) {
getOrCreateInternalState(type).markReservedDirectly(name);
}
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index f6eabad..7b3caa7 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -43,12 +43,15 @@
this.reporter = appView.options().reporter;
}
- public void run() {
+ public void run(Iterable<? extends DexClass> classes) {
final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector();
final GenericSignatureParser<DexType> genericSignatureParser =
new GenericSignatureParser<>(genericSignatureCollector);
-
- for (DexClass clazz : appView.appInfo().classes()) {
+ // classes may not be the same as appInfo().classes() if applymapping is used on classpath
+ // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
+ // either ProgramClass or has a mapping. This is then transitively called inside the
+ // ClassNameMinifier.
+ for (DexClass clazz : classes) {
clazz.annotations =
rewriteGenericSignatures(
clazz.annotations,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index d46a976..9d0adec 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -69,6 +69,7 @@
private boolean overloadAggressively;
private boolean keepRuleSynthesisForRecompilation = false;
private boolean configurationDebugging = false;
+ private boolean dontUseMixedCaseClassnames = false;
private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
this.dexItemFactory = dexItemFactory;
@@ -275,6 +276,10 @@
this.configurationDebugging = configurationDebugging;
}
+ public void setDontUseMixedCaseClassnames(boolean dontUseMixedCaseClassnames) {
+ this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
+ }
+
/**
* This synthesizes a set of keep rules that are necessary in order to be able to successfully
* recompile the generated dex files with the same keep rules.
@@ -332,7 +337,8 @@
adaptResourceFilenames.build(),
adaptResourceFileContents.build(),
keepDirectories.build(),
- configurationDebugging);
+ configurationDebugging,
+ dontUseMixedCaseClassnames);
reporter.failIfPendingErrors();
@@ -400,6 +406,7 @@
private final ProguardPathFilter adaptResourceFileContents;
private final ProguardPathFilter keepDirectories;
private final boolean configurationDebugging;
+ private final boolean dontUseMixedCaseClassnames;
private ProguardConfiguration(
String parsedConfiguration,
@@ -438,7 +445,8 @@
ProguardPathFilter adaptResourceFilenames,
ProguardPathFilter adaptResourceFileContents,
ProguardPathFilter keepDirectories,
- boolean configurationDebugging) {
+ boolean configurationDebugging,
+ boolean dontUseMixedCaseClassnames) {
this.parsedConfiguration = parsedConfiguration;
this.dexItemFactory = factory;
this.injars = ImmutableList.copyOf(injars);
@@ -476,6 +484,7 @@
this.adaptResourceFileContents = adaptResourceFileContents;
this.keepDirectories = keepDirectories;
this.configurationDebugging = configurationDebugging;
+ this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
}
/**
@@ -638,6 +647,10 @@
return configurationDebugging;
}
+ public boolean hasDontUseMixedCaseClassnames() {
+ return dontUseMixedCaseClassnames;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 45105c2..a3d58a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -61,7 +61,6 @@
private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
"forceprocessing",
- "dontusemixedcaseclassnames",
"dontpreverify",
"experimentalshrinkunusedprotofields",
"filterlibraryjarswithorginalprogramjars",
@@ -398,6 +397,8 @@
configurationBuilder.addRule(parseIfRule(optionStart));
} else if (acceptString("addconfigurationdebugging")) {
configurationBuilder.setConfigurationDebugging(true);
+ } else if (acceptString("dontusemixedcaseclassnames")) {
+ configurationBuilder.setDontUseMixedCaseClassnames(true);
} else {
String unknownOption = acceptString();
String devMessage = "";
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index c5b5d6b..295de13 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -17,10 +17,6 @@
public static final String[] EMPTY_ARRAY = {};
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
- private static final char[] IDENTIFIER_LETTERS
- = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".toCharArray();
- private static final int NUMBER_OF_LETTERS = IDENTIFIER_LETTERS.length;
-
public enum BraceType {
PARENS,
SQUARE,
@@ -257,40 +253,6 @@
return Arrays.toString(digest);
}
- public static String numberToIdentifier(int nameCount) {
- return numberToIdentifier(nameCount, EMPTY_CHAR_ARRAY, false);
- }
-
- public static String numberToIdentifier(int nameCount, char[] prefix) {
- return numberToIdentifier(nameCount, prefix, false);
- }
-
- public static String numberToIdentifier(int nameCount, char[] prefix, boolean addSemicolon) {
- // TODO(herhut): Add support for using numbers.
- int size = addSemicolon ? 1 : 0;
- int number = nameCount;
- while (number >= NUMBER_OF_LETTERS) {
- number /= NUMBER_OF_LETTERS;
- size++;
- }
- size++;
- char characters[] = Arrays.copyOfRange(prefix, 0, prefix.length + size);
- number = nameCount;
-
- int i = prefix.length;
- while (number >= NUMBER_OF_LETTERS) {
- characters[i++] = IDENTIFIER_LETTERS[number % NUMBER_OF_LETTERS];
- number /= NUMBER_OF_LETTERS;
- }
- characters[i++] = IDENTIFIER_LETTERS[number - 1];
- if (addSemicolon) {
- characters[i++] = ';';
- }
- assert i == characters.length;
-
- return new String(characters);
- }
-
public static String times(String string, int count) {
StringBuilder builder = new StringBuilder();
while (--count >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
new file mode 100644
index 0000000..b66ca5d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
@@ -0,0 +1,73 @@
+// 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.utils;
+
+import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
+
+import java.util.Arrays;
+
+public class SymbolGenerationUtils {
+
+ public enum MixedCasing {
+ USE_MIXED_CASE,
+ DONT_USE_MIXED_CASE
+ }
+
+ // These letters are used not creating fresh names to output and not for parsing dex/class files.
+ private static final char[] IDENTIFIER_CHARACTERS =
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+ private static final int NUMBER_OF_CHARACTERS = IDENTIFIER_CHARACTERS.length;
+ private static final int NUMBER_OF_CHARACTERS_MINUS_CAPITAL_LETTERS = NUMBER_OF_CHARACTERS - 26;
+ private static final int NON_ALLOWED_FIRST_CHARACTERS = 10;
+
+ public static String numberToIdentifier(int nameCount, MixedCasing mixedCasing) {
+ return numberToIdentifier(nameCount, mixedCasing, EMPTY_CHAR_ARRAY, false);
+ }
+
+ public static String numberToIdentifier(int nameCount, MixedCasing mixedCasing, char[] prefix) {
+ return numberToIdentifier(nameCount, mixedCasing, prefix, false);
+ }
+
+ public static String numberToIdentifier(
+ int nameCount, MixedCasing mixedCasing, char[] prefix, boolean addSemicolon) {
+ int size = 1;
+ int number = nameCount;
+ int maximumNumberOfCharacters =
+ mixedCasing == MixedCasing.USE_MIXED_CASE
+ ? NUMBER_OF_CHARACTERS
+ : NUMBER_OF_CHARACTERS_MINUS_CAPITAL_LETTERS;
+ int firstNumberOfCharacters = maximumNumberOfCharacters - NON_ALLOWED_FIRST_CHARACTERS;
+ int availableCharacters = firstNumberOfCharacters;
+ // We first do an initial computation to find the size of the resulting string to allocate an
+ // array that will fit in length.
+ while (number > availableCharacters) {
+ number = (number - 1) / availableCharacters;
+ availableCharacters = maximumNumberOfCharacters;
+ size++;
+ }
+ size += addSemicolon ? 1 : 0;
+ char characters[] = Arrays.copyOfRange(prefix, 0, prefix.length + size);
+ number = nameCount;
+
+ int i = prefix.length;
+ availableCharacters = firstNumberOfCharacters;
+ int firstLetterPadding = NON_ALLOWED_FIRST_CHARACTERS;
+ while (number > availableCharacters) {
+ characters[i++] =
+ IDENTIFIER_CHARACTERS[(number - 1) % availableCharacters + firstLetterPadding];
+ number = (number - 1) / availableCharacters;
+ availableCharacters = maximumNumberOfCharacters;
+ firstLetterPadding = 0;
+ }
+ characters[i++] = IDENTIFIER_CHARACTERS[number - 1 + firstLetterPadding];
+ if (addSemicolon) {
+ characters[i++] = ';';
+ }
+ assert i == characters.length;
+ assert !Character.isDigit(characters[prefix.length]);
+
+ return new String(characters);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
new file mode 100644
index 0000000..4f3a868
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
@@ -0,0 +1,79 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassObfuscationDictionaryDuplicateTest extends TestBase {
+
+ public static class A {}
+
+ public static class B {}
+
+ public static class C {
+ public static void main(String[] args) {
+ System.out.print("HELLO WORLD!");
+ }
+ }
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public ClassObfuscationDictionaryDuplicateTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws IOException, CompilationFailedException, ExecutionException {
+ Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+ FileUtils.writeTextFile(dictionary, "a");
+
+ Set<String> finalNames = new HashSet<>();
+ finalNames.add(A.class.getPackage().getName() + ".a");
+ finalNames.add(B.class.getPackage().getName() + ".b");
+
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, C.class)
+ .noTreeShaking()
+ .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+ .addKeepMainRule(C.class)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .run(parameters.getRuntime(), C.class)
+ .assertSuccessWithOutput("HELLO WORLD!")
+ .inspect(
+ inspector -> {
+ ClassSubject clazzA = inspector.clazz(A.class);
+ assertThat(clazzA, isPresent());
+ assertTrue(finalNames.contains(clazzA.getFinalName()));
+ finalNames.remove(clazzA.getFinalName());
+ ClassSubject clazzB = inspector.clazz(B.class);
+ assertThat(clazzB, isPresent());
+ assertTrue(finalNames.contains(clazzB.getFinalName()));
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
new file mode 100644
index 0000000..55b3a9a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
@@ -0,0 +1,204 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MinificationMixedCaseAndNumbersTest extends TestBase {
+
+ private static final int NUMBER_OF_MINIFIED_CLASSES = 60;
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public MinificationMixedCaseAndNumbersTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNaming() throws ExecutionException, CompilationFailedException, IOException {
+ Set<String> allowedNames = new HashSet<>();
+ allowedNames.add("com.android.tools.r8.naming.MinificationMixedCaseAndNumbersTest$Main");
+ for (int i = 1; i < NUMBER_OF_MINIFIED_CLASSES; i++) {
+ String newString =
+ SymbolGenerationUtils.numberToIdentifier(i, MixedCasing.DONT_USE_MIXED_CASE);
+ assertFalse(Character.isDigit(newString.charAt(0)));
+ allowedNames.add("com.android.tools.r8.naming." + newString);
+ }
+ testForR8(parameters.getBackend())
+ .addInnerClasses(MinificationMixedCaseAndNumbersTest.class)
+ .addKeepMainRule(Main.class)
+ .noTreeShaking()
+ .addKeepRules("-dontusemixedcaseclassnames")
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(
+ inspector -> {
+ List<FoundClassSubject> foundClassSubjects = new ArrayList<>(inspector.allClasses());
+ foundClassSubjects.forEach(
+ foundClazz -> {
+ assertTrue(allowedNames.contains(foundClazz.getFinalName()));
+ allowedNames.remove(foundClazz.getFinalName());
+ });
+ });
+ // The first identifier to use a number is 27 for MixedCasing.DONT_USE_MIXED_CASE.
+ assertTrue(allowedNames.isEmpty());
+ assertEquals(
+ "a0", SymbolGenerationUtils.numberToIdentifier(27, MixedCasing.DONT_USE_MIXED_CASE));
+ assertEquals(
+ "zz", SymbolGenerationUtils.numberToIdentifier(962, MixedCasing.DONT_USE_MIXED_CASE));
+ assertEquals(
+ "a00", SymbolGenerationUtils.numberToIdentifier(963, MixedCasing.DONT_USE_MIXED_CASE));
+ }
+
+ public static class A {}
+
+ public static class B {}
+
+ public static class C {}
+
+ public static class D {}
+
+ public static class E {}
+
+ public static class F {}
+
+ public static class G {}
+
+ public static class H {}
+
+ public static class I {}
+
+ public static class J {}
+
+ public static class K {}
+
+ public static class L {}
+
+ public static class M {}
+
+ public static class N {}
+
+ public static class O {}
+
+ public static class P {}
+
+ public static class Q {}
+
+ public static class R {}
+
+ public static class S {}
+
+ public static class T {}
+
+ public static class U {}
+
+ public static class V {}
+
+ public static class W {}
+
+ public static class X {}
+
+ public static class Y {}
+
+ public static class Z {}
+
+ public static class a {}
+
+ public static class b {}
+
+ public static class c {}
+
+ public static class d {}
+
+ public static class e {}
+
+ public static class f {}
+
+ public static class g {}
+
+ public static class h {}
+
+ public static class i {}
+
+ public static class j {}
+
+ public static class k {}
+
+ public static class l {}
+
+ public static class m {}
+
+ public static class n {}
+
+ public static class o {}
+
+ public static class p {}
+
+ public static class q {}
+
+ public static class r {}
+
+ public static class s {}
+
+ public static class t {}
+
+ public static class u {}
+
+ public static class v {}
+
+ public static class w {}
+
+ public static class x {}
+
+ public static class y {}
+
+ public static class z {}
+
+ public static class AA {}
+
+ public static class AB {}
+
+ public static class AC {}
+
+ public static class AD {}
+
+ public static class AE {}
+
+ public static class AF {}
+
+ public static class AG {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println("HELLO WORLD");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
new file mode 100644
index 0000000..f10947e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
@@ -0,0 +1,65 @@
+// 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 static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.keeppackagenames.Top;
+import com.android.tools.r8.naming.packageobfucationdict.A;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PackageObfuscationDictionaryDuplicateTest extends TestBase {
+
+ public static class C {
+ public static void main(String[] args) {
+ System.out.print("HELLO WORLD!");
+ }
+ }
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public PackageObfuscationDictionaryDuplicateTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws IOException, CompilationFailedException, ExecutionException {
+ Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+ FileUtils.writeTextFile(dictionary, "a");
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(Top.class, A.class, C.class)
+ .noTreeShaking()
+ .addKeepRules("-packageobfuscationdictionary " + dictionary.toString())
+ .addKeepMainRule(C.class)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .run(parameters.getRuntime(), C.class)
+ .assertSuccessWithOutput("HELLO WORLD!")
+ .inspect(
+ inspector -> {
+ ClassSubject clazzTop = inspector.clazz(Top.class);
+ assertTrue(clazzTop.getFinalName().endsWith(".a.a"));
+ ClassSubject clazzA = inspector.clazz(A.class);
+ assertTrue(clazzA.getFinalName().endsWith(".b.a"));
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
index 91da18d..4c265ef 100644
--- a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
+++ b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
@@ -162,4 +162,34 @@
assertEquals(2, ((TextPosition) diagnostic.getPosition()).getLine());
}
}
+
+ @Test
+ public void testInliningFrames() throws IOException {
+ Path applyMappingFile =
+ getApplyMappingFile(
+ "A.B.C -> a:",
+ " int foo(A) -> a",
+ " 1:2:int bar(A):3:4 -> a",
+ " 1:2:int baz(B):3 -> a");
+ TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+ Reporter reporter = new Reporter(testDiagnosticMessages);
+ SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
+ }
+
+ @Test
+ public void testDuplicateInliningFrames() throws IOException {
+ Path applyMappingFile =
+ getApplyMappingFile(
+ "A.B.C -> a:",
+ " int foo(Z) -> a",
+ " 1:1:int qux(A):3:3 -> a",
+ " 1:1:int bar(A):3 -> a",
+ " 2:2:int qux(A):3:3 -> a",
+ " 2:2:int bar(A):4 -> a",
+ " 3:3:int bar(A):5:5 -> a",
+ " int qux(C) -> a");
+ TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+ Reporter reporter = new Reporter(testDiagnosticMessages);
+ SeedMapper.seedMapperFromFile(reporter, applyMappingFile);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
index 500dbf5..2587730 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Assume;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -99,7 +98,6 @@
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
- @Ignore("b/126503704")
@Test
public void devirtualizingNoRenamingOfOverriddenNotKeptInterfaceMethods() throws Exception {
R8TestCompileResult libraryResult =
@@ -131,7 +129,6 @@
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
- @Ignore("b/126503704")
@Test
public void devirtualizingNoRenamingOfOverriddenKeptInterfaceMethods() throws Exception {
R8TestCompileResult libraryResult =
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
new file mode 100644
index 0000000..b920732
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMinificationTest.java
@@ -0,0 +1,133 @@
+// 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.applymapping;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingMinificationTest extends TestBase {
+
+ @NeverClassInline
+ public static class A {
+ public int fieldA = 1;
+ public int fieldB = 2;
+
+ @NeverInline
+ public void methodA() {
+ System.out.println("A.methodA");
+ }
+
+ @NeverInline
+ public void methodB() {
+ System.out.println("A.methodB");
+ }
+
+ @NeverInline
+ public void methodC() {
+ System.out.println("A.methodC");
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+ @NeverInline
+ public void foo() {
+ System.out.println("B.foo");
+ }
+ }
+
+ public static class C {
+
+ public static void main(String[] args) {
+ System.out.println(new A().fieldA);
+ System.out.println(new A().fieldB);
+ new A().methodA();
+ new A().methodB();
+ new A().methodC();
+ new B().foo();
+ }
+ }
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public ApplyMappingMinificationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testApplyMappingFollowedByMinification()
+ throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+ String[] pgMap =
+ new String[] {
+ A.class.getTypeName() + " -> a:", " int fieldA -> a", " void methodA() -> a"
+ };
+ R8TestRunResult runResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ApplyMappingMinificationTest.class)
+ .addKeepMainRule(C.class)
+ .addKeepMethodRules(methodFromMethod(A.class.getDeclaredMethod("methodC")))
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .addApplyMapping(StringUtils.lines(pgMap))
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), C.class)
+ .assertSuccessWithOutputLines("1", "2", "A.methodA", "A.methodB", "A.methodC", "B.foo")
+ .inspect(
+ inspector -> {
+ ClassSubject clazzB = inspector.clazz(B.class);
+ assertThat(clazzB, isPresent());
+ assertTrue(clazzB.isRenamed());
+ ClassSubject clazzA = inspector.clazz(A.class);
+ assertThat(clazzA, isPresent());
+ assertEquals("a", clazzA.getFinalName());
+ FieldSubject fieldA = clazzA.uniqueFieldWithName("fieldA");
+ assertThat(fieldA, isPresent());
+ assertEquals("a", fieldA.getFinalName());
+ MethodSubject methodA = clazzA.uniqueMethodWithName("methodA");
+ assertThat(methodA, isPresent());
+ assertEquals("a", methodA.getFinalName());
+ FieldSubject fieldB = clazzA.uniqueFieldWithName("fieldB");
+ assertThat(fieldB, isPresent());
+ assertTrue(fieldB.isRenamed());
+ MethodSubject methodB = clazzA.uniqueMethodWithName("methodB");
+ assertThat(methodB, isPresent());
+ assertTrue(methodB.isRenamed());
+ MethodSubject methodC = clazzA.uniqueMethodWithName("methodC");
+ assertThat(methodC, isPresent());
+ assertFalse(methodC.isRenamed());
+ });
+ // Ensure that the proguard map is extended with all the new minified names.
+ for (String pgLine : pgMap) {
+ runResult.proguardMap().contains(pgLine);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
new file mode 100644
index 0000000..ff87272
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
@@ -0,0 +1,58 @@
+// 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.applymapping;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingRotateNameClashTest extends TestBase {
+
+ public static class A {}
+
+ public static class B {}
+
+ public static class C {
+ public static void main(String[] args) {
+ System.out.print("HELLO WORLD!");
+ }
+ }
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public ApplyMappingRotateNameClashTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test_b131532229() throws ExecutionException, CompilationFailedException, IOException {
+ testForR8(parameters.getBackend())
+ .addLibraryClasses(A.class, B.class)
+ .addLibraryFiles(TestBase.runtimeJar(parameters.getBackend()))
+ .addProgramClasses(C.class)
+ .addKeepMainRule(C.class)
+ .noTreeShaking()
+ .addApplyMapping(
+ StringUtils.lines(
+ A.class.getTypeName() + " -> " + B.class.getTypeName() + ":",
+ B.class.getTypeName() + " -> " + A.class.getTypeName() + ":"))
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), C.class)
+ .assertSuccessWithOutput("HELLO WORLD!");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index 7989a29..cc4e99c 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibraryBase;
+import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibraryInterface;
import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.LibrarySubclass;
import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.ProgramClass;
import com.android.tools.r8.naming.applymapping.ApplyMappingVirtualInvokeTest.TestClasses.ProgramSubclass;
@@ -20,7 +21,6 @@
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -28,17 +28,28 @@
@RunWith(Parameterized.class)
public class ApplyMappingVirtualInvokeTest extends TestBase {
- public static final String EXPECTED_PROGRAM = StringUtils.lines("LibraryBase.foo");
+ public static final String EXPECTED_PROGRAM =
+ StringUtils.lines("LibraryBase.foo", "LibraryBase.bar");
public static final String EXPECTED_PROGRAM_SUBCLASS =
- StringUtils.lines("ProgramSubclass.foo", "LibraryBase.foo");
+ StringUtils.lines(
+ "ProgramSubclass.foo", "LibraryBase.foo", "ProgramSubclass.bar", "LibraryBase.bar");
public static class TestClasses {
- public static class LibraryBase {
+ public interface LibraryInterface {
+ void bar();
+ }
- void foo() {
+ public static class LibraryBase implements LibraryInterface {
+
+ public void foo() {
System.out.println("LibraryBase.foo");
}
+
+ @Override
+ public void bar() {
+ System.out.println("LibraryBase.bar");
+ }
}
public static class LibrarySubclass extends LibraryBase {}
@@ -47,6 +58,7 @@
public static void main(String[] args) {
new LibrarySubclass().foo();
+ new LibrarySubclass().bar();
}
}
@@ -54,17 +66,26 @@
public static void main(String[] args) {
new ProgramSubclass().foo();
+ new ProgramSubclass().bar();
}
@Override
- void foo() {
+ public void foo() {
System.out.println("ProgramSubclass.foo");
super.foo();
}
+
+ @Override
+ public void bar() {
+ System.out.println("ProgramSubclass.bar");
+ super.bar();
+ }
}
}
- private static final Class<?>[] LIBRARY_CLASSES = {LibraryBase.class, LibrarySubclass.class};
+ private static final Class<?>[] LIBRARY_CLASSES = {
+ LibraryInterface.class, LibraryBase.class, LibrarySubclass.class
+ };
private static final Class<?>[] PROGRAM_CLASSES = {ProgramClass.class, ProgramSubclass.class};
private final TestParameters parameters;
@@ -77,13 +98,15 @@
this.parameters = parameters;
}
- private static Function<Backend, R8TestCompileResult> compilationResults =
+ private static Function<TestParameters, R8TestCompileResult> compilationResults =
memoizeFunction(ApplyMappingVirtualInvokeTest::compile);
- private static R8TestCompileResult compile(Backend backend) throws CompilationFailedException {
- return testForR8(getStaticTemp(), backend)
+ private static R8TestCompileResult compile(TestParameters parameters)
+ throws CompilationFailedException {
+ return testForR8(getStaticTemp(), parameters.getBackend())
.addProgramClasses(LIBRARY_CLASSES)
.addKeepClassAndMembersRulesWithAllowObfuscation(LIBRARY_CLASSES)
+ .setMinApi(parameters.getRuntime())
.addOptionsModification(
options -> {
options.enableInlining = false;
@@ -116,14 +139,12 @@
.assertSuccessWithOutput(EXPECTED_PROGRAM_SUBCLASS);
}
- @Ignore("b/128868424")
@Test
public void testProgramClass()
throws ExecutionException, CompilationFailedException, IOException {
runTest(ProgramClass.class, EXPECTED_PROGRAM);
}
- @Ignore("b/128868424")
@Test
public void testProgramSubClass()
throws ExecutionException, IOException, CompilationFailedException {
@@ -132,7 +153,7 @@
private void runTest(Class<?> main, String expected)
throws ExecutionException, IOException, CompilationFailedException {
- R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters.getBackend());
+ R8TestCompileResult libraryCompileResult = compilationResults.apply(parameters);
Path outPath = temp.newFile("out.zip").toPath();
libraryCompileResult.writeToZip(outPath);
testForR8(parameters.getBackend())
@@ -140,7 +161,7 @@
.noMinification()
.addProgramClasses(PROGRAM_CLASSES)
.addApplyMapping(libraryCompileResult.getProguardMap())
- .addLibraryClasses(LIBRARY_CLASSES)
+ .addClasspathClasses(LIBRARY_CLASSES)
.addLibraryFiles(runtimeJar(parameters.getBackend()))
.setMinApi(parameters.getRuntime())
.compile()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
index 3bcc25c..58cdef6 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
@@ -99,6 +99,7 @@
.addProgramClasses(ProgramClass.class)
.addClasspathClasses(LibraryInterface.class)
.addApplyMapping(libraryResult.getProguardMap())
+ .addKeepMainRule(ProgramClass.class)
.setMinApi(parameters.getRuntime())
.compile()
.addRunClasspathFiles(libraryResult.writeToZip())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
index 0fdbe2d..c2f0b8f 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
@@ -20,7 +20,6 @@
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -140,6 +139,7 @@
.addKeepMainRule(MAIN)
.addKeepRules("-applymapping " + mappingFile)
.noTreeShaking()
+ .noMinification()
.compile()
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -181,18 +181,6 @@
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
- private void testR8_minifiedLibraryJar(Path mappingFile) throws Exception {
- testForR8(Backend.DEX)
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar)
- .addProgramFiles(prgJarThatUsesMinifiedLib)
- .addKeepMainRule(MAIN)
- .addKeepRules("-applymapping " + mappingFile)
- .noTreeShaking()
- .compile()
- .run(MAIN)
- .assertSuccessWithOutput(EXPECTED_OUTPUT);
- }
-
@Test
public void testProguard_prgClassRenamedToExistingPrgClass() throws Exception {
FileUtils.writeTextFile(mappingFile, mappingToAlreadyMappedName());
@@ -432,11 +420,4 @@
assertThat(e.getMessage(), containsString("can't find superclass or interface A"));
}
}
-
- @Ignore("b/121305642")
- @Test
- public void testR8_minifiedLib() throws Exception {
- FileUtils.writeTextFile(mappingFile, invertedMapping());
- testR8_minifiedLibraryJar(mappingFile);
- }
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
index f64424e..ec50858 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
@@ -29,7 +29,6 @@
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -70,7 +69,6 @@
out = temp.newFolder("out").toPath();
}
- @Ignore("b/128516926")
@Test
public void test044_apply() throws Exception {
Path flag =
diff --git a/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java b/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
new file mode 100644
index 0000000..012140e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
@@ -0,0 +1,7 @@
+// 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.packageobfucationdict;
+
+public class A {}