Merge "Add classlib and segments tool"
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 14dd1ae..aafe941 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.shaking.DiscardedChecker;
import com.android.tools.r8.shaking.Enqueuer;
@@ -80,9 +80,9 @@
}
// Print -whyareyoukeeping results if any.
if (whyAreYouKeepingConsumer != null) {
- for (DexDefinition definition : mainDexRootSet.reasonAsked) {
+ for (DexReference reference : mainDexRootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(definition), System.out);
+ enqueuer.getGraphNode(reference), System.out);
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 75b0c81..186caee 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -17,8 +17,8 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.conversion.IRConverter;
@@ -403,8 +403,10 @@
SeedMapper.seedMapperFromFile(
options.getProguardConfiguration().getApplyMappingFile());
timing.begin("apply-mapping");
- appView.setGraphLense(
- new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing));
+ GraphLense applyMappingLense =
+ new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing);
+ rootSet = rootSet.rewrittenWithLense(applyMappingLense);
+ appView.setGraphLense(applyMappingLense);
application = application.asDirect().rewrittenWithLense(appView.graphLense());
appView.setAppInfo(
appView
@@ -490,6 +492,7 @@
// At this point all code has been mapped according to the graph lens. We cannot remove the
// graph lens entirely, though, since it is needed for mapping all field and method signatures
// back to the original program.
+ GraphLense finalLense = appView.graphLense();
timing.begin("AppliedGraphLens construction");
appView.setGraphLense(new AppliedGraphLens(appView, application.classes()));
timing.end();
@@ -536,9 +539,9 @@
.run();
}
if (whyAreYouKeepingConsumer != null) {
- for (DexDefinition dexDefinition : mainDexRootSet.reasonAsked) {
+ for (DexReference reference : mainDexRootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(dexDefinition), System.out);
+ enqueuer.getGraphNode(reference), System.out);
}
}
}
@@ -578,9 +581,9 @@
// Print reasons on the application after pruning, so that we reflect the actual result.
if (whyAreYouKeepingConsumer != null) {
- for (DexDefinition dexDefinition : rootSet.reasonAsked) {
+ for (DexReference reference : rootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
- enqueuer.getGraphNode(dexDefinition), System.out);
+ enqueuer.getGraphNode(reference), System.out);
}
}
// Remove annotations that refer to types that no longer exist.
@@ -641,7 +644,8 @@
// Validity checks.
assert application.classes().stream().allMatch(DexClass::isValid);
- assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo(), options);
+ // Use the final lense while checking the validity of the final app against the root set.
+ assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo(), finalLense, options);
assert appView
.graphLense()
.verifyMappingToOriginalProgram(
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 9c50682..2037cda 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -106,7 +106,7 @@
if (parent != null && !parent.toFile().exists()) {
Files.createDirectories(parent);
}
- Files.write(outputPath, string.getBytes(encoding));
+ com.google.common.io.Files.asCharSink(outputPath.toFile(), encoding).write(string);
} catch (IOException e) {
Origin origin = new PathOrigin(outputPath);
handler.error(new ExceptionDiagnostic(e, origin));
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index c9edb88..028f5a2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -66,6 +66,17 @@
return app.classesWithDeterministicOrder();
}
+ public DexDefinition definitionFor(DexReference reference) {
+ if (reference.isDexType()) {
+ return definitionFor(reference.asDexType());
+ }
+ if (reference.isDexMethod()) {
+ return definitionFor(reference.asDexMethod());
+ }
+ assert reference.isDexField();
+ return definitionFor(reference.asDexField());
+ }
+
public DexClass definitionFor(DexType type) {
return app.definitionFor(type);
}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 11d9730..bac74f6 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -10,6 +10,8 @@
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
@@ -25,6 +27,9 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Function;
/**
* A GraphLense implements a virtual view on top of the graph, used to delay global rewrites until
@@ -494,6 +499,23 @@
return true;
}
+ public ImmutableList<DexReference> rewriteReferencesConservatively(List<DexReference> original) {
+ ImmutableList.Builder<DexReference> builder = ImmutableList.builder();
+ for (DexReference item : original) {
+ if (item.isDexMethod()) {
+ DexMethod method = item.asDexMethod();
+ if (isContextFreeForMethod(method)) {
+ builder.add(lookupMethod(method));
+ } else {
+ builder.addAll(lookupMethodInAllContexts(method));
+ }
+ } else {
+ builder.add(lookupReference(item));
+ }
+ }
+ return builder.build();
+ }
+
public ImmutableSet<DexReference> rewriteReferencesConservatively(Set<DexReference> original) {
ImmutableSet.Builder<DexReference> builder = ImmutableSet.builder();
for (DexReference item : original) {
@@ -511,6 +533,23 @@
return builder.build();
}
+ public Set<DexReference> rewriteMutableReferencesConservatively(Set<DexReference> original) {
+ Set<DexReference> result = Sets.newIdentityHashSet();
+ for (DexReference item : original) {
+ if (item.isDexMethod()) {
+ DexMethod method = item.asDexMethod();
+ if (isContextFreeForMethod(method)) {
+ result.add(lookupMethod(method));
+ } else {
+ result.addAll(lookupMethodInAllContexts(method));
+ }
+ } else {
+ result.add(lookupReference(item));
+ }
+ }
+ return result;
+ }
+
public Object2BooleanMap<DexReference> rewriteReferencesConservatively(
Object2BooleanMap<DexReference> original) {
Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
@@ -532,6 +571,22 @@
return result;
}
+ public ImmutableSet<DexType> rewriteTypesConservatively(Set<DexType> original) {
+ ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+ for (DexType item : original) {
+ builder.add(lookupType(item));
+ }
+ return builder.build();
+ }
+
+ public Set<DexType> rewriteMutableTypesConservatively(Set<DexType> original) {
+ Set<DexType> result = Sets.newIdentityHashSet();
+ for (DexType item : original) {
+ result.add(lookupType(item));
+ }
+ return result;
+ }
+
public ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(Set<DexMethod> methods) {
ImmutableSortedSet.Builder<DexMethod> builder =
new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
@@ -563,6 +618,45 @@
return builder.build();
}
+ public SortedSet<DexMethod> rewriteMutableMethodsConservatively(Set<DexMethod> original) {
+ SortedSet<DexMethod> result = new TreeSet<>(PresortedComparable::slowCompare);
+ if (isContextFreeForMethods()) {
+ for (DexMethod item : original) {
+ result.add(lookupMethod(item));
+ }
+ } else {
+ for (DexMethod item : original) {
+ // Avoid using lookupMethodInAllContexts when possible.
+ if (isContextFreeForMethod(item)) {
+ result.add(lookupMethod(item));
+ } else {
+ // The lense is context sensitive, but we do not have the context here. Therefore, we
+ // conservatively look up the method in all contexts.
+ result.addAll(lookupMethodInAllContexts(item));
+ }
+ }
+ }
+ return result;
+ }
+
+ public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys(
+ Map<T, S> original, Function<T, T> rewrite) {
+ ImmutableMap.Builder<T, S> builder = new ImmutableMap.Builder<>();
+ for (T item : original.keySet()) {
+ builder.put(rewrite.apply(item), original.get(item));
+ }
+ return builder.build();
+ }
+
+ public static <T extends DexReference, S> Map<T, S> rewriteMutableReferenceKeys(
+ Map<T, S> original, Function<T, T> rewrite) {
+ Map<T, S> result = new IdentityHashMap<>();
+ for (T item : original.keySet()) {
+ result.put(rewrite.apply(item), original.get(item));
+ }
+ return result;
+ }
+
public boolean verifyMappingToOriginalProgram(
Iterable<DexProgramClass> classes,
DexApplication originalApplication,
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index bef2765..a5070ca 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -686,6 +686,9 @@
public boolean verifyNoImpreciseOrBottomTypes() {
Predicate<Value> verifyValue =
v -> {
+ assert !v.isNeverNull()
+ || (v.getTypeLattice().isReference()
+ && v.getTypeLattice().nullability().isDefinitelyNotNull());
assert v.getTypeLattice().isPreciseType();
assert !v.getTypeLattice().isFineGrainedType();
// For now we assume no bottom types on IR values. We may want to reconsider this for
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index fbdb9f3..0af7c9e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1905,7 +1905,7 @@
String name = null;
if (invokedMethod == appInfo.dexItemFactory.classMethods.getName) {
if (code.options.enableMinification
- && !converter.rootSet.noObfuscation.contains(holder)) {
+ && !converter.rootSet.noObfuscation.contains(holderType)) {
deferred = new DexItemBasedValueString(
holderType, new ClassNameComputationInfo(NAME));
} else {
@@ -1915,7 +1915,7 @@
// TODO(b/119426668): desugar Type#getTypeName
} else if (invokedMethod == appInfo.dexItemFactory.classMethods.getCanonicalName) {
if (code.options.enableMinification
- && !converter.rootSet.noObfuscation.contains(holder)) {
+ && !converter.rootSet.noObfuscation.contains(holderType)) {
deferred = new DexItemBasedValueString(
holderType, new ClassNameComputationInfo(CANONICAL_NAME));
} else {
@@ -1923,7 +1923,7 @@
}
} else if (invokedMethod == appInfo.dexItemFactory.classMethods.getSimpleName) {
if (code.options.enableMinification
- && !converter.rootSet.noObfuscation.contains(holder)) {
+ && !converter.rootSet.noObfuscation.contains(holderType)) {
deferred = new DexItemBasedValueString(
holderType, new ClassNameComputationInfo(SIMPLE_NAME));
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index ad3fb38..3deeb63 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -6,13 +6,14 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -59,12 +60,16 @@
this.appInfo = appInfo;
}
- private ProguardMemberRuleLookup lookupMemberRule(DexItem item) {
- ProguardMemberRule rule = appInfo.noSideEffects.get(item);
+ private ProguardMemberRuleLookup lookupMemberRule(DexDefinition definition) {
+ if (definition == null) {
+ return null;
+ }
+ DexReference reference = definition.toReference();
+ ProguardMemberRule rule = appInfo.noSideEffects.get(reference);
if (rule != null) {
return new ProguardMemberRuleLookup(RuleType.ASSUME_NO_SIDE_EFFECTS, rule);
}
- rule = appInfo.assumedValues.get(item);
+ rule = appInfo.assumedValues.get(reference);
if (rule != null) {
return new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
}
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 b02801c..e53f959 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -87,12 +87,10 @@
this.classDictionary = options.getProguardConfiguration().getClassObfuscationDictionary();
this.keepInnerClassStructure = options.getProguardConfiguration().getKeepAttributes().signature;
this.noObfuscationTypes =
- DexReference.filterDexType(
- DexDefinition.mapToReference(rootSet.noObfuscation.stream()))
+ DexReference.filterDexType(rootSet.noObfuscation.stream())
.collect(Collectors.toSet());
this.keepPackageName =
- DexReference.filterDexType(
- DexDefinition.mapToReference(rootSet.keepPackageName.stream()))
+ DexReference.filterDexType(rootSet.keepPackageName.stream())
.collect(Collectors.toSet());
// Initialize top-level naming 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 3b546a8..f00787b 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -67,8 +67,8 @@
DexEncodedField encodedField,
NamingState<DexType, ?> state,
boolean isLibrary) {
- if (isLibrary || rootSet.noObfuscation.contains(encodedField)) {
- DexField field = encodedField.field;
+ DexField field = encodedField.field;
+ if (isLibrary || rootSet.noObfuscation.contains(field)) {
state.reserveName(field.name, field.type);
}
}
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 df11ba9..5f990ac 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -479,11 +479,12 @@
return state;
}
- private void reserveNamesForMethod(DexEncodedMethod method,
- boolean keepAll, NamingState<DexProto, ?> state) {
+ private void reserveNamesForMethod(
+ DexEncodedMethod encodedMethod, boolean keepAll, NamingState<DexProto, ?> state) {
+ DexMethod method = encodedMethod.method;
if (keepAll || rootSet.noObfuscation.contains(method)) {
- state.reserveName(method.method.name, method.method.proto);
- globalState.reserveName(method.method.name, method.method.proto);
+ state.reserveName(method.name, method.proto);
+ globalState.reserveName(method.name, method.proto);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index b28a1a7..07a62a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
@@ -19,7 +20,7 @@
public class DiscardedChecker {
- private final Set<DexDefinition> checkDiscarded;
+ private final Set<DexReference> checkDiscarded;
private final List<DexProgramClass> classes;
private boolean fail = false;
private final InternalOptions options;
@@ -55,7 +56,7 @@
}
private void checkItem(DexDefinition item) {
- if (checkDiscarded.contains(item)) {
+ if (checkDiscarded.contains(item.toReference())) {
options.reporter.info(
new StringDiagnostic("Item " + item.toSourceString() + " was not discarded."));
fail = true;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 0761a71..13dd3ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
@@ -63,7 +64,6 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
@@ -297,22 +297,21 @@
this.options = options;
}
- private void enqueueRootItems(Map<DexDefinition, Set<ProguardKeepRule>> items) {
+ private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRule>> items) {
items.entrySet().forEach(this::enqueueRootItem);
}
- private void enqueueRootItem(Entry<DexDefinition, Set<ProguardKeepRule>> root) {
- enqueueRootItem(root.getKey(), root.getValue());
- }
-
- private void enqueueRootItem(DexDefinition item, ProguardKeepRule rule) {
- enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
+ private void enqueueRootItem(Entry<DexReference, Set<ProguardKeepRule>> root) {
+ DexDefinition item = appInfo.definitionFor(root.getKey());
+ if (item != null) {
+ enqueueRootItem(item, root.getValue());
+ }
}
private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRule> rules) {
assert !rules.isEmpty();
if (keptGraphConsumer != null) {
- GraphNode node = getGraphNode(item);
+ GraphNode node = getGraphNode(item.toReference());
for (ProguardKeepRule rule : rules) {
registerEdge(node, KeepReason.dueToKeepRule(rule));
}
@@ -322,7 +321,7 @@
private void enqueueRootItem(DexDefinition item, KeepReason reason) {
if (keptGraphConsumer != null) {
- registerEdge(getGraphNode(item), reason);
+ registerEdge(getGraphNode(item.toReference()), reason);
}
internalEnqueueRootItem(item, reason);
}
@@ -368,16 +367,17 @@
}
private void enqueueHolderIfDependentNonStaticMember(
- DexClass holder, Map<DexDefinition, Set<ProguardKeepRule>> dependentItems) {
+ DexClass holder, Map<DexReference, Set<ProguardKeepRule>> dependentItems) {
// Check if any dependent members are not static, and in that case enqueue the class as well.
// Having a dependent rule like -keepclassmembers with non static items indicates that class
// instances will be present even if tracing do not find any instantiation. See b/115867670.
- for (Entry<DexDefinition, Set<ProguardKeepRule>> entry : dependentItems.entrySet()) {
- DexDefinition dependentItem = entry.getKey();
- if (dependentItem.isDexClass()) {
+ for (Entry<DexReference, Set<ProguardKeepRule>> entry : dependentItems.entrySet()) {
+ DexReference dependentItem = entry.getKey();
+ if (dependentItem.isDexType()) {
continue;
}
- if (!dependentItem.isStaticMember()) {
+ DexDefinition dependentDefinition = appInfo.definitionFor(dependentItem);
+ if (!dependentDefinition.isStaticMember()) {
enqueueRootItem(holder, entry.getValue());
// Enough to enqueue the known holder once.
break;
@@ -810,7 +810,7 @@
annotations.forEach(this::handleAnnotationOfLiveType);
}
- Map<DexDefinition, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
+ Map<DexReference, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
enqueueHolderIfDependentNonStaticMember(holder, dependentItems);
// Add all dependent members to the workqueue.
enqueueRootItems(dependentItems);
@@ -1325,7 +1325,7 @@
// TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
// marking of not renaming is in the root set.
enqueueRootItem(valuesMethod, reason);
- rootSet.noObfuscation.add(valuesMethod);
+ rootSet.noObfuscation.add(valuesMethod.toReference());
}
}
@@ -1465,17 +1465,14 @@
targetedMethods.getItems(),
executorService);
ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
+ rootSet.addConsequentRootSet(consequentRootSet);
enqueueRootItems(consequentRootSet.noShrinking);
- rootSet.neverInline.addAll(consequentRootSet.neverInline);
- rootSet.neverClassInline.addAll(consequentRootSet.neverClassInline);
- rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
- rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
- rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
// Check if any newly dependent members are not static, and in that case find the holder
// and enqueue it as well. This is -if version of workaround for b/115867670.
consequentRootSet.dependentNoShrinking.forEach((precondition, dependentItems) -> {
- if (precondition.isDexClass()) {
- enqueueHolderIfDependentNonStaticMember(precondition.asDexClass(), dependentItems);
+ if (precondition.isDexType()) {
+ DexClass preconditionHolder = appInfo.definitionFor(precondition.asDexType());
+ enqueueHolderIfDependentNonStaticMember(preconditionHolder, dependentItems);
}
// Add all dependent members to the workqueue.
enqueueRootItems(dependentItems);
@@ -1948,11 +1945,11 @@
/**
* All items with assumenosideeffects rule.
*/
- public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
+ public final Map<DexReference, ProguardMemberRule> noSideEffects;
/**
* All items with assumevalues rule.
*/
- public final Map<DexDefinition, ProguardMemberRule> assumedValues;
+ public final Map<DexReference, ProguardMemberRule> assumedValues;
/**
* All methods that should be inlined if possible due to a configuration directive.
*/
@@ -1965,9 +1962,13 @@
* All methods that *must* never be inlined due to a configuration directive (testing only).
*/
public final Set<DexMethod> neverInline;
- /** All methods that may not have any parameters with a constant value removed. */
+ /**
+ * All methods that may not have any parameters with a constant value removed.
+ */
public final Set<DexMethod> keepConstantArguments;
- /** All methods that may not have any unused arguments removed. */
+ /**
+ * All methods that may not have any unused arguments removed.
+ */
public final Set<DexMethod> keepUnusedArguments;
/**
* All types that *must* never be inlined due to a configuration directive (testing only).
@@ -2143,16 +2144,14 @@
this.callSites = previous.callSites;
this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
- assert lense.assertDefinitionsNotModified(previous.noSideEffects.keySet());
- this.noSideEffects = previous.noSideEffects;
- assert lense.assertDefinitionsNotModified(previous.assumedValues.keySet());
- this.assumedValues = previous.assumedValues;
+ this.noSideEffects = rewriteReferenceKeys(previous.noSideEffects, lense::lookupReference);
+ this.assumedValues = rewriteReferenceKeys(previous.assumedValues, lense::lookupReference);
assert lense.assertDefinitionsNotModified(
previous.alwaysInline.stream()
.map(this::definitionFor)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
- this.alwaysInline = previous.alwaysInline;
+ this.alwaysInline = lense.rewriteMethodsWithRenamedSignature(previous.alwaysInline);
this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
this.keepConstantArguments =
@@ -2165,7 +2164,7 @@
.filter(Objects::nonNull)
.collect(Collectors.toList()));
this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
- this.neverMerge = previous.neverMerge;
+ this.neverMerge = rewriteItems(previous.neverMerge, lense::lookupType);
this.identifierNameStrings =
lense.rewriteReferencesConservatively(previous.identifierNameStrings);
// Switchmap classes should never be affected by renaming.
@@ -2174,8 +2173,8 @@
.map(this::definitionFor)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
- this.switchMaps = previous.switchMaps;
- this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
+ this.switchMaps = rewriteReferenceKeys(previous.switchMaps, lense::lookupField);
+ this.ordinalsMaps = rewriteReferenceKeys(previous.ordinalsMaps, lense::lookupType);
// Sanity check sets after rewriting.
assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
@@ -2318,14 +2317,6 @@
return builder.build();
}
- private static <T extends PresortedComparable<T>, S> ImmutableMap<T, S> rewriteKeys(
- Map<T, S> original, Function<T, T> rewrite) {
- ImmutableMap.Builder<T, S> builder = new ImmutableMap.Builder<>();
- for (T item : original.keySet()) {
- builder.put(rewrite.apply(item), original.get(item));
- }
- return builder.build();
- }
private static <T extends PresortedComparable<T>, S>
Map<T, Set<S>> rewriteKeysWhileMergingValues(
@@ -2820,15 +2811,15 @@
return reason.getSourceNode(this);
}
- public GraphNode getGraphNode(DexDefinition item) {
- if (item instanceof DexClass) {
- return getClassGraphNode(((DexClass) item).type);
+ public GraphNode getGraphNode(DexReference reference) {
+ if (reference.isDexType()) {
+ return getClassGraphNode(reference.asDexType());
}
- if (item instanceof DexEncodedMethod) {
- return getMethodGraphNode(((DexEncodedMethod) item).method);
+ if (reference.isDexMethod()) {
+ return getMethodGraphNode(reference.asDexMethod());
}
- if (item instanceof DexEncodedField) {
- return getFieldGraphNode(((DexEncodedField) item).field);
+ if (reference.isDexField()) {
+ return getFieldGraphNode(reference.asDexField());
}
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 203cf82..43c39a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,6 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.GraphLense.rewriteMutableReferenceKeys;
+import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
+
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -22,6 +25,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -53,6 +57,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -61,14 +66,14 @@
private final AppView<? extends AppInfo> appView;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
- private final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
- private final Set<DexDefinition> noOptimization = Sets.newIdentityHashSet();
- private final Set<DexDefinition> noObfuscation = Sets.newIdentityHashSet();
- private final LinkedHashMap<DexDefinition, DexDefinition> reasonAsked = new LinkedHashMap<>();
- private final Set<DexDefinition> keepPackageName = Sets.newIdentityHashSet();
+ private final Map<DexReference, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
+ private final Set<DexReference> noOptimization = Sets.newIdentityHashSet();
+ private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
+ private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
+ private final Set<DexReference> keepPackageName = Sets.newIdentityHashSet();
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
- private final Set<DexDefinition> checkDiscarded = Sets.newIdentityHashSet();
+ private final Set<DexReference> checkDiscarded = Sets.newIdentityHashSet();
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
@@ -76,10 +81,10 @@
private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
- private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking =
+ private final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking =
new IdentityHashMap<>();
- private final Map<DexDefinition, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
- private final Map<DexDefinition, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
+ private final Map<DexReference, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
+ private final Map<DexReference, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
private final InternalOptions options;
@@ -863,11 +868,11 @@
}
// Keep the type if the item is also kept.
dependentNoShrinking
- .computeIfAbsent(item, x -> new IdentityHashMap<>())
- .computeIfAbsent(definition, k -> new HashSet<>())
+ .computeIfAbsent(item.toReference(), x -> new IdentityHashMap<>())
+ .computeIfAbsent(type, k -> new HashSet<>())
.add(context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
- noObfuscation.add(definition);
+ noObfuscation.add(type);
}
private void includeDescriptorClasses(DexDefinition item, ProguardKeepRule context) {
@@ -903,32 +908,32 @@
if (!modifiers.allowsShrinking) {
if (precondition != null) {
dependentNoShrinking
- .computeIfAbsent(precondition, x -> new IdentityHashMap<>())
- .computeIfAbsent(item, i -> new HashSet<>())
+ .computeIfAbsent(precondition.toReference(), x -> new IdentityHashMap<>())
+ .computeIfAbsent(item.toReference(), i -> new HashSet<>())
.add(keepRule);
} else {
- noShrinking.computeIfAbsent(item, i -> new HashSet<>()).add(keepRule);
+ noShrinking.computeIfAbsent(item.toReference(), i -> new HashSet<>()).add(keepRule);
}
}
if (!modifiers.allowsOptimization) {
- noOptimization.add(item);
+ noOptimization.add(item.toReference());
}
if (!modifiers.allowsObfuscation) {
- noObfuscation.add(item);
+ noObfuscation.add(item.toReference());
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
}
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
- noSideEffects.put(item, rule);
+ noSideEffects.put(item.toReference(), rule);
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
- reasonAsked.computeIfAbsent(item, i -> i);
+ reasonAsked.computeIfAbsent(item.toReference(), i -> i);
} else if (context instanceof ProguardKeepPackageNamesRule) {
- keepPackageName.add(item);
+ keepPackageName.add(item.toReference());
} else if (context instanceof ProguardAssumeValuesRule) {
- assumedValues.put(item, rule);
+ assumedValues.put(item.toReference(), rule);
} else if (context instanceof ProguardCheckDiscardRule) {
- checkDiscarded.add(item);
+ checkDiscarded.add(item.toReference());
} else if (context instanceof InlineRule) {
if (item.isDexEncodedMethod()) {
switch (((InlineRule) context).getType()) {
@@ -984,12 +989,12 @@
public static class RootSet {
- public final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
- public final Set<DexDefinition> noOptimization;
- public final Set<DexDefinition> noObfuscation;
- public final ImmutableList<DexDefinition> reasonAsked;
- public final Set<DexDefinition> keepPackageName;
- public final Set<DexDefinition> checkDiscarded;
+ public final Map<DexReference, Set<ProguardKeepRule>> noShrinking;
+ public final Set<DexReference> noOptimization;
+ public final Set<DexReference> noObfuscation;
+ public final ImmutableList<DexReference> reasonAsked;
+ public final Set<DexReference> keepPackageName;
+ public final Set<DexReference> checkDiscarded;
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> neverInline;
@@ -997,20 +1002,20 @@
public final Set<DexMethod> keepUnusedArguments;
public final Set<DexType> neverClassInline;
public final Set<DexType> neverMerge;
- public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
- public final Map<DexDefinition, ProguardMemberRule> assumedValues;
- private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>>
+ public final Map<DexReference, ProguardMemberRule> noSideEffects;
+ public final Map<DexReference, ProguardMemberRule> assumedValues;
+ private final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>>
dependentNoShrinking;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
private RootSet(
- Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
- Set<DexDefinition> noOptimization,
- Set<DexDefinition> noObfuscation,
- ImmutableList<DexDefinition> reasonAsked,
- Set<DexDefinition> keepPackageName,
- Set<DexDefinition> checkDiscarded,
+ Map<DexReference, Set<ProguardKeepRule>> noShrinking,
+ Set<DexReference> noOptimization,
+ Set<DexReference> noObfuscation,
+ ImmutableList<DexReference> reasonAsked,
+ Set<DexReference> keepPackageName,
+ Set<DexReference> checkDiscarded,
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
@@ -1018,12 +1023,12 @@
Set<DexMethod> keepUnusedArguments,
Set<DexType> neverClassInline,
Set<DexType> neverMerge,
- Map<DexDefinition, ProguardMemberRule> noSideEffects,
- Map<DexDefinition, ProguardMemberRule> assumedValues,
- Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking,
+ Map<DexReference, ProguardMemberRule> noSideEffects,
+ Map<DexReference, ProguardMemberRule> assumedValues,
+ Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules) {
- this.noShrinking = Collections.unmodifiableMap(noShrinking);
+ this.noShrinking = noShrinking;
this.noOptimization = noOptimization;
this.noObfuscation = noObfuscation;
this.reasonAsked = reasonAsked;
@@ -1043,33 +1048,81 @@
this.ifRules = Collections.unmodifiableSet(ifRules);
}
+ private RootSet(RootSet previous, GraphLense lense) {
+ this.noShrinking = rewriteMutableReferenceKeys(previous.noShrinking, lense::lookupReference);
+ this.noOptimization = lense.rewriteMutableReferencesConservatively(previous.noOptimization);
+ this.noObfuscation = lense.rewriteMutableReferencesConservatively(previous.noObfuscation);
+ this.reasonAsked = lense.rewriteReferencesConservatively(previous.reasonAsked);
+ this.keepPackageName = lense.rewriteReferencesConservatively(previous.keepPackageName);
+ this.checkDiscarded = lense.rewriteReferencesConservatively(previous.checkDiscarded);
+ this.alwaysInline = lense.rewriteMethodsConservatively(previous.alwaysInline);
+ this.forceInline = lense.rewriteMethodsConservatively(previous.forceInline);
+ this.neverInline = lense.rewriteMutableMethodsConservatively(previous.neverInline);
+ this.keepConstantArguments =
+ lense.rewriteMutableMethodsConservatively(previous.keepConstantArguments);
+ this.keepUnusedArguments =
+ lense.rewriteMutableMethodsConservatively(previous.keepUnusedArguments);
+ this.neverClassInline = lense.rewriteMutableTypesConservatively(previous.neverClassInline);
+ this.neverMerge = lense.rewriteTypesConservatively(previous.neverMerge);
+ this.noSideEffects = rewriteReferenceKeys(previous.noSideEffects, lense::lookupReference);
+ this.assumedValues = rewriteReferenceKeys(previous.assumedValues, lense::lookupReference);
+ this.dependentNoShrinking =
+ rewriteDependentReferenceKeys(previous.dependentNoShrinking, lense::lookupReference);
+ this.identifierNameStrings =
+ lense.rewriteReferencesConservatively(previous.identifierNameStrings);
+ this.ifRules = Collections.unmodifiableSet(previous.ifRules);
+ }
+
+ public RootSet rewrittenWithLense(GraphLense lense) {
+ return new RootSet(this, lense);
+ }
+
+ private static <T extends DexReference, S> Map<T, Map<T, S>> rewriteDependentReferenceKeys(
+ Map<T, Map<T, S>> original, Function<T, T> rewrite) {
+ Map<T, Map<T, S>> result = new IdentityHashMap<>();
+ for (T item : original.keySet()) {
+ result.put(rewrite.apply(item), rewriteReferenceKeys(original.get(item), rewrite));
+ }
+ return result;
+ }
+
+ void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
+ neverInline.addAll(consequentRootSet.neverInline);
+ neverClassInline.addAll(consequentRootSet.neverClassInline);
+ noOptimization.addAll(consequentRootSet.noOptimization);
+ noObfuscation.addAll(consequentRootSet.noObfuscation);
+ addDependentItems(consequentRootSet.dependentNoShrinking);
+ }
+
// Add dependent items that depend on -if rules.
- void addDependentItems(
- Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentItems) {
+ private void addDependentItems(
+ Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentItems) {
dependentItems.forEach(
- (def, dependence) ->
+ (reference, dependence) ->
dependentNoShrinking
- .computeIfAbsent(def, x -> new IdentityHashMap<>())
+ .computeIfAbsent(reference, x -> new IdentityHashMap<>())
.putAll(dependence));
}
- Map<DexDefinition, Set<ProguardKeepRule>> getDependentItems(DexDefinition item) {
- return Collections
- .unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
+ Map<DexReference, Set<ProguardKeepRule>> getDependentItems(DexDefinition item) {
+ return Collections.unmodifiableMap(
+ dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
}
public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
- for (DexDefinition definition : noShrinking.keySet()) {
- if (definition.isDexEncodedField()) {
- DexEncodedField field = definition.asDexEncodedField();
- if (field.isStatic() || isKeptDirectlyOrIndirectly(field.field.clazz, appInfo)) {
+ for (DexReference reference : noShrinking.keySet()) {
+ if (reference.isDexField()) {
+ DexField field = reference.asDexField();
+ DexEncodedField encodedField = appInfo.definitionFor(field);
+ if (encodedField != null
+ && (encodedField.isStatic() || isKeptDirectlyOrIndirectly(field.clazz, appInfo))) {
// TODO(b/121354886): Enable asserts for reads and writes.
- /*assert appInfo.fieldsRead.contains(field.field)
- : "Expected kept field `" + field.field.toSourceString() + "` to be read";
- assert appInfo.fieldsWritten.contains(field.field)
- : "Expected kept field `" + field.field.toSourceString() + "` to be written";*/
- assert appInfo.liveFields.contains(field.field)
- : "Expected kept field `" + field.field.toSourceString() + "` to be live";
+ /*assert appInfo.fieldsRead.contains(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be read";
+ assert appInfo.fieldsWritten.contains(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be written";*/
+ assert appInfo.liveFields.contains(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be live";
}
}
}
@@ -1077,17 +1130,17 @@
}
public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
- for (DexDefinition definition : noShrinking.keySet()) {
- if (definition.isDexEncodedMethod()) {
- DexEncodedMethod method = definition.asDexEncodedMethod();
- assert appInfo.targetedMethods.contains(method.method)
- : "Expected kept method `" + method.method.toSourceString() + "` to be targeted";
-
- if (!method.accessFlags.isAbstract()
- && isKeptDirectlyOrIndirectly(method.method.holder, appInfo)) {
- assert appInfo.liveMethods.contains(method.method)
+ for (DexReference reference : noShrinking.keySet()) {
+ if (reference.isDexMethod()) {
+ DexMethod method = reference.asDexMethod();
+ assert appInfo.targetedMethods.contains(method)
+ : "Expected kept method `" + method.toSourceString() + "` to be targeted";
+ DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
+ if (!encodedMethod.accessFlags.isAbstract()
+ && isKeptDirectlyOrIndirectly(method.holder, appInfo)) {
+ assert appInfo.liveMethods.contains(method)
: "Expected non-abstract kept method `"
- + method.method.toSourceString()
+ + method.toSourceString()
+ "` to be live";
}
}
@@ -1096,37 +1149,59 @@
}
public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
- for (DexDefinition definition : noShrinking.keySet()) {
- if (definition.isDexClass()) {
- DexClass clazz = definition.asDexClass();
- assert appInfo.liveTypes.contains(clazz.type)
- : "Expected kept type `" + clazz.type.toSourceString() + "` to be live";
+ for (DexReference reference : noShrinking.keySet()) {
+ if (reference.isDexType()) {
+ DexType type = reference.asDexType();
+ assert appInfo.liveTypes.contains(type)
+ : "Expected kept type `" + type.toSourceString() + "` to be live";
}
}
return true;
}
private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
+ if (noShrinking.containsKey(type)) {
+ return true;
+ }
DexClass clazz = appInfo.definitionFor(type);
if (clazz == null) {
return false;
}
- if (noShrinking.containsKey(clazz)) {
- return true;
- }
if (clazz.superType != null) {
return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
}
return false;
}
- public boolean verifyKeptItemsAreKept(
- DexApplication application, AppInfo appInfo, InternalOptions options) {
- if (options.getProguardConfiguration().hasApplyMappingFile()) {
- // TODO(b/121295633): Root set is obsolete after mapping has been applied.
- return true;
+ // TODO(b/121240523): Ideally, these default interface methods should be delegated.
+ private boolean isDesugaredMethod(
+ AppInfo appInfo, GraphLense lense, DexClass clazz, DexMethod method) {
+ if (clazz == null) {
+ clazz = appInfo.definitionFor(method.getHolder());
}
+ Set<DexMethod> lookupMethods = lense.lookupMethodInAllContexts(method);
+ if (lookupMethods.size() == 1) {
+ for (DexMethod lookupMethod : lookupMethods) {
+ if (lookupMethod == method) {
+ continue;
+ }
+ DexEncodedMethod encodedMethod = appInfo.definitionFor(lookupMethod);
+ if (clazz != null
+ && clazz.isInterface()
+ && encodedMethod != null
+ && encodedMethod.hasCode()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ public boolean verifyKeptItemsAreKept(
+ DexApplication application,
+ AppInfo appInfo,
+ GraphLense lense,
+ InternalOptions options) {
boolean isInterfaceMethodDesugaringEnabled =
options.enableDesugaring
&& options.interfaceMethodDesugaring == OffOrAuto.Auto
@@ -1136,33 +1211,36 @@
appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
// Create a mapping from each required type to the set of required members on that type.
- Map<DexType, Set<DexDefinition>> requiredDefinitionsPerType = new IdentityHashMap<>();
- for (DexDefinition definition : noShrinking.keySet()) {
+ Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>();
+ for (DexReference reference : noShrinking.keySet()) {
// Check that `pinnedItems` is a super set of the root set.
- assert pinnedItems == null || pinnedItems.contains(definition.toReference());
- if (definition.isDexClass()) {
- DexType type = definition.toReference().asDexType();
- requiredDefinitionsPerType.putIfAbsent(type, Sets.newIdentityHashSet());
+ assert pinnedItems == null || pinnedItems.contains(reference)
+ || (reference.isDexMethod()
+ && isInterfaceMethodDesugaringEnabled
+ && isDesugaredMethod(appInfo, lense, null, reference.asDexMethod()));
+ if (reference.isDexType()) {
+ DexType type = reference.asDexType();
+ requiredReferencesPerType.putIfAbsent(type, Sets.newIdentityHashSet());
} else {
- assert definition.isDexEncodedField() || definition.isDexEncodedMethod();
- Descriptor<?, ?> descriptor = definition.toReference().asDescriptor();
- requiredDefinitionsPerType
+ assert reference.isDexField() || reference.isDexMethod();
+ Descriptor<?, ?> descriptor = reference.asDescriptor();
+ requiredReferencesPerType
.computeIfAbsent(descriptor.getHolder(), key -> Sets.newIdentityHashSet())
- .add(definition);
+ .add(reference);
}
}
// Run through each class in the program and check that it has members it must have.
for (DexProgramClass clazz : application.classes()) {
- Set<DexDefinition> requiredDefinitions =
- requiredDefinitionsPerType.getOrDefault(clazz.type, ImmutableSet.of());
+ Set<DexReference> requiredReferences =
+ requiredReferencesPerType.getOrDefault(clazz.type, ImmutableSet.of());
Set<DexField> fields = null;
Set<DexMethod> methods = null;
- for (DexDefinition requiredDefinition : requiredDefinitions) {
- if (requiredDefinition.isDexEncodedField()) {
- DexEncodedField requiredField = requiredDefinition.asDexEncodedField();
+ for (DexReference requiredReference : requiredReferences) {
+ if (requiredReference.isDexField()) {
+ DexField requiredField = requiredReference.asDexField();
if (fields == null) {
// Create a Set of the fields to avoid quadratic behavior.
fields =
@@ -1170,15 +1248,12 @@
.map(DexEncodedField::getKey)
.collect(Collectors.toSet());
}
- assert fields.contains(requiredField.field);
- } else if (requiredDefinition.isDexEncodedMethod()) {
- DexEncodedMethod requiredMethod = requiredDefinition.asDexEncodedMethod();
- if (isInterfaceMethodDesugaringEnabled) {
- if (clazz.isInterface() && requiredMethod.hasCode()) {
- // TODO(b/121240523): Ideally, these default interface methods should not be in the
- // root set.
- continue;
- }
+ assert fields.contains(requiredField);
+ } else if (requiredReference.isDexMethod()) {
+ DexMethod requiredMethod = requiredReference.asDexMethod();
+ if (isInterfaceMethodDesugaringEnabled
+ && isDesugaredMethod(appInfo, lense, clazz, requiredMethod)) {
+ continue;
}
if (methods == null) {
// Create a Set of the methods to avoid quadratic behavior.
@@ -1187,21 +1262,21 @@
.map(DexEncodedMethod::getKey)
.collect(Collectors.toSet());
}
- assert methods.contains(requiredMethod.method);
+ assert methods.contains(requiredMethod);
} else {
assert false;
}
}
- requiredDefinitionsPerType.remove(clazz.type);
+ requiredReferencesPerType.remove(clazz.type);
}
// If the map is non-empty, then a type in the root set was not in the application.
- if (!requiredDefinitionsPerType.isEmpty()) {
- DexType type = requiredDefinitionsPerType.keySet().iterator().next();
+ if (!requiredReferencesPerType.isEmpty()) {
+ DexType type = requiredReferencesPerType.keySet().iterator().next();
DexClass clazz = application.definitionFor(type);
assert clazz == null || clazz.isProgramClass()
: "Unexpected library type in root set: `" + type + "`";
- assert requiredDefinitionsPerType.isEmpty()
+ assert requiredReferencesPerType.isEmpty()
: "Expected type `" + type.toSourceString() + "` to be present";
}
@@ -1227,7 +1302,7 @@
builder.append("\n\nNo Shrinking:");
noShrinking.keySet().stream()
- .sorted(Comparator.comparing(DexDefinition::toSourceString))
+ .sorted(Comparator.comparing(DexReference::toSourceString))
.forEach(a -> builder
.append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
builder.append("\n");
@@ -1239,18 +1314,18 @@
static class ConsequentRootSet {
final Set<DexMethod> neverInline;
final Set<DexType> neverClassInline;
- final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
- final Set<DexDefinition> noOptimization;
- final Set<DexDefinition> noObfuscation;
- final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking;
+ final Map<DexReference, Set<ProguardKeepRule>> noShrinking;
+ final Set<DexReference> noOptimization;
+ final Set<DexReference> noObfuscation;
+ final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking;
private ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
- Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
- Set<DexDefinition> noOptimization,
- Set<DexDefinition> noObfuscation,
- Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking) {
+ Map<DexReference, Set<ProguardKeepRule>> noShrinking,
+ Set<DexReference> noOptimization,
+ Set<DexReference> noObfuscation,
+ Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking) {
this.neverInline = Collections.unmodifiableSet(neverInline);
this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
this.noShrinking = Collections.unmodifiableMap(noShrinking);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 15dcafa..fd90187 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -272,9 +271,7 @@
// TODO(christofferqa): Remove the invariant that the graph lense should not modify any
// methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
extractPinnedItems(appInfo.alwaysInline, AbortReason.ALWAYS_INLINE);
- extractPinnedItems(
- DexDefinition.mapToReference(appInfo.noSideEffects.keySet().stream())::iterator,
- AbortReason.NO_SIDE_EFFECTS);
+ extractPinnedItems(appInfo.noSideEffects.keySet(), AbortReason.NO_SIDE_EFFECTS);
for (DexProgramClass clazz : classes) {
for (DexEncodedMethod method : clazz.methods()) {
@@ -620,7 +617,7 @@
}
private boolean verifyGraphLense(GraphLense graphLense) {
- assert graphLense.assertDefinitionsNotModified(appInfo.noSideEffects.keySet());
+ assert graphLense.assertReferencesNotModified(appInfo.noSideEffects.keySet());
// Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
// getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1df6808..7493eba 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -903,18 +903,22 @@
return extractor.getClassInternalType();
}
- protected Path writeToJar(List<byte[]> classes) throws IOException {
- Path result = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+ protected static void writeToJar(Path output, List<byte[]> classes) throws IOException {
try (ZipOutputStream out =
new ZipOutputStream(
Files.newOutputStream(
- result, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+ output, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
for (byte[] clazz : classes) {
String name = extractClassName(clazz);
ZipUtils.writeToZipStream(
out, DescriptorUtils.getPathFromJavaType(name), clazz, ZipEntry.STORED);
}
}
+ }
+
+ protected Path writeToJar(List<byte[]> classes) throws IOException {
+ Path result = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+ writeToJar(result, classes);
return result;
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java
new file mode 100644
index 0000000..0da8519
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java
@@ -0,0 +1,404 @@
+// 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 org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+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;
+
+class LibraryClass {
+ static String LIB_MSG = "LibraryClass::foo";
+ void foo() {
+ System.out.println(LIB_MSG);
+ }
+}
+
+class AnotherLibraryClass {
+ static String ANOTHERLIB_MSG = "AnotherLibraryClass::foo";
+ void foo() {
+ System.out.println(ANOTHERLIB_MSG);
+ }
+}
+
+class ProgramClass extends LibraryClass {
+ static String PRG_MSG = "ProgramClass::bar";
+ void bar() {
+ System.out.println(PRG_MSG);
+ }
+
+ public static void main(String[] args) {
+ new AnotherLibraryClass().foo();
+ ProgramClass instance = new ProgramClass();
+ instance.foo();
+ instance.bar();
+ }
+}
+
+public class NameClashTest extends TestBase {
+
+ @ClassRule
+ public static TemporaryFolder temporaryFolder = ToolHelper.getTemporaryFolderForTest();
+
+ private static Class<?> MAIN = ProgramClass.class;
+ private static String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ AnotherLibraryClass.ANOTHERLIB_MSG, LibraryClass.LIB_MSG, ProgramClass.PRG_MSG);
+
+ private static Path prgJarThatUsesOriginalLib;
+ private static Path prgJarThatUsesMinifiedLib;
+ private static Path libJar;
+ private Path mappingFile;
+
+ @BeforeClass
+ public static void setUpJars() throws Exception {
+ prgJarThatUsesOriginalLib =
+ temporaryFolder.newFile("prgOrginalLib.jar").toPath().toAbsolutePath();
+ writeToJar(prgJarThatUsesOriginalLib, ImmutableList.of(ToolHelper.getClassAsBytes(MAIN)));
+ prgJarThatUsesMinifiedLib =
+ temporaryFolder.newFile("prgMinifiedLib.jar").toPath().toAbsolutePath();
+ writeToJar(prgJarThatUsesMinifiedLib, ImmutableList.of(ProgramClassDump.dump()));
+ libJar = temporaryFolder.newFile("lib.jar").toPath().toAbsolutePath();
+ writeToJar(libJar, ImmutableList.of(
+ ToolHelper.getClassAsBytes(LibraryClass.class),
+ ToolHelper.getClassAsBytes(AnotherLibraryClass.class)));
+ }
+
+ @Before
+ public void setUpMappingFile() throws Exception {
+ mappingFile = temp.newFile("mapping.txt").toPath().toAbsolutePath();
+ }
+
+ private String invertedMapping() {
+ return StringUtils.lines(
+ "A -> " + LibraryClass.class.getTypeName() + ":",
+ " void a() -> foo",
+ "B -> " + AnotherLibraryClass.class.getTypeName() + ":",
+ " void a() -> foo"
+ );
+ }
+
+ // Note that all the test mappings below still need identity mappings for classes/memebers that
+ // are not renamed, for some reasons:
+ // 1) to mimic how R8 generates the mapping, where identity mappings are used in the same way.
+ // 2) otherwise, those classes/members will be renamed if minification is enabled, resulting in
+ // no name clash, which is definitely not intended.
+
+ private String mappingToExistingClassName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName()
+ + " -> " + AnotherLibraryClass.class.getTypeName() + ":",
+ AnotherLibraryClass.class.getTypeName()
+ + " -> " + AnotherLibraryClass.class.getTypeName() + ":"
+ );
+ }
+
+ private String mappingToTheSameClassName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName() + " -> Clash:",
+ AnotherLibraryClass.class.getTypeName() + " -> Clash:"
+ );
+ }
+
+ private String mappingToExistingMethodName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName() + " -> A:",
+ " void foo() -> bar",
+ AnotherLibraryClass.class.getTypeName() + " -> B:",
+ ProgramClass.class.getTypeName() + " -> " + ProgramClass.class.getTypeName() + ":",
+ " void bar() -> bar"
+ );
+ }
+
+ private String mappingToTheSameMethodName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName() + " -> A:",
+ " void foo() -> clash",
+ AnotherLibraryClass.class.getTypeName() + " -> B:",
+ " void foo() -> clash",
+ ProgramClass.class.getTypeName() + " -> " + ProgramClass.class.getTypeName() + ":",
+ " void bar() -> clash"
+ );
+ }
+
+ private void testProguard_inputJar(Path mappingFile) throws Exception {
+ testForProguard()
+ .addProgramFiles(libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testR8_inputJar(Path mappingFile) throws Exception {
+ testForR8(Backend.DEX)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProgramFiles(libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testProguard_originalLibraryJar(Path mappingFile) throws Exception {
+ testForProguard()
+ .addLibraryFiles(libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testR8_originalLibraryJar(Path mappingFile) throws Exception {
+ testForR8(Backend.DEX)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testProguard_minifiedLibraryJar(Path mappingFile) throws Exception {
+ testForProguard()
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar(), libJar)
+ .addProgramFiles(prgJarThatUsesMinifiedLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .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, mappingToExistingClassName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("Duplicate jar entry"));
+ assertThat(e.getMessage(), containsString("AnotherLibraryClass.class"));
+ }
+ }
+
+ @Test
+ public void testR8_prgClassRenamedToExistingPrgClass() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getCause().getMessage(), containsString("Program type already present"));
+ assertThat(e.getCause().getMessage(), containsString("AnotherLibraryClass"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibClassRenamedToExistingLibClass() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibClassRenamedToExistingLibClass() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_prgClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("Duplicate jar entry [Clash.class]"));
+ }
+ }
+
+ @Test
+ public void testR8_prgClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getCause().getMessage(), containsString("Program type already present"));
+ assertThat(e.getCause().getMessage(), containsString("Clash"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_prgMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_prgMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_prgMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'clash'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'clash'"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_prgMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_minifiedLib() throws Exception {
+ FileUtils.writeTextFile(mappingFile, invertedMapping());
+ try {
+ testProguard_minifiedLibraryJar(mappingFile);
+ } catch (CompilationFailedException e) {
+ 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/NameClashTestDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTestDump.java
new file mode 100644
index 0000000..7da3800
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTestDump.java
@@ -0,0 +1,131 @@
+// 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 org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// asmified the following:
+// class ProgramClass extends LibraryClass {
+// static String PRG_MSG = "ProgramClass::bar";
+// void bar() {
+// System.out.println(PRG_MSG);
+// }
+// public static void main(String[] args) {
+// new AnotherLibraryClass().foo();
+// ProgramClass instance = new ProgramClass();
+// instance.foo();
+// instance.bar();
+// }
+// }
+//
+// then replaced the use of LibraryClass and AnotherLibraryClass with A and B so that providing
+// LibraryClass and AnotherLibraryClass, along with mapping, as a minified library:
+// A -> LibraryClass:
+// void a() -> foo
+// B -> AnotherLibraryClass:
+// void a() -> foo
+class ProgramClassDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ String pkg = LibraryClass.class.getPackage().getName();
+
+ classWriter.visit(V1_8, ACC_SUPER, pkg.replace('.', '/') + "/ProgramClass", null, "A", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ fieldVisitor = classWriter.visitField(
+ ACC_STATIC, "PRG_MSG", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(15, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "bar", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitFieldInsn(GETSTATIC, "ProgramClass", "PRG_MSG", "Ljava/lang/String;");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(19, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(22, label0);
+ methodVisitor.visitTypeInsn(NEW, "B");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "B", "<init>", "()V", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "B", "a", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(23, label1);
+ methodVisitor.visitTypeInsn(NEW, "ProgramClass");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "ProgramClass", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(24, label2);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "ProgramClass", "a", "()V", false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(25, label3);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "ProgramClass", "bar", "()V", false);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(26, label4);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(16, label0);
+ methodVisitor.visitLdcInsn("ProgramClass::bar");
+ methodVisitor.visitFieldInsn(PUTSTATIC, "ProgramClass", "PRG_MSG", "Ljava/lang/String;");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
index e5371c5..25497e3 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
@@ -66,11 +66,11 @@
@BeforeClass
public static void setUpMappingFile() throws Exception {
- mappingFile = temporaryFolder.newFile("mapping.txt").toPath();
+ mappingFile = temporaryFolder.newFile("mapping.txt").toPath().toAbsolutePath();
FileUtils.writeTextFile(mappingFile, StringUtils.lines(
ToBeRenamedClass.class.getTypeName() + " -> " + RENAMED_CLASS_NAME + ":",
" void toBeRenamedMethod() -> abc"));
- configuration = temporaryFolder.newFile("pg.conf").toPath();
+ configuration = temporaryFolder.newFile("pg.conf").toPath().toAbsolutePath();
FileUtils.writeTextFile(configuration, StringUtils.lines(
"-dontoptimize",
"-applymapping " + mappingFile