Merge "Rename dangling types, i.e., types that are only referenced in signatures."
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 75b1bf3..bdc4fb3 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -270,9 +270,10 @@
proguardSeedsData = bytes.toByteArray();
}
if (options.useTreeShaking) {
- application = new TreePruner(application, appInfo.withLiveness(), options).run();
+ TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ application = pruner.run();
// Recompute the subtyping information.
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+ appInfo = appInfo.withLiveness().prunedCopyFrom(application, pruner.getRemovedClasses());
new AbstractMethodRemover(appInfo).run();
new AnnotationRemover(appInfo.withLiveness(), options).run();
}
@@ -295,11 +296,13 @@
// Class merging requires inlining.
if (!options.skipClassMerging && options.inlineAccessors) {
timing.begin("ClassMerger");
- graphLense = new SimpleClassMerger(application, appInfo.withLiveness(), graphLense,
- timing).run();
+ SimpleClassMerger classMerger = new SimpleClassMerger(application,
+ appInfo.withLiveness(), graphLense, timing);
+ graphLense = classMerger.run();
timing.end();
+ appInfo = appInfo.withLiveness()
+ .prunedCopyFrom(application, classMerger.getRemovedClasses());
}
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
appInfo = appInfo.withLiveness().rewrittenWithLense(graphLense);
// Collect switch maps and ordinals maps.
new SwitchMapCollector(appInfo.withLiveness(), options).run();
@@ -333,8 +336,10 @@
Enqueuer enqueuer = new Enqueuer(appInfo);
appInfo = enqueuer.traceApplication(rootSet, timing);
if (options.useTreeShaking) {
- application = new TreePruner(application, appInfo.withLiveness(), options).run();
- appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+ TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ application = pruner.run();
+ appInfo = appInfo.withLiveness()
+ .prunedCopyFrom(application, pruner.getRemovedClasses());
// Print reasons on the application after pruning, so that we reflect the actual result.
ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
reasonPrinter.run(application);
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 3ef67e6..f4bd577 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -9,7 +9,10 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
@@ -81,7 +84,7 @@
// Collect names we have to keep.
timing.begin("reserve");
for (DexClass clazz : classes) {
- if (rootSet.noObfuscation.contains(clazz)) {
+ if (rootSet.noObfuscation.contains(clazz.type)) {
assert !renaming.containsKey(clazz.type);
registerClassAsUsed(clazz.type);
}
@@ -97,6 +100,12 @@
}
timing.end();
+ timing.begin("rename-dangling-types");
+ for (DexClass clazz : classes) {
+ renameDanglingTypes(clazz);
+ }
+ timing.end();
+
timing.begin("rename-generic");
renameTypesInGenericSignatures();
timing.end();
@@ -108,6 +117,35 @@
return Collections.unmodifiableMap(renaming);
}
+ private void renameDanglingTypes(DexClass clazz) {
+ clazz.forEachMethod(this::renameDanglingTypesInMethod);
+ clazz.forEachField(this::renameDanglingTypesInField);
+ }
+
+ private void renameDanglingTypesInField(DexEncodedField field) {
+ renameDanglingType(field.field.type);
+ }
+
+ private void renameDanglingTypesInMethod(DexEncodedMethod method) {
+ DexProto proto = method.method.proto;
+ renameDanglingType(proto.returnType);
+ for (DexType type : proto.parameters.values) {
+ renameDanglingType(type);
+ }
+ }
+
+ private void renameDanglingType(DexType type) {
+ if (appInfo.wasPruned(type)
+ && !renaming.containsKey(type)
+ && !rootSet.noObfuscation.contains(type)) {
+ // We have a type that is defined in the program source but is only used in a proto or
+ // return type. As we don't need the class, we can rename it to anything as long as it is
+ // unique.
+ assert appInfo.definitionFor(type) == null;
+ renaming.put(type, topLevelState.nextTypeName());
+ }
+ }
+
private void renameTypesInGenericSignatures() {
for (DexClass clazz : appInfo.classes()) {
rewriteGenericSignatures(clazz.annotations.annotations,
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 524207e..ff8adee 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -927,13 +928,13 @@
SortedSet<DexField> collectFieldsRead() {
return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
+ collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
}
SortedSet<DexField> collectFieldsWritten() {
return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo,
Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
+ collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
}
private static class Action {
@@ -1099,6 +1100,10 @@
* Map from the class of an extension to the state it produced.
*/
public final Map<Class, Object> extensions;
+ /**
+ * A set of types that have been removed by the {@link TreePruner}.
+ */
+ public final Set<DexType> prunedTypes;
private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
super(appInfo);
@@ -1124,11 +1129,13 @@
this.assumedValues = enqueuer.rootSet.assumedValues;
this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.extensions = enqueuer.extensionsState;
+ this.prunedTypes = Collections.emptySet();
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
- private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application) {
+ private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application,
+ Collection<DexType> removedClasses) {
super(application);
this.liveTypes = previous.liveTypes;
this.instantiatedTypes = previous.instantiatedTypes;
@@ -1151,6 +1158,7 @@
this.staticInvokes = previous.staticInvokes;
this.extensions = previous.extensions;
this.alwaysInline = previous.alwaysInline;
+ this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1178,6 +1186,7 @@
this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
this.alwaysInline = previous.alwaysInline;
this.extensions = previous.extensions;
+ this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1209,6 +1218,13 @@
return builder.build();
}
+ private static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
+ ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+ builder.addAll(first);
+ builder.addAll(second);
+ return builder.build();
+ }
+
@SuppressWarnings("unchecked")
public <T> T getExtension(Class extension, T defaultValue) {
if (extensions.containsKey(extension)) {
@@ -1237,14 +1253,23 @@
* Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the
* given DexApplication object.
*/
- public AppInfoWithLiveness prunedCopyFrom(DexApplication application) {
- return new AppInfoWithLiveness(this, application);
+ public AppInfoWithLiveness prunedCopyFrom(DexApplication application,
+ Collection<DexType> removedClasses) {
+ return new AppInfoWithLiveness(this, application, removedClasses);
}
public AppInfoWithLiveness rewrittenWithLense(GraphLense lense) {
assert lense.isContextFree();
return new AppInfoWithLiveness(this, lense);
}
+
+ /**
+ * Returns true if the given type was part of the original program but has been removed during
+ * tree shaking.
+ */
+ public boolean wasPruned(DexType type) {
+ return prunedTypes.contains(type);
+ }
}
private static class SetWithReason<T> {
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 0fa5051..5e7b42d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -452,7 +452,7 @@
dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
.put(definition, context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
- noObfuscation.add(definition);
+ noObfuscation.add(type);
}
private void includeDescriptorClasses(DexItem item, ProguardKeepRule context) {
@@ -487,7 +487,11 @@
noOptimization.add(item);
}
if (!modifiers.allowsObfuscation) {
- noObfuscation.add(item);
+ if (item instanceof DexClass) {
+ noObfuscation.add(((DexClass) item).type);
+ } else {
+ noObfuscation.add(item);
+ }
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
@@ -520,38 +524,24 @@
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
- private boolean legalNoObfuscationItem(DexItem item) {
- if (!(item instanceof DexProgramClass
- || item instanceof DexLibraryClass
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField)) {
- }
- assert item instanceof DexProgramClass
- || item instanceof DexLibraryClass
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField;
- return true;
- }
-
- private boolean legalNoObfuscationItems(Set<DexItem> items) {
- items.forEach(this::legalNoObfuscationItem);
- return true;
- }
-
- private boolean legalDependentNoShrinkingItem(DexItem item) {
- if (!(item instanceof DexType
- || item instanceof DexEncodedMethod
- || item instanceof DexEncodedField)) {
- }
+ private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
assert item instanceof DexType
|| item instanceof DexEncodedMethod
|| item instanceof DexEncodedField;
+ return item instanceof DexType
+ || item instanceof DexEncodedMethod
+ || item instanceof DexEncodedField;
+ }
+
+ private boolean legalNoObfuscationItems(Set<DexItem> items) {
+ assert items.stream().allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
private boolean legalDependentNoShrinkingItems(
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
- dependentNoShrinking.keySet().forEach(this::legalDependentNoShrinkingItem);
+ assert dependentNoShrinking.keySet().stream()
+ .allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index c04b29a..193ebdf 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -655,4 +655,8 @@
return result;
}
}
+
+ public Collection<DexType> getRemovedClasses() {
+ return Collections.unmodifiableCollection(mergedClasses.keySet());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 64294bd..153f604 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,13 +8,17 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -23,7 +27,8 @@
private DexApplication application;
private final AppInfoWithLiveness appInfo;
private final InternalOptions options;
- private UsagePrinter usagePrinter;
+ private final UsagePrinter usagePrinter;
+ private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
public TreePruner(
DexApplication application, AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -64,6 +69,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing class: " + clazz);
}
+ prunedTypes.add(clazz.type);
usagePrinter.printUnusedClass(clazz);
} else {
newClasses.add(clazz);
@@ -190,4 +196,8 @@
}
return reachableFields.toArray(new DexEncodedField[reachableFields.size()]);
}
+
+ public Collection<DexType> getRemovedClasses() {
+ return Collections.unmodifiableCollection(prunedTypes);
+ }
}