Change ProguardMapMinifier to handle applymapping use cases
This is the major change for applymapping. The change handles:
- Tracking all non-private members and propegate all names down the
type hierarchy.
- Allow for renaming both in classpath and program classes.
- Rewrite generic type signatures.
Bug: 126503704
Bug: 131532229
Bug: 128868424
Bug: 121305642
Bug: 128516926
Change-Id: Icb98459ba1b837a3e1cd5268d087cd446d8d0597
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e01b4ed..0e69108 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -686,8 +686,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/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 10430cc..8018a2d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -128,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");
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 633a179..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 (strategy.isReserved(field, clazz)) {
+ 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);
+ }
}
}
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 67298e2..d8cb81d 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -50,10 +50,15 @@
DexEncodedField encodedField = appView.appInfo().resolveField(field);
if (encodedField != null) {
DexClass clazz = appView.definitionFor(encodedField.field.holder);
- if (clazz == null || strategy.isReserved(encodedField, clazz)) {
+ if (clazz == null) {
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);
}
@@ -93,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/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index 6510d6c..f33027b 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -18,11 +18,13 @@
DexString next(DexField field, InternalNamingState internalState);
- boolean breakOnNotAvailable(DexReference source, DexString name);
+ DexString getReservedNameOrDefault(
+ DexEncodedMethod method, DexClass holder, DexString defaultValue);
- boolean isReserved(DexEncodedMethod method, DexClass holder);
-
- boolean isReserved(DexEncodedField field, DexClass holder);
+ DexString getReservedNameOrDefault(
+ DexEncodedField field, DexClass holder, DexString defaultValue);
boolean allowMemberRenaming(DexClass holder);
+
+ 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 b5b32b2..ba458bc 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -72,9 +72,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
@@ -179,10 +179,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,26 +209,37 @@
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, 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);
}
}
@@ -272,8 +285,37 @@
DexClass holder = appView.definitionFor(type);
if (holder != null) {
for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) {
- if (strategy.isReserved(method, holder)) {
- 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);
+ }
}
}
}
@@ -281,11 +323,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 8b9d042..6b48304 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -8,11 +8,9 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
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> {
@@ -36,7 +34,7 @@
this.strategy = strategy;
}
- public MethodNamingState<KeyType> createChild() {
+ MethodNamingState<KeyType> createChild() {
return new MethodNamingState<>(this, keyTransform, strategy);
}
@@ -67,7 +65,7 @@
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) {
@@ -77,13 +75,13 @@
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);
+ 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);
if (state == null) {
@@ -92,7 +90,16 @@
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);
+ if (state == null) {
+ return null;
+ }
+ return state.getReservedOriginalName(name);
+ }
+
+ boolean isAvailable(DexProto proto, DexString candidate) {
KeyType key = keyTransform.apply(proto);
InternalState state = findInternalStateFor(key);
if (state == null) {
@@ -101,7 +108,7 @@
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);
state.addRenaming(original, newName);
@@ -136,7 +143,7 @@
private static final int INITIAL_DICTIONARY_INDEX = 0;
private final InternalState parentInternalState;
- private Set<DexString> reservedNames = null;
+ private Map<DexString, DexString> reservedNames = null;
private Map<DexString, DexString> renamings = null;
private int virtualNameCount;
private int directNameCount = 0;
@@ -153,21 +160,32 @@
}
private boolean isReserved(DexString name) {
- return (reservedNames != null && reservedNames.contains(name))
+ return (reservedNames != null && reservedNames.containsKey(name))
|| (parentInternalState != null && parentInternalState.isReserved(name));
}
+ private DexString getReservedOriginalName(DexString name) {
+ DexString result = null;
+ if (reservedNames != null) {
+ result = reservedNames.get(name);
+ }
+ if (result == null && parentInternalState != null) {
+ result = parentInternalState.getReservedOriginalName(name);
+ }
+ return result;
+ }
+
private boolean isAvailable(DexString name) {
return !(renamings != null && renamings.containsValue(name))
- && !(reservedNames != null && reservedNames.contains(name))
+ && !(reservedNames != null && reservedNames.containsKey(name))
&& (parentInternalState == null || parentInternalState.isAvailable(name));
}
- void reserveName(DexString name) {
+ void reserveName(DexString name, DexString originalName) {
if (reservedNames == null) {
- reservedNames = Sets.newIdentityHashSet();
+ reservedNames = new HashMap<>();
}
- reservedNames.add(name);
+ reservedNames.put(name, originalName);
}
@Override
@@ -217,7 +235,7 @@
DexString name;
do {
name = strategy.next(source, this);
- } while (!isAvailable(name) && !strategy.breakOnNotAvailable(source, name));
+ } while (!isAvailable(name));
return name;
}
@@ -275,11 +293,11 @@
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();
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 c6ffef4..a3d15a2 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -185,23 +185,24 @@
}
@Override
- public boolean breakOnNotAvailable(DexReference source, DexString name) {
- return false;
- }
-
- @Override
- public boolean isReserved(DexEncodedMethod method, DexClass holder) {
+ public DexString getReservedNameOrDefault(
+ DexEncodedMethod method, DexClass holder, DexString defaultValue) {
if (!allowMemberRenaming(holder)
|| holder.accessFlags.isAnnotation()
- || method.accessFlags.isConstructor()) {
- return true;
+ || method.accessFlags.isConstructor()
+ || appView.rootSet().mayNotBeMinified(method.method, appView)) {
+ return method.method.name;
}
- return appView.rootSet().mayNotBeMinified(method.method, appView);
+ return defaultValue;
}
@Override
- public boolean isReserved(DexEncodedField field, DexClass holder) {
- return holder.isLibraryClass() || appView.rootSet().mayNotBeMinified(field.field, appView);
+ 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
@@ -214,5 +215,11 @@
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 784dc8b..6efddeb 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -28,18 +28,46 @@
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,
@@ -51,70 +79,34 @@
}
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,
@@ -129,23 +121,21 @@
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();
@@ -159,7 +149,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,
@@ -224,65 +349,57 @@
private final Reporter reporter;
public ApplyMappingMemberNamingStrategy(
- Map<DexReference, MemberNaming> mappedNames, DexItemFactory factory, Reporter reporter) {
+ AppView<?> appView, Map<DexReference, MemberNaming> mappedNames) {
this.mappedNames = mappedNames;
- this.factory = factory;
- this.reporter = reporter;
+ this.factory = appView.dexItemFactory();
+ this.reporter = appView.options().reporter;
}
@Override
public DexString next(DexMethod method, InternalNamingState internalState) {
- return next(method);
+ assert !mappedNames.containsKey(method);
+ return method.name;
}
@Override
public DexString next(DexField field, InternalNamingState internalState) {
- return next(field);
+ assert !mappedNames.containsKey(field);
+ return 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;
+ @Override
+ public DexString getReservedNameOrDefault(
+ DexEncodedMethod method, DexClass holder, DexString nullValue) {
+ if (mappedNames.containsKey(method.method)) {
+ return factory.createString(mappedNames.get(method.method).getRenamedName());
}
+ return nullValue;
}
@Override
- public 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(
+ DexEncodedField field, DexClass holder, DexString nullValue) {
+ if (mappedNames.containsKey(field.field)) {
+ return factory.createString(mappedNames.get(field.field).getRenamedName());
}
- return true;
- }
-
- @Override
- public boolean isReserved(DexEncodedMethod method, DexClass holder) {
- return false;
- }
-
- @Override
- public boolean isReserved(DexEncodedField field, DexClass holder) {
- return false;
+ return nullValue;
}
@Override
public boolean allowMemberRenaming(DexClass holder) {
return true;
}
+
+ @Override
+ public void reportReservationError(DexReference source, DexString name) {
+ MemberNaming memberNaming = mappedNames.get(source);
+ assert source.isDexMethod() || source.isDexField();
+ ApplyMappingError applyMappingError =
+ ApplyMappingError.mapToExistingMember(
+ source.toSourceString(),
+ name.toString(),
+ memberNaming == null ? Position.UNKNOWN : memberNaming.position);
+ reporter.error(applyMappingError);
+ }
}
}
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/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/ApplyMappingRotateNameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
index dee54cb..3526829 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingRotateNameClashTest.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -41,7 +40,6 @@
}
@Test
- @Ignore("b/131532229")
public void test_b131532229() throws ExecutionException, CompilationFailedException, IOException {
testForR8(parameters.getBackend())
.addLibraryClasses(A.class, B.class)
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/shared/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/NameClashTest.java
index 7dd567a..d3df8d3 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;
@@ -139,6 +138,7 @@
.addKeepMainRule(MAIN)
.addKeepRules("-applymapping " + mappingFile)
.noTreeShaking()
+ .noMinification()
.compile()
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -180,18 +180,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());
@@ -431,11 +419,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 =