Only enqueue program types in live sets.
Change-Id: Ic22166281522c23fff637b3b09ad4c31eb7e35a6
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 3c7fd3a..ea3472f 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerFactory;
import com.android.tools.r8.shaking.MainDexClasses;
@@ -64,7 +63,7 @@
}
Enqueuer enqueuer = EnqueuerFactory.createForMainDexTracing(appView, graphConsumer);
- Set<DexType> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
+ Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
// LiveTypes is the result.
MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, application).run();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ba1b041..aebcbee 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -420,7 +420,7 @@
mainDexRootSet =
new RootSetBuilder(appView, application, options.mainDexKeepRules).run(executorService);
// Live types is the tracing result.
- Set<DexType> mainDexBaseClasses =
+ Set<DexProgramClass> mainDexBaseClasses =
EnqueuerFactory.createForMainDexTracing(appView)
.traceMainDex(mainDexRootSet, executorService, timing);
// Calculate the automatic main dex list according to legacy multidex constraints.
@@ -601,7 +601,7 @@
EnqueuerFactory.createForMainDexTracing(appView, mainDexKeptGraphConsumer);
// Find classes which may have code executed before secondary dex files installation.
// Live types is the tracing result.
- Set<DexType> mainDexBaseClasses =
+ Set<DexProgramClass> mainDexBaseClasses =
enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
// Calculate the automatic main dex list according to legacy multidex constraints.
mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 3e0aa3a..0f60e07 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -36,6 +36,10 @@
this.annotation = annotation;
}
+ public DexType getAnnotationType() {
+ return annotation.type;
+ }
+
@Override
public int hashCode() {
return visibility + annotation.hashCode() * 3;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 15d5487..890b6c0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -148,20 +148,24 @@
} else if (reachesMapOrRequiredField(protoFieldInfo)) {
enqueuer.registerFieldAccess(encodedValueStorage.field, dynamicMethod);
worklist.enqueueMarkReachableFieldAction(
- encodedValueStorage.field, KeepReason.reflectiveUseIn(dynamicMethod));
+ clazz.asProgramClass(),
+ encodedValueStorage,
+ KeepReason.reflectiveUseIn(dynamicMethod));
encodedValueStorageIsLive = true;
} else {
encodedValueStorageIsLive = false;
}
- DexField newlyLiveField = null;
+ DexEncodedField newlyLiveField = null;
if (encodedValueStorageIsLive) {
// For one-of fields, mark the corresponding one-of-case field as live, and for proto2
// singular fields, mark the corresponding hazzer-bit field as live.
if (protoFieldInfo.getType().isOneOf()) {
- newlyLiveField = protoFieldInfo.getOneOfCaseField(protoMessageInfo);
+ newlyLiveField =
+ appView.appInfo().resolveField(protoFieldInfo.getOneOfCaseField(protoMessageInfo));
} else if (protoFieldInfo.hasHazzerBitField(protoMessageInfo)) {
- newlyLiveField = protoFieldInfo.getHazzerBitField(protoMessageInfo);
+ newlyLiveField =
+ appView.appInfo().resolveField(protoFieldInfo.getHazzerBitField(protoMessageInfo));
}
} else {
// For one-of fields, mark the one-of field as live if the one-of-case field is live, and
@@ -171,21 +175,21 @@
DexField oneOfCaseField = protoFieldInfo.getOneOfCaseField(protoMessageInfo);
DexEncodedField encodedOneOfCaseField = appView.appInfo().resolveField(oneOfCaseField);
if (encodedOneOfCaseField != null && enqueuer.isFieldLive(encodedOneOfCaseField)) {
- newlyLiveField = encodedValueStorage.field;
+ newlyLiveField = encodedValueStorage;
}
} else if (protoFieldInfo.hasHazzerBitField(protoMessageInfo)) {
DexField hazzerBitField = protoFieldInfo.getHazzerBitField(protoMessageInfo);
DexEncodedField encodedHazzerBitField = appView.appInfo().resolveField(hazzerBitField);
if (encodedHazzerBitField != null && enqueuer.isFieldLive(encodedHazzerBitField)) {
- newlyLiveField = encodedValueStorage.field;
+ newlyLiveField = encodedValueStorage;
}
}
}
if (newlyLiveField != null) {
- if (enqueuer.registerFieldAccess(newlyLiveField, dynamicMethod)) {
+ if (enqueuer.registerFieldAccess(newlyLiveField.field, dynamicMethod)) {
worklist.enqueueMarkReachableFieldAction(
- newlyLiveField, KeepReason.reflectiveUseIn(dynamicMethod));
+ clazz.asProgramClass(), newlyLiveField, KeepReason.reflectiveUseIn(dynamicMethod));
}
}
}
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 0435887..dc90b8f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -396,7 +396,9 @@
// done already in the reservation step for classes since there is only one 'path', unlike
// members that can be reserved differently in the hierarchy.
DexClass clazz = appView.definitionFor(type);
- assert clazz != null;
+ if (clazz == null) {
+ return type.descriptor;
+ }
if (clazz.isNotProgramClass() && mappings.containsKey(type)) {
return mappings.get(type);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 9437f7d..cadd59c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -113,9 +113,7 @@
if (appView.options().isShrinking() && definition == null) {
return false;
}
- return definition == null
- || definition.isNotProgramClass()
- || appView.appInfo().liveTypes.contains(annotationType);
+ return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
}
/**
@@ -215,17 +213,17 @@
if (appView.appInfo().isPinned(clazz.type)) {
for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
DexType inner = innerClassAttribute.getInner();
- if (appView.appInfo().liveTypes.contains(inner)) {
+ if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
result.add(inner);
}
DexType context = innerClassAttribute.getLiveContext(appView.appInfo());
- if (context != null && appView.appInfo().liveTypes.contains(context)) {
+ if (context != null && appView.appInfo().isNonProgramTypeOrLiveProgramType(context)) {
result.add(context);
}
}
}
if (clazz.getInnerClassAttributeForThisClass() != null
- && appView.appInfo().liveTypes.contains(clazz.type)
+ && appView.appInfo().isNonProgramTypeOrLiveProgramType(clazz.type)
&& hasGenericEnclosingClass(clazz, enclosingClasses, genericClasses)) {
result.add(clazz.type);
}
@@ -271,8 +269,7 @@
assert rewrite != null;
DexClass annotationClass = appView.definitionFor(rewrittenType);
assert annotationClass == null
- || annotationClass.isNotProgramClass()
- || appView.appInfo().liveTypes.contains(rewrittenType);
+ || appView.appInfo().isNonProgramTypeOrLiveProgramType(rewrittenType);
return rewrite;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 9df98de..9841fff 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -59,16 +59,16 @@
* Set of types that are mentioned in the program. We at least need an empty abstract classitem
* for these.
*/
- public final SortedSet<DexType> liveTypes;
+ private final Set<DexType> liveTypes;
/** Set of annotation types that are instantiated. */
- private final SortedSet<DexType> instantiatedAnnotationTypes;
+ private final Set<DexType> instantiatedAnnotationTypes;
/**
* Set of service types (from META-INF/services/) that may have been instantiated reflectively via
* ServiceLoader.load() or ServiceLoader.loadInstalled().
*/
- public final SortedSet<DexType> instantiatedAppServices;
+ public final Set<DexType> instantiatedAppServices;
/** Set of types that are actually instantiated. These cannot be abstract. */
- final SortedSet<DexType> instantiatedTypes;
+ final Set<DexType> instantiatedTypes;
/** Cache for {@link #isInstantiatedDirectlyOrIndirectly(DexType)}. */
private final IdentityHashMap<DexType, Boolean> indirectlyInstantiatedTypes =
new IdentityHashMap<>();
@@ -174,15 +174,15 @@
}
}
- final ImmutableSortedSet<DexType> instantiatedLambdas;
+ final Set<DexType> instantiatedLambdas;
// TODO(zerny): Clean up the constructors so we have just one.
private AppInfoWithLiveness(
DexApplication application,
- SortedSet<DexType> liveTypes,
- SortedSet<DexType> instantiatedAnnotationTypes,
- SortedSet<DexType> instantiatedAppServices,
- SortedSet<DexType> instantiatedTypes,
+ Set<DexType> liveTypes,
+ Set<DexType> instantiatedAnnotationTypes,
+ Set<DexType> instantiatedAppServices,
+ Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
@@ -213,7 +213,7 @@
Set<DexType> prunedTypes,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
- ImmutableSortedSet<DexType> instantiatedLambdas) {
+ Set<DexType> instantiatedLambdas) {
super(application);
this.liveTypes = liveTypes;
this.instantiatedAnnotationTypes = instantiatedAnnotationTypes;
@@ -255,10 +255,10 @@
public AppInfoWithLiveness(
AppInfoWithSubtyping appInfoWithSubtyping,
- SortedSet<DexType> liveTypes,
- SortedSet<DexType> instantiatedAnnotationTypes,
- SortedSet<DexType> instantiatedAppServices,
- SortedSet<DexType> instantiatedTypes,
+ Set<DexType> liveTypes,
+ Set<DexType> instantiatedAnnotationTypes,
+ Set<DexType> instantiatedAppServices,
+ Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
@@ -289,7 +289,7 @@
Set<DexType> prunedTypes,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
- ImmutableSortedSet<DexType> instantiatedLambdas) {
+ Set<DexType> instantiatedLambdas) {
super(appInfoWithSubtyping);
this.liveTypes = liveTypes;
this.instantiatedAnnotationTypes = instantiatedAnnotationTypes;
@@ -506,6 +506,23 @@
previous.markObsolete();
}
+ public boolean isLiveProgramClass(DexProgramClass clazz) {
+ return liveTypes.contains(clazz.type);
+ }
+
+ public boolean isLiveProgramType(DexType type) {
+ DexClass clazz = definitionFor(type);
+ return clazz.isProgramClass() && isLiveProgramClass(clazz.asProgramClass());
+ }
+
+ public boolean isNonProgramTypeOrLiveProgramType(DexType type) {
+ if (liveTypes.contains(type)) {
+ return true;
+ }
+ DexClass clazz = definitionFor(type);
+ return clazz == null || !clazz.isProgramClass();
+ }
+
public Collection<DexClass> computeReachableInterfaces(Set<DexCallSite> desugaredCallSites) {
Set<DexClass> interfaces = Sets.newIdentityHashSet();
Set<DexType> seen = Sets.newIdentityHashSet();
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 dc1a70e..9ec65b6 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
import static com.android.tools.r8.shaking.EnqueuerUtils.toImmutableSortedMap;
-import static com.google.common.base.Predicates.or;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.IndexedItemCollection;
@@ -35,6 +34,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
@@ -63,6 +63,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.shaking.EnqueuerWorklist.Action;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
@@ -76,7 +77,6 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
-import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
@@ -93,7 +93,6 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
@@ -166,7 +165,7 @@
private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
- private final Map<ProguardKeepRuleBase, GraphNode> ruleNodes = new IdentityHashMap<>();
+ private final Map<ProguardKeepRuleBase, KeepRuleGraphNode> ruleNodes = new IdentityHashMap<>();
private final Map<EdgeKind, GraphEdgeInfo> reasonInfo = new IdentityHashMap<>();
/**
@@ -179,11 +178,11 @@
* is reachable even if no live subtypes exist, so this is not sufficient for inclusion in the
* live set.
*/
- private final Map<DexType, SetWithStoredReason<DexEncodedMethod>> reachableVirtualMethods =
- Maps.newIdentityHashMap();
+ private final Map<DexProgramClass, SetWithStoredReason<DexEncodedMethod>>
+ reachableVirtualMethods = Maps.newIdentityHashMap();
// TODO(b/139464956): Lazily compute library dependencies.
- private final Map<DexType, Map<DexEncodedMethod, Set<DexType>>>
+ private final Map<DexProgramClass, Map<DexEncodedMethod, Set<DexType>>>
reachableVirtualMethodsFromLibraries = Maps.newIdentityHashMap();
/**
@@ -192,17 +191,18 @@
*/
private final Map<DexEncodedMethod, Set<DexEncodedMethod>> superInvokeDependencies = Maps
.newIdentityHashMap();
- /**
- * Set of instance fields that can be reached by read/write operations.
- */
- private final Map<DexType, SetWithReason<DexEncodedField>> reachableInstanceFields = Maps
- .newIdentityHashMap();
+ /** Set of instance fields that can be reached by read/write operations. */
+ private final Map<DexProgramClass, SetWithReason<DexEncodedField>> reachableInstanceFields =
+ Maps.newIdentityHashMap();
/**
* Set of types that are mentioned in the program. We at least need an empty abstract class item
* for these.
*/
- private final Set<DexType> liveTypes = Sets.newIdentityHashSet();
+ private final Set<DexProgramClass> liveTypes = Sets.newIdentityHashSet();
+
+ /** Set of live types defined in the library and classpath. Used to avoid duplicate tracing. */
+ private final Set<DexClass> liveNonProgramTypes = Sets.newIdentityHashSet();
/**
* Set of proto extension types that are technically live, but which we have not traced because
@@ -215,8 +215,10 @@
/** Set of annotation types that are instantiated. */
private final SetWithReason<DexAnnotation> liveAnnotations =
new SetWithReason<>(this::registerAnnotation);
+
/** Set of types that are actually instantiated. These cannot be abstract. */
- private final SetWithReason<DexType> instantiatedTypes = new SetWithReason<>(this::registerType);
+ private final SetWithReason<DexProgramClass> instantiatedTypes =
+ new SetWithReason<>(this::registerClass);
/**
* Set of methods that are the immediate target of an invoke. They might not actually be live but
* are required so that invokes can find the method. If a method is only a target but not live,
@@ -263,16 +265,14 @@
* Set of interface types for which a lambda expression can be reached. These never have a single
* interface implementation.
*/
- private final SetWithReason<DexType> instantiatedLambdas =
- new SetWithReason<>(this::registerType);
+ private final SetWithReason<DexProgramClass> instantiatedLambdas =
+ new SetWithReason<>(this::registerClass);
/** A queue of items that need processing. Different items trigger different actions. */
- private final EnqueuerWorklist workList = new EnqueuerWorklist();
+ private final EnqueuerWorklist workList;
- /**
- * A queue of items that have been added to try to keep Proguard compatibility.
- */
- private final Queue<Action> proguardCompatibilityWorkList = Queues.newArrayDeque();
+ /** A queue of items that have been added to try to keep Proguard compatibility. */
+ private final EnqueuerWorklist proguardCompatibilityWorkList;
/**
* A set of methods that need code inspection for Java reflection in use.
@@ -333,6 +333,9 @@
this.keptGraphConsumer = keptGraphConsumer;
this.mode = mode;
this.options = options;
+ this.workList = EnqueuerWorklist.createWorklist(appView);
+ this.proguardCompatibilityWorkList =
+ EnqueuerWorklist.createProguardCompatibilityWorklist(appView);
if (options.enableGeneratedMessageLiteShrinking && mode.isInitialOrFinalTreeShaking()) {
registerAnalysis(new ProtoEnqueuerExtension(appView));
@@ -348,6 +351,30 @@
return this;
}
+ private DexProgramClass getProgramClassOrNull(DexType type) {
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz != null) {
+ if (clazz.isProgramClass()) {
+ return clazz.asProgramClass();
+ }
+ if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) {
+ ensureMethodsContinueToWidenAccess(clazz);
+ warnIfLibraryTypeInheritsFromProgramType(clazz.asLibraryClass());
+ }
+ }
+ reportMissingClass(type);
+ return null;
+ }
+
+ private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
+ if (clazz.superType != null) {
+ ensureFromLibraryOrThrow(clazz.superType, clazz);
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ ensureFromLibraryOrThrow(iface, clazz);
+ }
+ }
+
private Set<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer() {
Set<DexField> result = Sets.newIdentityHashSet();
fieldAccessInfoCollection.forEach(
@@ -414,43 +441,74 @@
private void internalEnqueueRootItem(DexDefinition item, KeepReason reason) {
// TODO(b/120959039): do we need to propagate the reason to the action now?
if (item.isDexClass()) {
- DexClass clazz = item.asDexClass();
- workList.add(Action.markInstantiated(clazz, reason));
- if (clazz.hasDefaultInitializer()) {
- if (forceProguardCompatibility) {
- ProguardKeepRule compatRule =
- ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
- proguardCompatibilityWorkList.add(
- Action.markMethodLive(
- clazz.getDefaultInitializer(),
- KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
- }
- if (clazz.isExternalizable(appView)) {
- workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+ DexProgramClass clazz = item.asDexClass().asProgramClass();
+ if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
+ // TODO(zerny): This marking is due to a keep rule, so why add to the instantiatedLambdas?
+ markInterfaceAsInstantiatedByLambda(clazz, reason);
+ } else {
+ workList.enqueueMarkInstantiatedAction(clazz, reason);
+ if (clazz.hasDefaultInitializer()) {
+ if (forceProguardCompatibility) {
+ ProguardKeepRule compatRule =
+ ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+ proguardCompatibilityWorkList.enqueueMarkMethodKeptAction(
+ clazz.getDefaultInitializer(),
+ KeepReason.dueToProguardCompatibilityKeepRule(compatRule));
+ }
+ if (clazz.isExternalizable(appView)) {
+ enqueueMarkMethodLiveAction(clazz, clazz.getDefaultInitializer(), reason);
+ }
}
}
} else if (item.isDexEncodedField()) {
- workList.add(Action.markFieldKept(item.asDexEncodedField(), reason));
+ workList.enqueueMarkFieldKeptAction(item.asDexEncodedField(), reason);
} else if (item.isDexEncodedMethod()) {
- workList.add(Action.markMethodKept(item.asDexEncodedMethod(), reason));
+ workList.enqueueMarkMethodKeptAction(item.asDexEncodedMethod(), reason);
} else {
throw new IllegalArgumentException(item.toString());
}
pinnedItems.add(item.toReference());
}
- private void enqueueFirstNonSerializableClassInitializer(DexClass clazz, KeepReason reason) {
- assert clazz.isProgramClass() && clazz.isSerializable(appView);
+ // TODO(zerny): Why is this "ByLambda"?
+ private void markInterfaceAsInstantiatedByLambda(DexProgramClass clazz, KeepReason reason) {
+ assert clazz.isInterface() && !clazz.accessFlags.isAnnotation();
+
+ if (!instantiatedLambdas.add(clazz, reason)) {
+ return;
+ }
+
+ markTypeAsLive(clazz.type);
+ }
+
+ private void enqueueFirstNonSerializableClassInitializer(
+ DexProgramClass clazz, KeepReason reason) {
+ assert clazz.isSerializable(appView);
// Climb up the class hierarchy. Break out if the definition is not found, or hit the library
// classes which are kept by definition, or encounter the first non-serializable class.
- while (clazz != null && clazz.isProgramClass() && clazz.isSerializable(appView)) {
- clazz = appView.definitionFor(clazz.superType);
+ while (clazz.isSerializable(appView)) {
+ DexProgramClass superClass = getProgramClassOrNull(clazz.superType);
+ if (superClass == null) {
+ return;
+ }
+ clazz = superClass;
}
- if (clazz != null && clazz.isProgramClass() && clazz.hasDefaultInitializer()) {
- workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+ if (clazz.hasDefaultInitializer()) {
+ enqueueMarkMethodLiveAction(clazz, clazz.getDefaultInitializer(), reason);
}
}
+ // Utility to avoid adding to the worklist if already live.
+ private boolean enqueueMarkMethodLiveAction(
+ DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
+ assert method.method.holder == clazz.type;
+ if (liveMethods.add(method, reason)) {
+ workList.enqueueMarkMethodLiveAction(clazz, method, reason);
+ return true;
+ }
+ return false;
+ }
+
private void compatEnqueueHolderIfDependentNonStaticMember(
DexClass holder, Set<ProguardKeepRuleBase> compatRules) {
if (!forceProguardCompatibility || compatRules == null) {
@@ -550,7 +608,7 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeVirtual `%s`.", method);
}
- workList.add(Action.markReachableVirtual(method, keepReason));
+ workList.enqueueMarkReachableVirtualAction(method, keepReason);
return true;
}
@@ -617,7 +675,7 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
}
- workList.add(Action.markReachableInterface(method, keepReason));
+ workList.enqueueMarkReachableInterfaceAction(method, keepReason);
return true;
}
@@ -632,7 +690,7 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeSuper `%s`.", actualTarget);
}
- workList.add(Action.markReachableSuper(method, currentMethod));
+ workList.enqueueMarkReachableSuperAction(method, currentMethod);
return true;
}
@@ -641,11 +699,35 @@
if (!registerFieldWrite(field, currentMethod)) {
return false;
}
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field);
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ reportMissingField(field);
+ return false;
+ }
+
+ DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+ if (clazz == null) {
+ return false;
+ }
+
if (Log.ENABLED) {
Log.verbose(getClass(), "Register Iput `%s`.", field);
}
- // TODO(herhut): We have to add this, but DCR should eliminate dead writes.
- workList.enqueueMarkReachableFieldAction(field, KeepReason.fieldReferencedIn(currentMethod));
+
+ // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
+ // the field as live, if the holder is an interface.
+ if (appView.options().enableUnusedInterfaceRemoval) {
+ if (encodedField.field != field) {
+ markTypeAsLive(encodedField.field.holder);
+ markTypeAsLive(encodedField.field.type);
+ }
+ }
+
+ workList.enqueueMarkReachableFieldAction(
+ clazz, encodedField, KeepReason.fieldReferencedIn(currentMethod));
return true;
}
@@ -654,10 +736,36 @@
if (!registerFieldRead(field, currentMethod)) {
return false;
}
+
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field);
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ reportMissingField(field);
+ return false;
+ }
+
+ DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+ if (clazz == null) {
+ return false;
+ }
+
if (Log.ENABLED) {
Log.verbose(getClass(), "Register Iget `%s`.", field);
}
- workList.enqueueMarkReachableFieldAction(field, KeepReason.fieldReferencedIn(currentMethod));
+
+ // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
+ // the field as live, if the holder is an interface.
+ if (appView.options().enableUnusedInterfaceRemoval) {
+ if (encodedField.field != field) {
+ markTypeAsLive(encodedField.field.holder);
+ markTypeAsLive(encodedField.field.type);
+ }
+ }
+
+ workList.enqueueMarkReachableFieldAction(
+ clazz, encodedField, KeepReason.fieldReferencedIn(currentMethod));
return true;
}
@@ -667,7 +775,14 @@
}
public boolean registerNewInstance(DexType type, KeepReason keepReason) {
- markInstantiated(type, keepReason);
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz != null) {
+ if (clazz.isInterface()) {
+ markTypeAsLive(clazz.type);
+ } else {
+ markInstantiated(clazz, keepReason);
+ }
+ }
return true;
}
@@ -677,26 +792,37 @@
return false;
}
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field);
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ reportMissingField(field);
+ return false;
+ }
+
+ DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+ if (clazz == null) {
+ return false;
+ }
+
if (Log.ENABLED) {
Log.verbose(getClass(), "Register Sget `%s`.", field);
}
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null && encodedField.isProgramField(appView)) {
- if (appView.options().enableGeneratedExtensionRegistryShrinking) {
- // If it is a dead proto extension field, don't trace onwards.
- boolean skipTracing =
- appView.withGeneratedExtensionRegistryShrinker(
- shrinker ->
- shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
- false);
- if (skipTracing) {
- return false;
- }
+ if (appView.options().enableGeneratedExtensionRegistryShrinking) {
+ // If it is a dead proto extension field, don't trace onwards.
+ boolean skipTracing =
+ appView.withGeneratedExtensionRegistryShrinker(
+ shrinker ->
+ shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
+ false);
+ if (skipTracing) {
+ return false;
}
}
- markStaticFieldAsLive(field, KeepReason.fieldReferencedIn(currentMethod));
+ markStaticFieldAsLive(encodedField, KeepReason.fieldReferencedIn(currentMethod));
return true;
}
@@ -706,36 +832,44 @@
return false;
}
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field);
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ reportMissingField(field);
+ return false;
+ }
+
+ DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+ if (clazz == null) {
+ return false;
+ }
+
if (Log.ENABLED) {
Log.verbose(getClass(), "Register Sput `%s`.", field);
}
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField != null && encodedField.isProgramField(appView)) {
- if (appView.options().enableGeneratedExtensionRegistryShrinking) {
- // If it is a dead proto extension field, don't trace onwards.
- boolean skipTracing =
- appView.withGeneratedExtensionRegistryShrinker(
- shrinker ->
- shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
- false);
- if (skipTracing) {
- return false;
- }
- }
-
- // If it is written outside of the <clinit> of its enclosing class, record it.
- boolean isWrittenOutsideEnclosingStaticInitializer =
- currentMethod.method.holder != encodedField.field.holder
- || !currentMethod.isClassInitializer();
- if (isWrittenOutsideEnclosingStaticInitializer) {
- staticFieldsWrittenOutsideEnclosingStaticInitializer.add(encodedField.field);
+ if (appView.options().enableGeneratedExtensionRegistryShrinking) {
+ // If it is a dead proto extension field, don't trace onwards.
+ boolean skipTracing =
+ appView.withGeneratedExtensionRegistryShrinker(
+ shrinker ->
+ shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
+ false);
+ if (skipTracing) {
+ return false;
}
}
+ // If it is written outside of the <clinit> of its enclosing class, record it.
+ boolean isWrittenOutsideEnclosingStaticInitializer =
+ currentMethod.method.holder != encodedField.field.holder
+ || !currentMethod.isClassInitializer();
+ if (isWrittenOutsideEnclosingStaticInitializer) {
+ staticFieldsWrittenOutsideEnclosingStaticInitializer.add(encodedField.field);
+ }
- // TODO(herhut): We have to add this, but DCR should eliminate dead writes.
- markStaticFieldAsLive(field, KeepReason.fieldReferencedIn(currentMethod), encodedField);
-
+ markStaticFieldAsLive(encodedField, KeepReason.fieldReferencedIn(currentMethod));
return true;
}
@@ -765,9 +899,15 @@
// stays in the output (and is not class merged). To ensure that we treat the receiver
// as instantiated.
if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
- DexClass holder = appView.definitionFor(methodHandle.asMethod().holder);
- if (holder != null) {
- markInstantiated(holder.type, KeepReason.methodHandleReferencedIn(currentMethod));
+ DexType type = methodHandle.asMethod().holder;
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz != null) {
+ KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
+ if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
+ markInterfaceAsInstantiatedByLambda(clazz, reason);
+ } else {
+ markInstantiated(clazz, reason);
+ }
}
}
}
@@ -794,8 +934,9 @@
}
}
- DexClass bootstrapClass = appView.definitionFor(callSite.bootstrapMethod.asMethod().holder);
- if (bootstrapClass != null && bootstrapClass.isProgramClass()) {
+ DexProgramClass bootstrapClass =
+ getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
+ if (bootstrapClass != null) {
bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
}
@@ -848,21 +989,6 @@
return;
}
- Set<DexType> allInterfaces = Sets.newHashSet(directInterfaces);
- DexType instantiatedType = appView.dexItemFactory().objectType;
- DexClass clazz = appView.definitionFor(instantiatedType);
- if (clazz == null) {
- reportMissingClass(instantiatedType);
- return;
- }
-
- // We only have to look at virtual methods here, as only those can actually be executed at
- // runtime. Illegal dispatch situations and the corresponding exceptions are already handled
- // by the reachability logic.
- ScopedDexMethodSet seen = new ScopedDexMethodSet();
- transitionReachableVirtualMethods(instantiatedType, seen);
- Collections.addAll(allInterfaces, clazz.interfaces.values);
-
// The set now contains all virtual methods on the type and its supertype that are reachable.
// In a second step, we now look at interfaces. We have to do this in this order due to JVM
// semantics for default methods. A default method is only reachable if it is not overridden
@@ -871,44 +997,42 @@
// we have to look at the interface chain and mark default methods as reachable, not taking
// the shadowing of other interface chains into account.
// See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
- for (DexType iface : allInterfaces) {
- DexClass ifaceClazz = appView.definitionFor(iface);
- if (ifaceClazz == null) {
- reportMissingClass(iface);
- return;
+ ScopedDexMethodSet seen = new ScopedDexMethodSet();
+ for (DexType iface : directInterfaces) {
+ DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
+ if (ifaceClazz != null) {
+ transitionDefaultMethodsForInstantiatedClass(iface, seen);
}
- transitionDefaultMethodsForInstantiatedClass(iface, instantiatedType, seen);
}
}
private boolean registerConstClassOrCheckCast(DexType type) {
- if (forceProguardCompatibility) {
- DexType baseType = type.toBaseType(appView.dexItemFactory());
- if (baseType.isClassType()) {
- DexClass baseClass = appView.definitionFor(baseType);
- if (baseClass != null && baseClass.isProgramClass()) {
- // Don't require any constructor, see b/112386012.
- markClassAsInstantiatedWithCompatRule(baseClass);
- } else {
- // This also handles reporting of missing classes.
- markTypeAsLive(baseType);
- }
- return true;
- }
- return false;
- } else {
+ if (!forceProguardCompatibility) {
return registerTypeReference(type);
}
+ DexType baseType = type.toBaseType(appView.dexItemFactory());
+ if (baseType.isClassType()) {
+ DexProgramClass baseClass = getProgramClassOrNull(baseType);
+ if (baseClass != null && !baseClass.isInterface()) {
+ // Don't require any constructor, see b/112386012.
+ markClassAsInstantiatedWithCompatRule(baseClass);
+ } else {
+ // This also handles reporting of missing classes.
+ markTypeAsLive(baseType);
+ }
+ return true;
+ }
+ return false;
}
}
- private void transitionReachableVirtualMethods(DexType type, ScopedDexMethodSet seen) {
- SetWithStoredReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(type);
+ private void transitionReachableVirtualMethods(DexProgramClass clazz, ScopedDexMethodSet seen) {
+ SetWithStoredReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(clazz);
if (reachableMethods != null) {
- transitionNonAbstractMethodsToLiveAndShadow(reachableMethods, seen);
+ transitionNonAbstractMethodsToLiveAndShadow(clazz, reachableMethods, seen);
}
Map<DexEncodedMethod, Set<DexType>> libraryMethods =
- reachableVirtualMethodsFromLibraries.get(type);
+ reachableVirtualMethodsFromLibraries.get(clazz);
if (libraryMethods == null) {
return;
}
@@ -918,9 +1042,9 @@
// Abstract methods do shadow implementations but they cannot be live, as they have no
// code.
// TODO(b/120959039): The edge registration needs to be per interface.
- KeepReason reason = KeepReason.isLibraryMethod(type, entry.getValue().iterator().next());
+ KeepReason reason = KeepReason.isLibraryMethod(clazz, entry.getValue().iterator().next());
if (!encodedMethod.accessFlags.isAbstract()) {
- markVirtualMethodAsLive(encodedMethod, reason);
+ markVirtualMethodAsLive(clazz, encodedMethod, reason);
}
}
}
@@ -944,116 +1068,128 @@
// Actual actions performed.
//
+ private boolean verifyMethodIsTargeted(DexEncodedMethod method) {
+ assert !method.isClassInitializer() : "Class initializers are never targeted";
+ assert targetedMethods.contains(method);
+ return true;
+ }
+
+ private boolean verifyTypeIsLive(DexProgramClass clazz) {
+ assert liveTypes.contains(clazz);
+ return true;
+ }
+
private void markTypeAsLive(DexType type) {
markTypeAsLive(
type, scopedMethodsForLiveTypes.computeIfAbsent(type, ignore -> new ScopedDexMethodSet()));
}
private void markTypeAsLive(DexType type, ScopedDexMethodSet seen) {
- type = type.toBaseType(appView.dexItemFactory());
+ if (type.isArrayType()) {
+ markTypeAsLive(type.toBaseType(appView.dexItemFactory()));
+ return;
+ }
if (!type.isClassType()) {
// Ignore primitive types.
return;
}
- if (liveTypes.add(type)) {
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Type `%s` has become live.", type);
+ DexProgramClass holder = getProgramClassOrNull(type);
+ if (holder == null || !liveTypes.add(holder)) {
+ return;
+ }
+
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Type `%s` has become live.", type);
+ }
+
+ for (DexType iface : holder.interfaces.values) {
+ markInterfaceTypeAsLiveViaInheritanceClause(iface);
+ }
+
+ if (holder.superType != null) {
+ ScopedDexMethodSet seenForSuper =
+ scopedMethodsForLiveTypes.computeIfAbsent(
+ holder.superType, ignore -> new ScopedDexMethodSet());
+ seen.setParent(seenForSuper);
+ // TODO(b/120959039): The keep reason should be passed to markTypeAsLive.
+ DexClass holderSuper = appView.definitionFor(holder.superType);
+ if (holderSuper != null && holderSuper.isProgramClass()) {
+ registerType(holder.superType, KeepReason.reachableFromLiveType(type));
}
- DexClass holder = appView.definitionFor(type);
- if (holder == null) {
- reportMissingClass(type);
- return;
+ markTypeAsLive(holder.superType, seenForSuper);
+ }
+
+ KeepReason reason = KeepReason.reachableFromLiveType(type);
+
+ // We cannot remove virtual methods defined earlier in the type hierarchy if it is widening
+ // access and is defined in an interface:
+ //
+ // public interface I {
+ // void clone();
+ // }
+ //
+ // class Model implements I {
+ // public void clone() { ... } <-- this cannot be removed
+ // }
+ //
+ // Any class loading of Model with Model.clone() removed will result in an illegal access
+ // error because their exists an existing implementation (here it is Object.clone()). This is
+ // only a problem in the DEX VM. We have to make this check no matter the output because
+ // CF libraries can be used by Android apps. See b/136698023 for more information.
+ ensureMethodsContinueToWidenAccess(holder, seen, reason);
+
+ // We also need to add the corresponding <clinit> to the set of live methods, as otherwise
+ // static field initialization (and other class-load-time sideeffects) will not happen.
+ if (holder.hasClassInitializer()) {
+ DexEncodedMethod clinit = holder.getClassInitializer();
+ if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
+ assert clinit.method.holder == holder.type;
+ markDirectStaticOrConstructorMethodAsLive(holder, clinit, reason);
}
- for (DexType iface : holder.interfaces.values) {
- markInterfaceTypeAsLiveViaInheritanceClause(iface);
- }
- if (holder.superType != null) {
- ScopedDexMethodSet seenForSuper =
- scopedMethodsForLiveTypes.computeIfAbsent(
- holder.superType, ignore -> new ScopedDexMethodSet());
- seen.setParent(seenForSuper);
- DexClass holderSuper = appView.definitionFor(holder.superType);
- if (holderSuper != null && holderSuper.isProgramClass()) {
- registerType(holder.superType, KeepReason.reachableFromLiveType(type));
- }
- markTypeAsLive(holder.superType, seenForSuper);
- if (holder.isNotProgramClass()) {
- // Library classes may only extend other implement library classes.
- ensureNotFromProgramOrThrow(holder.superType, type);
- for (DexType iface : holder.interfaces.values) {
- ensureNotFromProgramOrThrow(iface, type);
- }
- }
+ }
+
+ if (holder.isSerializable(appView)) {
+ enqueueFirstNonSerializableClassInitializer(holder, reason);
+ }
+
+ if (!holder.annotations.isEmpty()) {
+ processAnnotations(holder, holder.annotations.annotations);
+ }
+ // If this type has deferred annotations, we have to process those now, too.
+ Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
+ if (annotations != null && !annotations.isEmpty()) {
+ assert holder.accessFlags.isAnnotation();
+ assert annotations.stream().allMatch(a -> a.annotation.type == holder.type);
+ annotations.forEach(annotation -> handleAnnotation(holder, annotation));
}
- KeepReason reason = KeepReason.reachableFromLiveType(type);
+ rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
+ compatEnqueueHolderIfDependentNonStaticMember(
+ holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
+ }
- // We cannot remove virtual methods defined earlier in the type hierarchy if it is widening
- // access and is defined in an interface:
- //
- // public interface I {
- // void clone();
- // }
- //
- // class Model implements I {
- // public void clone() { ... } <-- this cannot be removed
- // }
- //
- // Any class loading of Model with Model.clone() removed will result in an illegal access
- // error because their exists an existing implementation (here it is Object.clone()). This is
- // only a problem in the DEX VM. We have to make this check no matter the output because
- // CF libraries can be used by Android apps. See b/136698023 for more information.
- holder
- .virtualMethods()
- .forEach(
- m -> {
- if (seen.addMethodIfMoreVisible(m)
- == AddMethodIfMoreVisibleResult.ADDED_MORE_VISIBLE
- && holder.isProgramClass()
- && appView.appInfo().methodDefinedInInterfaces(m, holder.type)) {
- markMethodAsTargeted(m, reason);
- }
- });
+ private void ensureMethodsContinueToWidenAccess(DexClass clazz) {
+ assert !clazz.isProgramClass();
+ ScopedDexMethodSet seen =
+ scopedMethodsForLiveTypes.computeIfAbsent(clazz.type, ignore -> new ScopedDexMethodSet());
+ clazz.virtualMethods().forEach(seen::addMethodIfMoreVisible);
+ }
- // We also need to add the corresponding <clinit> to the set of live methods, as otherwise
- // static field initialization (and other class-load-time sideeffects) will not happen.
- if (holder.isProgramClass() && holder.hasClassInitializer()) {
- DexEncodedMethod clinit = holder.getClassInitializer();
- if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
- assert clinit.method.holder == holder.type;
- markDirectStaticOrConstructorMethodAsLive(clinit, reason);
- }
+ private void ensureMethodsContinueToWidenAccess(
+ DexProgramClass clazz, ScopedDexMethodSet seen, KeepReason reason) {
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ if (seen.addMethodIfMoreVisible(method) == AddMethodIfMoreVisibleResult.ADDED_MORE_VISIBLE
+ && clazz.isProgramClass()
+ && appView.appInfo().methodDefinedInInterfaces(method, clazz.type)) {
+ markMethodAsTargeted(clazz, method, reason);
}
-
- if (holder.isProgramClass() && holder.isSerializable(appView)) {
- enqueueFirstNonSerializableClassInitializer(holder, reason);
- }
-
- if (holder.isProgramClass()) {
- if (!holder.annotations.isEmpty()) {
- processAnnotations(holder, holder.annotations.annotations);
- }
- // If this type has deferred annotations, we have to process those now, too.
- Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
- if (annotations != null && !annotations.isEmpty()) {
- assert holder.accessFlags.isAnnotation();
- assert annotations.stream().allMatch(a -> a.annotation.type == holder.type);
- annotations.forEach(annotation -> handleAnnotation(holder, annotation));
- }
- } else {
- assert !deferredAnnotations.containsKey(holder.type);
- }
- rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
- compatEnqueueHolderIfDependentNonStaticMember(
- holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
}
}
private void markInterfaceTypeAsLiveViaInheritanceClause(DexType type) {
if (appView.options().enableUnusedInterfaceRemoval && !mode.isTracingMainDex()) {
- DexClass clazz = appView.definitionFor(type);
- if (clazz == null || !clazz.isProgramClass()) {
- markTypeAsLive(type);
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz == null) {
return;
}
@@ -1108,9 +1244,9 @@
private void handleAnnotation(DexDefinition holder, DexAnnotation annotation) {
assert !holder.isDexClass() || holder.asDexClass().isProgramClass();
DexType type = annotation.annotation.type;
- boolean annotationTypeIsLibraryClass =
- appView.definitionFor(type) == null || appView.definitionFor(type).isNotProgramClass();
- boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(type);
+ DexClass clazz = appView.definitionFor(type);
+ boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
+ boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
if (!shouldKeepAnnotation(annotation, isLive, appView.dexItemFactory(), options)) {
// Remember this annotation for later.
if (!annotationTypeIsLibraryClass) {
@@ -1125,53 +1261,76 @@
}
private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
- // We have to mark the resolved method as targeted even if it cannot actually be invoked
- // to make sure the invocation will keep failing in the appropriate way.
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
if (resolutionResult == null) {
reportMissingMethod(method);
return;
}
- resolutionResult.forEachTarget(m -> markMethodAsTargeted(m, reason));
+ DexEncodedMethod encodedMethod = resolutionResult.asSingleTarget();
+ if (encodedMethod == null) {
+ // Note: should this be reported too? Or is this unreachable?
+ return;
+ }
+ DexProgramClass clazz = getProgramClassOrNull(encodedMethod.method.holder);
+ if (clazz == null) {
+ return;
+ }
+
+ // We have to mark the resolved method as targeted even if it cannot actually be invoked
+ // to make sure the invocation will keep failing in the appropriate way.
+ markMethodAsTargeted(clazz, encodedMethod, reason);
+
// Only mark methods for which invocation will succeed at runtime live.
- DexEncodedMethod targetMethod = appInfo.dispatchStaticInvoke(resolutionResult);
- if (targetMethod != null) {
- registerClassInitializer(targetMethod.method.holder, reason);
- markDirectStaticOrConstructorMethodAsLive(targetMethod, reason);
+ if (encodedMethod.isStatic()) {
+ registerClassInitializer(clazz, reason);
+ markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
}
}
- private void registerClassInitializer(DexType holder, KeepReason reason) {
- DexClass definition = appView.definitionFor(holder);
- if (definition != null && definition.hasClassInitializer()) {
+ private void registerClassInitializer(DexProgramClass definition, KeepReason reason) {
+ if (definition.hasClassInitializer()) {
registerMethod(definition.getClassInitializer(), reason);
}
}
+ private void markNonStaticDirectMethodAsReachable(DexMethod method, KeepReason reason) {
+ handleInvokeOfDirectTarget(method, reason);
+ }
+
private void handleInvokeOfDirectTarget(DexMethod method, KeepReason reason) {
- // We have to mark the resolved method as targeted even if it cannot actually be invoked
- // to make sure the invocation will keep failing in the appropriate way.
- ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- if (resolutionResult == null) {
+ DexType holder = method.holder;
+ DexProgramClass clazz = getProgramClassOrNull(holder);
+ if (clazz == null) {
+ return;
+ }
+ // TODO(zerny): Is it ok that we lookup in both the direct and virtual pool here?
+ DexEncodedMethod encodedMethod = clazz.lookupMethod(method);
+ if (encodedMethod == null) {
reportMissingMethod(method);
return;
}
- resolutionResult.forEachTarget(m -> markMethodAsTargeted(m, reason));
- // Only mark methods for which invocation will succeed at runtime live.
- DexEncodedMethod target = appInfo.dispatchDirectInvoke(resolutionResult);
- if (target != null) {
- markDirectStaticOrConstructorMethodAsLive(target, reason);
- // It is valid to have an invoke-direct instruction in a default interface method that
- // targets another default method in the same interface (see testInvokeSpecialToDefault-
- // Method). In a class, that would lead to a verification error.
- if (target.isVirtualMethod()) {
- virtualMethodsTargetedByInvokeDirect.add(target.method);
- }
+ // We have to mark the resolved method as targeted even if it cannot actually be invoked
+ // to make sure the invocation will keep failing in the appropriate way.
+ markMethodAsTargeted(clazz, encodedMethod, reason);
+
+ // Only mark methods for which invocation will succeed at runtime live.
+ if (encodedMethod.isStatic()) {
+ return;
+ }
+
+ markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
+
+ // It is valid to have an invoke-direct instruction in a default interface method that
+ // targets another default method in the same interface (see testInvokeSpecialToDefault-
+ // Method). In a class, that would lead to a verification error.
+ if (encodedMethod.isVirtualMethod()
+ && virtualMethodsTargetedByInvokeDirect.add(encodedMethod.method)) {
+ enqueueMarkMethodLiveAction(clazz, encodedMethod, reason);
}
}
- private void ensureNotFromProgramOrThrow(DexType type, DexType context) {
+ private void ensureFromLibraryOrThrow(DexType type, DexClass context) {
if (!mode.isInitialTreeShaking()) {
// b/72312389: android.jar contains parts of JUnit and most developers include JUnit in
// their programs. This leads to library classes extending program classes. When tracing
@@ -1179,21 +1338,24 @@
return;
}
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null && clazz.isProgramClass()) {
- if (!dontWarnPatterns.matches(context)) {
- Diagnostic message =
- new StringDiagnostic(
- "Library class "
- + context.toSourceString()
- + (clazz.isInterface() ? " implements " : " extends ")
- + "program class "
- + type.toSourceString());
- if (forceProguardCompatibility) {
- options.reporter.warning(message);
- } else {
- options.reporter.error(message);
- }
+ if (dontWarnPatterns.matches(context.type)) {
+ // Ignore.
+ return;
+ }
+
+ DexClass holder = appView.definitionFor(type);
+ if (holder != null && !holder.isLibraryClass()) {
+ Diagnostic message =
+ new StringDiagnostic(
+ "Library class "
+ + context.type.toSourceString()
+ + (holder.isInterface() ? " implements " : " extends ")
+ + "program class "
+ + type.toSourceString());
+ if (forceProguardCompatibility) {
+ options.reporter.warning(message);
+ } else {
+ options.reporter.error(message);
}
}
}
@@ -1216,26 +1378,27 @@
}
}
- private void markMethodAsTargeted(DexEncodedMethod method, KeepReason reason) {
+ private void markMethodAsTargeted(
+ DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
+ assert method.method.holder == clazz.type;
if (!targetedMethods.add(method, reason)) {
+ // Already targeted.
return;
}
markTypeAsLive(method.method.holder);
markParameterAndReturnTypesAsLive(method);
- if (appView.definitionFor(method.method.holder).isProgramClass()) {
- processAnnotations(method, method.annotations.annotations);
- method.parameterAnnotationsList.forEachAnnotation(
- annotation -> processAnnotation(method, annotation));
- }
+ processAnnotations(method, method.annotations.annotations);
+ method.parameterAnnotationsList.forEachAnnotation(
+ annotation -> processAnnotation(method, annotation));
+
if (Log.ENABLED) {
Log.verbose(getClass(), "Method `%s` is targeted.", method.method);
}
if (forceProguardCompatibility) {
// Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
// these methods abstract, whereas Proguard does not (seem to) touch their code.
- DexClass clazz = appView.definitionFor(method.method.holder);
- if (!method.accessFlags.isAbstract() && clazz.isInterface() && clazz.isProgramClass()) {
- markMethodAsKeptWithCompatRule(method);
+ if (!method.accessFlags.isAbstract() && clazz.isInterface()) {
+ markMethodAsLiveWithCompatRule(clazz, method);
}
}
}
@@ -1244,15 +1407,15 @@
* Adds the class to the set of instantiated classes and marks its fields and methods live
* depending on the currently seen invokes and field reads.
*/
- private void processNewlyInstantiatedClass(DexClass clazz, KeepReason reason) {
+ private void processNewlyInstantiatedClass(DexProgramClass clazz, KeepReason reason) {
+ assert !clazz.isInterface() || clazz.accessFlags.isAnnotation();
// Notify analyses. This is done even if `clazz` has already been marked as instantiated,
// because each analysis may depend on seeing all the (clazz, reason) pairs. Thus, not doing so
// could lead to nondeterminism.
- if (clazz.isProgramClass()) {
- analyses.forEach(
- analysis -> analysis.processNewlyInstantiatedClass(clazz.asProgramClass(), reason));
- }
- if (!instantiatedTypes.add(clazz.type, reason)) {
+ analyses.forEach(
+ analysis -> analysis.processNewlyInstantiatedClass(clazz.asProgramClass(), reason));
+
+ if (!instantiatedTypes.add(clazz, reason)) {
return;
}
collectProguardCompatibilityRule(reason);
@@ -1263,43 +1426,37 @@
markTypeAsLive(clazz.type);
// For all methods of the class, if we have seen a call, mark the method live.
// We only do this for virtual calls, as the other ones will be done directly.
- transitionMethodsForInstantiatedClass(clazz.type);
+ transitionMethodsForInstantiatedClass(clazz);
// For all instance fields visible from the class, mark them live if we have seen a read.
- transitionFieldsForInstantiatedClass(clazz.type);
+ transitionFieldsForInstantiatedClass(clazz);
// Add all dependent instance members to the workqueue.
transitionDependentItemsForInstantiatedClass(clazz);
}
/**
* Marks all methods live that can be reached by calls previously seen.
- * <p>
+ *
* <p>This should only be invoked if the given type newly becomes instantiated. In essence, this
* method replays all the invokes we have seen so far that could apply to this type and marks the
* corresponding methods live.
- * <p>
+ *
* <p>Only methods that are visible in this type are considered. That is, only those methods that
* are either defined directly on this type or that are defined on a supertype but are not
* shadowed by another inherited method. Furthermore, default methods from implemented interfaces
* that are not otherwise shadowed are considered, too.
*/
- private void transitionMethodsForInstantiatedClass(DexType instantiatedType) {
+ private void transitionMethodsForInstantiatedClass(DexProgramClass instantiatedClass) {
ScopedDexMethodSet seen = new ScopedDexMethodSet();
Set<DexType> interfaces = Sets.newIdentityHashSet();
- DexType type = instantiatedType;
+ DexProgramClass current = instantiatedClass;
do {
- DexClass clazz = appView.definitionFor(type);
- if (clazz == null) {
- reportMissingClass(type);
- // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
- break;
- }
// We only have to look at virtual methods here, as only those can actually be executed at
// runtime. Illegal dispatch situations and the corresponding exceptions are already handled
// by the reachability logic.
- transitionReachableVirtualMethods(type, seen);
- Collections.addAll(interfaces, clazz.interfaces.values);
- type = clazz.superType;
- } while (type != null && !instantiatedTypes.contains(type));
+ transitionReachableVirtualMethods(current, seen);
+ Collections.addAll(interfaces, current.interfaces.values);
+ current = getProgramClassOrNull(current.superType);
+ } while (current != null && !instantiatedTypes.contains(current));
// The set now contains all virtual methods on the type and its supertype that are reachable.
// In a second step, we now look at interfaces. We have to do this in this order due to JVM
// semantics for default methods. A default method is only reachable if it is not overridden in
@@ -1315,32 +1472,35 @@
// TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
break;
}
- transitionDefaultMethodsForInstantiatedClass(iface, instantiatedType, seen);
+ transitionDefaultMethodsForInstantiatedClass(iface, seen);
}
}
- private void transitionDefaultMethodsForInstantiatedClass(DexType iface, DexType instantiatedType,
- ScopedDexMethodSet seen) {
- DexClass clazz = appView.definitionFor(iface);
+ private void transitionDefaultMethodsForInstantiatedClass(
+ DexType iface, ScopedDexMethodSet seen) {
+ DexProgramClass clazz = getProgramClassOrNull(iface);
if (clazz == null) {
- reportMissingClass(iface);
return;
}
assert clazz.accessFlags.isInterface();
- transitionReachableVirtualMethods(iface, seen.newNestedScope());
+ transitionReachableVirtualMethods(clazz, seen.newNestedScope());
for (DexType subInterface : clazz.interfaces.values) {
- transitionDefaultMethodsForInstantiatedClass(subInterface, instantiatedType, seen);
+ transitionDefaultMethodsForInstantiatedClass(subInterface, seen);
}
}
private void transitionNonAbstractMethodsToLiveAndShadow(
- SetWithStoredReason<DexEncodedMethod> reachable, ScopedDexMethodSet seen) {
+ DexProgramClass clazz,
+ SetWithStoredReason<DexEncodedMethod> reachable,
+ ScopedDexMethodSet seen) {
for (DexEncodedMethod encodedMethod : reachable.getItems()) {
if (seen.addMethod(encodedMethod)) {
// Abstract methods do shadow implementations but they cannot be live, as they have no code.
if (!encodedMethod.accessFlags.isAbstract()) {
markVirtualMethodAsLive(
- encodedMethod, registerMethod(encodedMethod, reachable.getReasons(encodedMethod)));
+ clazz,
+ encodedMethod,
+ registerMethod(encodedMethod, reachable.getReasons(encodedMethod)));
}
}
}
@@ -1350,26 +1510,17 @@
* Marks all fields live that can be reached by a read assuming that the given type or one of its
* subtypes is instantiated.
*/
- private void transitionFieldsForInstantiatedClass(DexType type) {
+ private void transitionFieldsForInstantiatedClass(DexProgramClass clazz) {
do {
- DexClass clazz = appView.definitionFor(type);
- if (clazz == null) {
- // TODO(herhut) The subtype chain is broken. We need a way to deal with this better.
- reportMissingClass(type);
- break;
- }
- if (!clazz.isProgramClass()) {
- break;
- }
- SetWithReason<DexEncodedField> reachableFields = reachableInstanceFields.get(type);
+ SetWithReason<DexEncodedField> reachableFields = reachableInstanceFields.get(clazz);
if (reachableFields != null) {
for (DexEncodedField field : reachableFields.getItems()) {
// TODO(b/120959039): Should the reason this field is reachable come from the set?
- markInstanceFieldAsLive(field, KeepReason.reachableFromLiveType(type));
+ markInstanceFieldAsLive(field, KeepReason.reachableFromLiveType(clazz.type));
}
}
- type = clazz.superType;
- } while (type != null && !instantiatedTypes.contains(type));
+ clazz = getProgramClassOrNull(clazz.superType);
+ } while (clazz != null && !instantiatedTypes.contains(clazz));
}
private void transitionDependentItemsForInstantiatedClass(DexClass clazz) {
@@ -1382,43 +1533,30 @@
current = current.superType != null ? appView.definitionFor(current.superType) : null;
} while (current != null
&& current.isProgramClass()
- && !instantiatedTypes.contains(current.type));
+ && !instantiatedTypes.contains(current.asProgramClass()));
}
- private void markStaticFieldAsLive(DexField field, KeepReason reason) {
- markStaticFieldAsLive(field, reason, appInfo.resolveField(field));
+ private void markFieldAsTargeted(DexField field) {
+ markTypeAsLive(field.type);
+ markTypeAsLive(field.holder);
}
- private void markStaticFieldAsLive(
- DexField field, KeepReason reason, DexEncodedField encodedField) {
+ private void markStaticFieldAsLive(DexEncodedField encodedField, KeepReason reason) {
// Mark the type live here, so that the class exists at runtime.
- registerClassInitializer(field.holder, reason);
+ DexField field = encodedField.field;
markTypeAsLive(field.holder);
markTypeAsLive(field.type);
- // Find the actual field.
- if (encodedField == null) {
- reportMissingField(field);
+ DexProgramClass clazz = getProgramClassOrNull(field.holder);
+ if (clazz == null) {
return;
}
- if (!encodedField.isProgramField(appView)) {
- return;
- }
-
- // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
- // the field as live, if the holder is an interface.
- if (appView.options().enableUnusedInterfaceRemoval) {
- if (encodedField.field != field) {
- markTypeAsLive(encodedField.field.holder);
- markTypeAsLive(encodedField.field.type);
- }
- }
+ registerClassInitializer(clazz, reason);
// This field might be an instance field reachable from a static context, e.g. a getStatic that
// resolves to an instance field. We have to keep the instance field nonetheless, as otherwise
// we might unmask a shadowed static field and hence change semantics.
-
if (encodedField.accessFlags.isStatic()) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Adding static field `%s` to live set.", encodedField.field);
@@ -1459,71 +1597,68 @@
analyses.forEach(analysis -> analysis.processNewlyLiveField(field));
}
- private void markInstantiated(DexType type, KeepReason reason) {
- DexClass clazz = appView.definitionFor(type);
- if (clazz == null) {
- reportMissingClass(type);
- return;
- }
+ private void markInstantiated(DexProgramClass clazz, KeepReason reason) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Register new instantiation of `%s`.", clazz);
}
- workList.add(Action.markInstantiated(clazz, reason));
+ workList.enqueueMarkInstantiatedAction(clazz, reason);
}
private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
DexClass clazz = appView.definitionFor(itf);
if (clazz == null) {
- if (options.reporter != null) {
- StringDiagnostic message =
- new StringDiagnostic(
- "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
- appInfo.originFor(method.method.holder));
- options.reporter.warning(message);
- }
+ StringDiagnostic message =
+ new StringDiagnostic(
+ "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
+ appInfo.originFor(method.method.holder));
+ options.reporter.warning(message);
return;
}
if (!clazz.isInterface()) {
- if (options.reporter != null) {
- StringDiagnostic message =
- new StringDiagnostic(
- "Lambda expression expected to implement an interface, but found "
- + "`" + itf.toSourceString() + "`",
- appInfo.originFor(method.method.holder));
- options.reporter.warning(message);
- }
+ StringDiagnostic message =
+ new StringDiagnostic(
+ "Lambda expression expected to implement an interface, but found "
+ + "`"
+ + itf.toSourceString()
+ + "`",
+ appInfo.originFor(method.method.holder));
+ options.reporter.warning(message);
return;
}
if (clazz.isProgramClass()) {
- instantiatedLambdas.add(itf, KeepReason.instantiatedIn(method));
+ instantiatedLambdas.add(clazz.asProgramClass(), KeepReason.instantiatedIn(method));
}
}
private void markDirectStaticOrConstructorMethodAsLive(
- DexEncodedMethod encodedMethod, KeepReason reason) {
- assert encodedMethod != null;
- markMethodAsTargeted(encodedMethod, reason);
- if (!liveMethods.contains(encodedMethod)) {
- markTypeAsLive(encodedMethod.method.holder);
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Method `%s` has become live due to direct invoke",
- encodedMethod.method);
- }
- workList.add(Action.markMethodLive(encodedMethod, reason));
+ DexProgramClass clazz, DexEncodedMethod encodedMethod, KeepReason reason) {
+ assert encodedMethod.method.holder == clazz.type;
+
+ if (!enqueueMarkMethodLiveAction(clazz, encodedMethod, reason)) {
+ // Already marked live.
+ return;
+ }
+ // Should already have marked the type live previously.
+ DexMethod method = encodedMethod.method;
+ assert encodedMethod.isClassInitializer() || verifyMethodIsTargeted(encodedMethod);
+ assert verifyTypeIsLive(clazz);
+ if (Log.ENABLED) {
+ Log.verbose(
+ getClass(), "Method `%s` has become live due to direct invoke", encodedMethod.method);
}
}
- private void markVirtualMethodAsLive(DexEncodedMethod method, KeepReason reason) {
+ private void markVirtualMethodAsLive(
+ DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
assert method != null;
// Only explicit keep rules or reflective use should make abstract methods live.
assert !method.accessFlags.isAbstract()
|| reason.isDueToKeepRule()
|| reason.isDueToReflectiveUse();
- if (!liveMethods.contains(method)) {
+ if (enqueueMarkMethodLiveAction(clazz, method, reason)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Adding virtual method `%s` to live set.", method.method);
}
- workList.add(Action.markMethodLive(method, reason));
}
}
@@ -1531,14 +1666,26 @@
return liveFields.contains(field);
}
- private boolean isInstantiatedOrHasInstantiatedSubtype(DexType type) {
- return instantiatedTypes.contains(type)
- || instantiatedLambdas.contains(type)
- || appInfo.subtypes(type).stream()
- .anyMatch(or(instantiatedTypes::contains, instantiatedLambdas::contains));
+ private boolean isInstantiatedOrHasInstantiatedSubtype(DexProgramClass clazz) {
+ // TODO(zerny): Cache this or instead compute this from subtype and up when instantiated.
+ if (instantiatedTypes.contains(clazz) || instantiatedLambdas.contains(clazz)) {
+ return true;
+ }
+ for (DexType subtype : appInfo.subtypes(clazz.type)) {
+ DexProgramClass subClass = getProgramClassOrNull(subtype);
+ if (subClass == null) {
+ assert appView.definitionFor(subtype) != null;
+ return true;
+ }
+ if (instantiatedTypes.contains(subClass) || instantiatedLambdas.contains(subClass)) {
+ return true;
+ }
+ }
+ return false;
}
- private void markInstanceFieldAsReachable(DexField field, KeepReason reason) {
+ private void markInstanceFieldAsReachable(DexEncodedField encodedField, KeepReason reason) {
+ DexField field = encodedField.field;
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking instance field `%s` as reachable.", field);
}
@@ -1546,39 +1693,25 @@
markTypeAsLive(field.holder);
markTypeAsLive(field.type);
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField == null) {
- reportMissingField(field);
+ DexProgramClass clazz = getProgramClassOrNull(field.holder);
+ if (clazz == null) {
return;
}
- if (!encodedField.isProgramField(appView)) {
- return;
- }
-
- // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
- // the field as live, if the holder is an interface.
- if (appView.options().enableUnusedInterfaceRemoval) {
- if (encodedField.field != field) {
- markTypeAsLive(encodedField.field.holder);
- markTypeAsLive(encodedField.field.type);
- }
- }
-
// We might have a instance field access that is dispatched to a static field. In such case,
// we have to keep the static field, so that the dispatch fails at runtime in the same way that
// it did before. We have to keep the field even if the receiver has no live inhabitants, as
// field resolution happens before the receiver is inspected.
if (encodedField.accessFlags.isStatic()) {
- markStaticFieldAsLive(encodedField.field, reason);
+ markStaticFieldAsLive(encodedField, reason);
} else {
- if (isInstantiatedOrHasInstantiatedSubtype(encodedField.field.holder)) {
+ if (isInstantiatedOrHasInstantiatedSubtype(clazz)) {
// We have at least one live subtype, so mark it as live.
markInstanceFieldAsLive(encodedField, reason);
} else {
// Add the field to the reachable set if the type later becomes instantiated.
reachableInstanceFields
- .computeIfAbsent(encodedField.field.holder, ignore -> newSetWithoutReasonReporter())
+ .computeIfAbsent(clazz, ignore -> newSetWithoutReasonReporter())
.add(encodedField, reason);
}
}
@@ -1631,79 +1764,14 @@
}
KeepReason overridesReason = KeepReason.overridesMethod(resolutionTarget);
for (DexEncodedMethod encodedPossibleTarget : possibleTargets) {
- DexMethod possibleTarget = encodedPossibleTarget.method;
- DexClass clazz = appView.definitionFor(possibleTarget.holder);
- if (clazz == null) {
- assert false;
+ if (encodedPossibleTarget.isAbstract()) {
continue;
}
-
- if (!clazz.isProgramClass()) {
- // Should only be tracing the program.
- continue;
- }
-
- if (!possibleTargetsFilter.test(clazz.asProgramClass(), encodedPossibleTarget)) {
- continue;
- }
-
- if (isLibraryEnqueueing) {
- Map<DexEncodedMethod, Set<DexType>> entry =
- reachableVirtualMethodsFromLibraries.computeIfAbsent(
- possibleTarget.holder, ignore -> Maps.newIdentityHashMap());
- Set<DexType> libraryTypes = entry.get(encodedPossibleTarget);
- if (libraryTypes != null) {
- libraryTypes.add(method.holder);
- continue;
- }
- entry.put(encodedPossibleTarget, SetUtils.newIdentityHashSet(method.holder, 2));
- } else {
- SetWithStoredReason<DexEncodedMethod> reachable =
- reachableVirtualMethods.computeIfAbsent(
- possibleTarget.holder, ignore -> SetWithStoredReason.create());
- if (!reachable.add(
- encodedPossibleTarget,
- resolutionTarget == encodedPossibleTarget ? reason : overridesReason)) {
- continue;
- }
- }
-
- // Abstract methods cannot be live.
- if (encodedPossibleTarget.accessFlags.isAbstract()) {
- continue;
- }
-
- // If the holder type is instantiated, the method is live. Otherwise check whether we find
- // a subtype that does not shadow this methods but is instantiated.
- // Note that library classes are always considered instantiated, as we do not know where
- // they are instantiated.
- if (!isInstantiatedOrHasInstantiatedSubtype(possibleTarget.holder)) {
- continue;
- }
-
- if (instantiatedTypes.contains(possibleTarget.holder)
- || instantiatedLambdas.contains(possibleTarget.holder)) {
- markVirtualMethodAsLive(
- encodedPossibleTarget, KeepReason.reachableFromLiveType(possibleTarget.holder));
- } else {
- Deque<DexType> worklist =
- new ArrayDeque<>(appInfo.allImmediateSubtypes(possibleTarget.holder));
- while (!worklist.isEmpty()) {
- DexType current = worklist.pollFirst();
- DexClass currentHolder = appView.definitionFor(current);
- // If this class overrides the virtual, abort the search. Note that, according to
- // the JVM spec, private methods cannot override a virtual method.
- if (currentHolder == null || currentHolder.lookupVirtualMethod(possibleTarget) != null) {
- continue;
- }
- if (instantiatedTypes.contains(current)) {
- markVirtualMethodAsLive(
- encodedPossibleTarget, KeepReason.reachableFromLiveType(current));
- break;
- }
- appInfo.allImmediateSubtypes(current).forEach(worklist::addLast);
- }
- }
+ markPossibleTargetsAsReachable(
+ resolutionTarget == encodedPossibleTarget ? reason : overridesReason,
+ possibleTargetsFilter,
+ isLibraryEnqueueing ? method.holder : null,
+ encodedPossibleTarget);
}
if (possibleTargetsConsumer != null) {
@@ -1711,6 +1779,74 @@
}
}
+ private void markPossibleTargetsAsReachable(
+ KeepReason reason,
+ BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter,
+ DexType enqueueingLibraryHolder,
+ DexEncodedMethod encodedPossibleTarget) {
+ assert encodedPossibleTarget.isVirtualMethod() || options.canUseNestBasedAccess();
+ assert !encodedPossibleTarget.isAbstract();
+ DexMethod possibleTarget = encodedPossibleTarget.method;
+ DexProgramClass clazz = getProgramClassOrNull(possibleTarget.holder);
+ if (clazz == null) {
+ return;
+ }
+ if (!possibleTargetsFilter.test(clazz, encodedPossibleTarget)) {
+ return;
+ }
+ if (enqueueingLibraryHolder != null) {
+ Map<DexEncodedMethod, Set<DexType>> entry =
+ reachableVirtualMethodsFromLibraries.computeIfAbsent(
+ clazz, ignore -> Maps.newIdentityHashMap());
+ Set<DexType> libraryTypes = entry.get(encodedPossibleTarget);
+ if (libraryTypes != null) {
+ libraryTypes.add(enqueueingLibraryHolder);
+ return;
+ }
+ entry.put(encodedPossibleTarget, SetUtils.newIdentityHashSet(enqueueingLibraryHolder, 2));
+ } else {
+ SetWithStoredReason<DexEncodedMethod> reachable =
+ reachableVirtualMethods.computeIfAbsent(clazz, ignore -> SetWithStoredReason.create());
+ if (!reachable.add(encodedPossibleTarget, reason)) {
+ return;
+ }
+ }
+
+ // If the holder type is instantiated, the method is live. Otherwise check whether we find
+ // a subtype that does not shadow this methods but is instantiated.
+ // Note that library classes are always considered instantiated, as we do not know where
+ // they are instantiated.
+ if (!isInstantiatedOrHasInstantiatedSubtype(clazz)) {
+ return;
+ }
+
+ if (instantiatedTypes.contains(clazz)
+ || instantiatedLambdas.contains(clazz)
+ || pinnedItems.contains(clazz.type)) {
+ markVirtualMethodAsLive(
+ clazz, encodedPossibleTarget, KeepReason.reachableFromLiveType(possibleTarget.holder));
+ } else {
+ Deque<DexType> worklist =
+ new ArrayDeque<>(appInfo.allImmediateSubtypes(possibleTarget.holder));
+ while (!worklist.isEmpty()) {
+ DexType current = worklist.pollFirst();
+ DexProgramClass currentClass = getProgramClassOrNull(current);
+ // If this class overrides the virtual, abort the search. Note that, according to
+ // the JVM spec, private methods cannot override a virtual method.
+ if (currentClass == null || currentClass.lookupVirtualMethod(possibleTarget) != null) {
+ continue;
+ }
+ // TODO(zerny): Why does not not confer with lambdas and pinned too?
+ if (instantiatedTypes.contains(currentClass)) {
+ markVirtualMethodAsLive(
+ clazz, encodedPossibleTarget, KeepReason.reachableFromLiveType(current));
+ break;
+ }
+ appInfo.allImmediateSubtypes(current).forEach(worklist::addLast);
+ }
+ }
+ }
+
private DexEncodedMethod findAndMarkResolutionTarget(
DexMethod method, boolean interfaceInvoke, KeepReason reason) {
DexEncodedMethod resolutionTarget =
@@ -1730,7 +1866,7 @@
// need at least an abstract version of it so that we have a target for the corresponding
// invoke. This also ensures preserving the errors detailed below.
if (resolutionTargetClass.isProgramClass()) {
- markMethodAsTargeted(resolutionTarget, reason);
+ markMethodAsTargeted(resolutionTargetClass.asProgramClass(), resolutionTarget, reason);
}
// If the method of an invoke-virtual instruction resolves to a private or static method, then
@@ -1776,7 +1912,7 @@
}
}
if (resolutionTargetClass.isProgramClass()) {
- markMethodAsTargeted(resolutionTarget, reason);
+ markMethodAsTargeted(resolutionTargetClass.asProgramClass(), resolutionTarget, reason);
}
return resolutionTarget;
}
@@ -1816,10 +1952,16 @@
reportMissingMethod(method);
return;
}
+
if (resolutionTarget.accessFlags.isPrivate() || resolutionTarget.accessFlags.isStatic()) {
brokenSuperInvokes.add(method);
}
- markMethodAsTargeted(resolutionTarget, KeepReason.targetedBySuperFrom(from));
+ DexProgramClass resolutionTargetClass = getProgramClassOrNull(resolutionTarget.method.holder);
+ if (resolutionTargetClass != null) {
+ markMethodAsTargeted(
+ resolutionTargetClass, resolutionTarget, KeepReason.targetedBySuperFrom(from));
+ }
+
// Now we need to compute the actual target in the context.
DexEncodedMethod target = appInfo.lookupSuperTarget(method, from.method.holder);
if (target == null) {
@@ -1827,6 +1969,10 @@
reportMissingMethod(method);
return;
}
+ DexProgramClass clazz = getProgramClassOrNull(target.method.holder);
+ if (clazz == null) {
+ return;
+ }
if (target.accessFlags.isPrivate()) {
brokenSuperInvokes.add(method);
}
@@ -1837,16 +1983,16 @@
if (superInvokeDependencies.computeIfAbsent(
from, ignore -> Sets.newIdentityHashSet()).add(target)) {
if (liveMethods.contains(from)) {
- markMethodAsTargeted(target, KeepReason.invokedViaSuperFrom(from));
+ markMethodAsTargeted(clazz, target, KeepReason.invokedViaSuperFrom(from));
if (!target.accessFlags.isAbstract()) {
- markVirtualMethodAsLive(target, KeepReason.invokedViaSuperFrom(from));
+ markVirtualMethodAsLive(clazz, target, KeepReason.invokedViaSuperFrom(from));
}
}
}
}
// Returns the set of live types.
- public SortedSet<DexType> traceMainDex(
+ public Set<DexProgramClass> traceMainDex(
RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
assert analyses.isEmpty();
assert mode.isTracingMainDex();
@@ -1855,7 +2001,7 @@
enqueueRootItems(rootSet.noShrinking);
trace(executorService, timing);
options.reporter.failIfPendingErrors();
- return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo, liveTypes);
+ return Collections.unmodifiableSet(liveTypes);
}
public AppInfoWithLiveness traceApplication(
@@ -1892,17 +2038,18 @@
AppInfoWithLiveness appInfoWithLiveness =
new AppInfoWithLiveness(
appInfo,
- ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo, liveTypes),
- builder.build(),
- ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo, instantiatedAppServices),
- ImmutableSortedSet.copyOf(
- PresortedComparable::slowCompareTo, instantiatedTypes.getItems()),
+ SetUtils.mapIdentityHashSet(liveTypes, DexProgramClass::getType),
+ SetUtils.mapIdentityHashSet(
+ liveAnnotations.getItems(), DexAnnotation::getAnnotationType),
+ Collections.unmodifiableSet(instantiatedAppServices),
+ SetUtils.mapIdentityHashSet(instantiatedTypes.getItems(), DexProgramClass::getType),
Enqueuer.toSortedDescriptorSet(targetedMethods.getItems()),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, bootstrapMethods),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, methodsTargetedByInvokeDynamic),
ImmutableSortedSet.copyOf(
DexMethod::slowCompareTo, virtualMethodsTargetedByInvokeDirect),
toSortedDescriptorSet(liveMethods.getItems()),
+ // Filter out library fields and pinned fields, because these are read by default.
fieldAccessInfoCollection,
ImmutableSortedSet.copyOf(
DexField::slowCompareTo, staticFieldsWrittenOnlyInEnclosingStaticInitializer()),
@@ -1930,8 +2077,7 @@
Collections.emptySet(),
Collections.emptyMap(),
Collections.emptyMap(),
- ImmutableSortedSet.copyOf(
- PresortedComparable::slowCompareTo, instantiatedLambdas.getItems()));
+ SetUtils.mapIdentityHashSet(instantiatedLambdas.getItems(), DexProgramClass::getType));
appInfo.markObsolete();
return appInfoWithLiveness;
}
@@ -1939,7 +2085,7 @@
private static <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
Set<? extends KeyedDexItem<T>> set) {
ImmutableSortedSet.Builder<T> builder =
- new ImmutableSortedSet.Builder<>(PresortedComparable<T>::slowCompareTo);
+ new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo);
for (KeyedDexItem<T> item : set) {
builder.add(item.getKey());
}
@@ -1969,10 +2115,13 @@
Action action = workList.poll();
switch (action.kind) {
case MARK_INSTANTIATED:
- processNewlyInstantiatedClass((DexClass) action.target, action.reason);
+ processNewlyInstantiatedClass((DexProgramClass) action.target, action.reason);
break;
case MARK_REACHABLE_FIELD:
- markInstanceFieldAsReachable((DexField) action.target, action.reason);
+ markInstanceFieldAsReachable((DexEncodedField) action.target, action.reason);
+ break;
+ case MARK_REACHABLE_DIRECT:
+ markNonStaticDirectMethodAsReachable((DexMethod) action.target, action.reason);
break;
case MARK_REACHABLE_VIRTUAL:
markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
@@ -1991,10 +2140,10 @@
markFieldAsKept((DexEncodedField) action.target, action.reason);
break;
case MARK_METHOD_LIVE:
- processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
+ markMethodAsLive(((DexEncodedMethod) action.target), action.reason);
break;
default:
- throw new IllegalArgumentException(action.kind.toString());
+ throw new IllegalArgumentException("" + action.kind);
}
}
@@ -2053,8 +2202,7 @@
pendingReflectiveUses.clear();
}
if (!proguardCompatibilityWorkList.isEmpty()) {
- workList.addAll(proguardCompatibilityWorkList);
- proguardCompatibilityWorkList.clear();
+ proguardCompatibilityWorkList.transferTo(workList, liveMethods::add);
}
if (!workList.isEmpty()) {
continue;
@@ -2073,14 +2221,14 @@
if (Log.ENABLED) {
Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
- for (Entry<DexType, SetWithStoredReason<DexEncodedMethod>> entry :
+ for (Entry<DexProgramClass, SetWithStoredReason<DexEncodedMethod>> entry :
reachableVirtualMethods.entrySet()) {
allLive.addAll(entry.getValue().getItems());
}
Set<DexEncodedMethod> reachableNotLive = Sets.difference(allLive, liveMethods.getItems());
Log.debug(getClass(), "%s methods are reachable but not live", reachableNotLive.size());
Log.info(getClass(), "Only reachable: %s", reachableNotLive);
- Set<DexType> liveButNotInstantiated =
+ Set<DexProgramClass> liveButNotInstantiated =
Sets.difference(liveTypes, instantiatedTypes.getItems());
Log.debug(getClass(), "%s classes are live but not instantiated",
liveButNotInstantiated.size());
@@ -2090,8 +2238,6 @@
Log.debug(getClass(), "%s methods are targeted but not live", targetedButNotLive.size());
Log.info(getClass(), "Targeted but not live: %s", targetedButNotLive);
}
- assert liveTypes.stream().allMatch(DexType::isClassType);
- assert instantiatedTypes.getItems().stream().allMatch(DexType::isClassType);
} finally {
timing.end();
}
@@ -2107,8 +2253,8 @@
}
private void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
- DexClass holder = appView.definitionFor(target.method.holder);
- // If this method no longer has a corresponding class then we have shaken it away before.
+ DexMethod method = target.method;
+ DexProgramClass holder = getProgramClassOrNull(method.holder);
if (holder == null) {
return;
}
@@ -2116,37 +2262,41 @@
// A virtual method. Mark it as reachable so that subclasses, if instantiated, keep
// their overrides. However, we don't mark it live, as a keep rule might not imply that
// the corresponding class is live.
- markVirtualMethodAsReachable(target.method, holder.accessFlags.isInterface(), reason);
- // Reachability for default methods is based on live subtypes in general. For keep rules,
- // we need special handling as we essentially might have live subtypes that are outside of
- // the current compilation unit. Keep either the default-method or its implementation method.
- // TODO(b/120959039): Codify the kept-graph expectations for these cases in tests.
- if (holder.isInterface() && target.isVirtualMethod()) {
+ if (!holder.isInterface()) {
+ workList.enqueueMarkReachableVirtualAction(method, reason);
+ } else {
+ workList.enqueueMarkReachableInterfaceAction(method, reason);
+ // Reachability for default methods is based on live subtypes in general. For keep rules,
+ // we need special handling as we essentially might have live subtypes that are outside of
+ // the current compilation unit. Keep either the default-method or its implementation
+ // method.
+ // TODO(b/120959039): Codify the kept-graph expectations for these cases in tests.
if (target.isNonAbstractVirtualMethod()) {
- markVirtualMethodAsLive(target, reason);
+ markVirtualMethodAsLive(holder, target, reason);
} else {
DexEncodedMethod implementation = target.getDefaultInterfaceMethodImplementation();
if (implementation != null) {
- DexClass companion = appView.definitionFor(implementation.method.holder);
+ DexProgramClass companion = getProgramClassOrNull(implementation.method.holder);
markTypeAsLive(companion.type);
- markVirtualMethodAsLive(implementation, reason);
+ markVirtualMethodAsLive(companion, implementation, reason);
}
}
}
} else {
- markDirectStaticOrConstructorMethodAsLive(target, reason);
+ markMethodAsTargeted(holder, target, reason);
+ markDirectStaticOrConstructorMethodAsLive(holder, target, reason);
}
}
private void markFieldAsKept(DexEncodedField target, KeepReason reason) {
- // If this field no longer has a corresponding class, then we have shaken it away before.
- if (appView.definitionFor(target.field.holder) == null) {
+ DexProgramClass clazz = getProgramClassOrNull(target.field.holder);
+ if (clazz == null) {
return;
}
if (target.accessFlags.isStatic()) {
- markStaticFieldAsLive(target.field, reason);
+ markStaticFieldAsLive(target, reason);
} else {
- markInstanceFieldAsReachable(target.field, reason);
+ markInstanceFieldAsReachable(target, reason);
}
}
@@ -2156,9 +2306,8 @@
getClass(), "Marking all methods of library class `%s` as reachable.", clazz.type);
}
// TODO(b/139464956, b/124480748): Remove this 'reason'. Lazy load libraries and no reporting.
- KeepReason reason = KeepReason.isLibraryMethod(clazz.type, clazz.type);
+ KeepReason reason = KeepReasonWitness.INSTANCE;
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
- markMethodAsTargeted(encodedMethod, reason);
markVirtualMethodAsReachable(
encodedMethod.method,
clazz.isInterface(),
@@ -2210,40 +2359,45 @@
return false;
}
- private void processNewlyLiveMethod(DexEncodedMethod method, KeepReason reason) {
- if (liveMethods.add(method, reason)) {
- collectProguardCompatibilityRule(reason);
- DexClass holder = appView.definitionFor(method.method.holder);
- assert holder != null;
- if (holder.isNotProgramClass()) {
- // We do not process library classes.
- return;
- }
- Set<DexEncodedMethod> superCallTargets = superInvokeDependencies.get(method);
- if (superCallTargets != null) {
- for (DexEncodedMethod superCallTarget : superCallTargets) {
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Found super invoke constraint on `%s`.",
- superCallTarget.method);
- }
- markMethodAsTargeted(superCallTarget, KeepReason.invokedViaSuperFrom(method));
- markVirtualMethodAsLive(superCallTarget, KeepReason.invokedViaSuperFrom(method));
+ private void markMethodAsLive(DexEncodedMethod method, KeepReason reason) {
+ assert liveMethods.contains(method);
+
+ DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+
+ collectProguardCompatibilityRule(reason);
+
+ Set<DexEncodedMethod> superCallTargets = superInvokeDependencies.get(method);
+ if (superCallTargets != null) {
+ for (DexEncodedMethod superCallTarget : superCallTargets) {
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Found super invoke constraint on `%s`.", superCallTarget.method);
+ }
+ DexProgramClass targetClass = getProgramClassOrNull(superCallTarget.method.holder);
+ assert targetClass != null;
+ if (targetClass != null) {
+ markMethodAsTargeted(
+ targetClass, superCallTarget, KeepReason.invokedViaSuperFrom(method));
+ markVirtualMethodAsLive(
+ targetClass, superCallTarget, KeepReason.invokedViaSuperFrom(method));
}
}
- markParameterAndReturnTypesAsLive(method);
- if (appView.definitionFor(method.method.holder).isProgramClass()) {
- processAnnotations(method, method.annotations.annotations);
- method.parameterAnnotationsList.forEachAnnotation(
- annotation -> processAnnotation(method, annotation));
- }
- method.registerCodeReferences(new UseRegistry(options.itemFactory, method));
-
- // Add all dependent members to the workqueue.
- enqueueRootItems(rootSet.getDependentItems(method));
-
- // Notify analyses.
- analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method));
}
+ markParameterAndReturnTypesAsLive(method);
+ if (appView.definitionFor(method.method.holder).isProgramClass()) {
+ processAnnotations(method, method.annotations.annotations);
+ method.parameterAnnotationsList.forEachAnnotation(
+ annotation -> processAnnotation(method, annotation));
+ }
+ method.registerCodeReferences(new UseRegistry(options.itemFactory, method));
+
+ // Add all dependent members to the workqueue.
+ enqueueRootItems(rootSet.getDependentItems(method));
+
+ // Notify analyses.
+ analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method));
}
private void markParameterAndReturnTypesAsLive(DexEncodedMethod method) {
@@ -2259,31 +2413,29 @@
}
}
- private void markClassAsInstantiatedWithReason(DexClass clazz, KeepReason reason) {
- assert clazz.isProgramClass();
- workList.add(Action.markInstantiated(clazz, reason));
+ private void markClassAsInstantiatedWithReason(DexProgramClass clazz, KeepReason reason) {
+ workList.enqueueMarkInstantiatedAction(clazz, reason);
if (clazz.hasDefaultInitializer()) {
- workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+ workList.enqueueMarkReachableDirectAction(clazz.getDefaultInitializer().method, reason);
}
}
- private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
+ private void markClassAsInstantiatedWithCompatRule(DexProgramClass clazz) {
ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
- proguardCompatibilityWorkList.add(
- Action.markInstantiated(clazz, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ KeepReason reason = KeepReason.dueToProguardCompatibilityKeepRule(rule);
+ proguardCompatibilityWorkList.enqueueMarkInstantiatedAction(clazz, reason);
if (clazz.hasDefaultInitializer()) {
- proguardCompatibilityWorkList.add(
- Action.markMethodLive(
- clazz.getDefaultInitializer(), KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ proguardCompatibilityWorkList.enqueueMarkReachableDirectAction(
+ clazz.getDefaultInitializer().method, reason);
}
}
- private void markMethodAsKeptWithCompatRule(DexEncodedMethod method) {
- DexClass holderClass = appView.definitionFor(method.method.holder);
- ProguardKeepRule rule =
- ProguardConfigurationUtils.buildMethodKeepRule(holderClass, method);
- proguardCompatibilityWorkList.add(
- Action.markMethodLive(method, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ private void markMethodAsLiveWithCompatRule(DexProgramClass clazz, DexEncodedMethod method) {
+ proguardCompatibilityWorkList.enqueueMarkMethodLiveAction(
+ clazz,
+ method,
+ KeepReason.dueToProguardCompatibilityKeepRule(
+ ProguardConfigurationUtils.buildMethodKeepRule(clazz, method)));
}
private void handleReflectiveBehavior(DexEncodedMethod method) {
@@ -2332,44 +2484,61 @@
return;
}
if (identifierItem.isDexType()) {
- DexClass clazz = appView.definitionFor(identifierItem.asDexType());
- if (clazz != null) {
- markInstantiated(clazz.type, KeepReason.reflectiveUseIn(method));
+ DexProgramClass clazz = getProgramClassOrNull(identifierItem.asDexType());
+ if (clazz == null) {
+ return;
+ }
+ if (!clazz.isInterface()) {
+ markInstantiated(clazz, KeepReason.reflectiveUseIn(method));
if (clazz.hasDefaultInitializer()) {
- markDirectStaticOrConstructorMethodAsLive(
- clazz.getDefaultInitializer(), KeepReason.reflectiveUseIn(method));
+ DexEncodedMethod initializer = clazz.getDefaultInitializer();
+ KeepReason reason = KeepReason.reflectiveUseIn(method);
+ markMethodAsTargeted(clazz, initializer, reason);
+ markDirectStaticOrConstructorMethodAsLive(clazz, initializer, reason);
}
}
} else if (identifierItem.isDexField()) {
- DexEncodedField encodedField = appView.definitionFor(identifierItem.asDexField());
- if (encodedField != null) {
- // Normally, we generate a -keepclassmembers rule for the field, such that the field is only
- // kept if it is a static field, or if the holder or one of its subtypes are instantiated.
- // However, if the invoked method is a field updater, then we always need to keep instance
- // fields since the creation of a field updater throws a NoSuchFieldException if the field
- // is not present.
- boolean keepClass =
- !encodedField.accessFlags.isStatic()
- && dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod);
- if (keepClass) {
- DexClass holderClass = appView.definitionFor(encodedField.field.holder);
- markInstantiated(holderClass.type, KeepReason.reflectiveUseIn(method));
- }
- markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
- // Fields accessed by reflection is marked as both read and written.
- registerFieldRead(encodedField.field, method);
- registerFieldWrite(encodedField.field, method);
+ DexField field = identifierItem.asDexField();
+ DexProgramClass clazz = getProgramClassOrNull(field.holder);
+ if (clazz == null) {
+ return;
}
+ DexEncodedField encodedField = appView.definitionFor(field);
+ if (encodedField == null) {
+ return;
+ }
+ // Normally, we generate a -keepclassmembers rule for the field, such that the field is only
+ // kept if it is a static field, or if the holder or one of its subtypes are instantiated.
+ // However, if the invoked method is a field updater, then we always need to keep instance
+ // fields since the creation of a field updater throws a NoSuchFieldException if the field
+ // is not present.
+ boolean keepClass =
+ !encodedField.accessFlags.isStatic()
+ && dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod);
+ if (keepClass) {
+ markInstantiated(clazz, KeepReason.reflectiveUseIn(method));
+ }
+ markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
+ // Fields accessed by reflection is marked as both read and written.
+ registerFieldRead(encodedField.field, method);
+ registerFieldWrite(encodedField.field, method);
+
} else {
assert identifierItem.isDexMethod();
- DexEncodedMethod encodedMethod = appView.definitionFor(identifierItem.asDexMethod());
- if (encodedMethod != null) {
- if (encodedMethod.accessFlags.isStatic() || encodedMethod.accessFlags.isConstructor()) {
- markDirectStaticOrConstructorMethodAsLive(
- encodedMethod, KeepReason.reflectiveUseIn(method));
- } else {
- markVirtualMethodAsLive(encodedMethod, KeepReason.reflectiveUseIn(method));
- }
+ DexMethod targetedMethod = identifierItem.asDexMethod();
+ DexProgramClass clazz = getProgramClassOrNull(targetedMethod.holder);
+ if (clazz == null) {
+ return;
+ }
+ DexEncodedMethod encodedMethod = appView.definitionFor(targetedMethod);
+ if (encodedMethod == null) {
+ return;
+ }
+ if (encodedMethod.accessFlags.isStatic() || encodedMethod.accessFlags.isConstructor()) {
+ markDirectStaticOrConstructorMethodAsLive(
+ clazz, encodedMethod, KeepReason.reflectiveUseIn(method));
+ } else {
+ markVirtualMethodAsLive(clazz, encodedMethod, KeepReason.reflectiveUseIn(method));
}
}
}
@@ -2390,14 +2559,16 @@
return;
}
- DexClass clazz = appView.definitionFor(instantiatedType);
- if (clazz != null && clazz.isProgramClass()) {
- DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
- if (defaultInitializer != null) {
- KeepReason reason = KeepReason.reflectiveUseIn(method);
- markClassAsInstantiatedWithReason(clazz, reason);
- markDirectStaticOrConstructorMethodAsLive(defaultInitializer, reason);
- }
+ DexProgramClass clazz = getProgramClassOrNull(instantiatedType);
+ if (clazz == null) {
+ return;
+ }
+ DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+ if (defaultInitializer != null) {
+ KeepReason reason = KeepReason.reflectiveUseIn(method);
+ markClassAsInstantiatedWithReason(clazz, reason);
+ markMethodAsTargeted(clazz, defaultInitializer, reason);
+ markDirectStaticOrConstructorMethodAsLive(clazz, defaultInitializer, reason);
}
}
@@ -2431,73 +2602,74 @@
return;
}
- DexClass clazz = appView.definitionFor(instantiatedType);
- if (clazz != null && clazz.isProgramClass()) {
- Value parametersValue = constructorDefinition.inValues().get(1);
- if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
- // Give up, we can't tell which constructor is being invoked.
- return;
- }
+ DexProgramClass clazz = getProgramClassOrNull(instantiatedType);
+ if (clazz == null) {
+ return;
+ }
+ Value parametersValue = constructorDefinition.inValues().get(1);
+ if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
+ // Give up, we can't tell which constructor is being invoked.
+ return;
+ }
- Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
- if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
- // Give up, we can't tell which constructor is being invoked.
- return;
- }
+ Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
+ if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
+ // Give up, we can't tell which constructor is being invoked.
+ return;
+ }
- DexEncodedMethod initializer = null;
+ DexEncodedMethod initializer = null;
- int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
- if (parametersSize == 0) {
- initializer = clazz.getDefaultInitializer();
- } else {
- DexType[] parameterTypes = new DexType[parametersSize];
- int missingIndices = parametersSize;
- for (Instruction user : parametersValue.uniqueUsers()) {
- if (user.isArrayPut()) {
- ArrayPut arrayPutInstruction = user.asArrayPut();
- if (arrayPutInstruction.array() != parametersValue) {
- return;
- }
-
- Value indexValue = arrayPutInstruction.index();
- if (indexValue.isPhi() || !indexValue.definition.isConstNumber()) {
- return;
- }
- int index = indexValue.definition.asConstNumber().getIntValue();
- if (index >= parametersSize) {
- return;
- }
-
- DexType type =
- ConstantValueUtils.getDexTypeRepresentedByValue(
- arrayPutInstruction.value(), appView);
- if (type == null) {
- return;
- }
-
- if (parameterTypes[index] == type) {
- continue;
- }
- if (parameterTypes[index] != null) {
- return;
- }
- parameterTypes[index] = type;
- missingIndices--;
+ int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
+ if (parametersSize == 0) {
+ initializer = clazz.getDefaultInitializer();
+ } else {
+ DexType[] parameterTypes = new DexType[parametersSize];
+ int missingIndices = parametersSize;
+ for (Instruction user : parametersValue.uniqueUsers()) {
+ if (user.isArrayPut()) {
+ ArrayPut arrayPutInstruction = user.asArrayPut();
+ if (arrayPutInstruction.array() != parametersValue) {
+ return;
}
- }
- if (missingIndices == 0) {
- initializer = clazz.getInitializer(parameterTypes);
+ Value indexValue = arrayPutInstruction.index();
+ if (indexValue.isPhi() || !indexValue.definition.isConstNumber()) {
+ return;
+ }
+ int index = indexValue.definition.asConstNumber().getIntValue();
+ if (index >= parametersSize) {
+ return;
+ }
+
+ DexType type =
+ ConstantValueUtils.getDexTypeRepresentedByValue(arrayPutInstruction.value(), appView);
+ if (type == null) {
+ return;
+ }
+
+ if (parameterTypes[index] == type) {
+ continue;
+ }
+ if (parameterTypes[index] != null) {
+ return;
+ }
+ parameterTypes[index] = type;
+ missingIndices--;
}
}
- if (initializer != null) {
- KeepReason reason = KeepReason.reflectiveUseIn(method);
- markClassAsInstantiatedWithReason(clazz, reason);
- markDirectStaticOrConstructorMethodAsLive(initializer, reason);
+ if (missingIndices == 0) {
+ initializer = clazz.getInitializer(parameterTypes);
}
}
+
+ if (initializer != null) {
+ KeepReason reason = KeepReason.reflectiveUseIn(method);
+ markClassAsInstantiatedWithReason(clazz, reason);
+ markMethodAsTargeted(clazz, initializer, reason);
+ markDirectStaticOrConstructorMethodAsLive(clazz, initializer, reason);
+ }
}
/**
@@ -2528,8 +2700,8 @@
continue;
}
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null && clazz.isProgramClass() && clazz.isInterface()) {
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz != null && clazz.isInterface()) {
// Add this interface to the set of pinned items to ensure that we do not merge the
// interface into its subtype and to ensure that the devirtualizer does not perform illegal
// rewritings of invoke-interface instructions into invoke-virtual instructions.
@@ -2596,71 +2768,13 @@
continue;
}
- DexClass serviceImplementationClass = appView.definitionFor(serviceImplementationType);
+ DexProgramClass serviceImplementationClass = getProgramClassOrNull(serviceImplementationType);
if (serviceImplementationClass != null && serviceImplementationClass.isProgramClass()) {
markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
}
}
}
- static class Action {
-
- final Kind kind;
- final DexItem target;
- final DexItem context;
- final KeepReason reason;
-
- private Action(Kind kind, DexItem target, DexItem context, KeepReason reason) {
- this.kind = kind;
- this.target = target;
- this.context = context;
- this.reason = reason;
- }
-
- public static Action markReachableVirtual(DexMethod method, KeepReason reason) {
- return new Action(Kind.MARK_REACHABLE_VIRTUAL, method, null, reason);
- }
-
- public static Action markReachableInterface(DexMethod method, KeepReason reason) {
- return new Action(Kind.MARK_REACHABLE_INTERFACE, method, null, reason);
- }
-
- public static Action markReachableSuper(DexMethod method, DexEncodedMethod from) {
- return new Action(Kind.MARK_REACHABLE_SUPER, method, from, null);
- }
-
- public static Action markReachableField(DexField field, KeepReason reason) {
- return new Action(Kind.MARK_REACHABLE_FIELD, field, null, reason);
- }
-
- public static Action markInstantiated(DexClass clazz, KeepReason reason) {
- return new Action(Kind.MARK_INSTANTIATED, clazz, null, reason);
- }
-
- public static Action markMethodLive(DexEncodedMethod method, KeepReason reason) {
- return new Action(Kind.MARK_METHOD_LIVE, method, null, reason);
- }
-
- public static Action markMethodKept(DexEncodedMethod method, KeepReason reason) {
- return new Action(Kind.MARK_METHOD_KEPT, method, null, reason);
- }
-
- public static Action markFieldKept(DexEncodedField field, KeepReason reason) {
- return new Action(Kind.MARK_FIELD_KEPT, field, null, reason);
- }
-
- private enum Kind {
- MARK_REACHABLE_VIRTUAL,
- MARK_REACHABLE_INTERFACE,
- MARK_REACHABLE_SUPER,
- MARK_REACHABLE_FIELD,
- MARK_INSTANTIATED,
- MARK_METHOD_LIVE,
- MARK_METHOD_KEPT,
- MARK_FIELD_KEPT
- }
- }
-
private static class SetWithReason<T> {
private final Set<T> items = Sets.newIdentityHashSet();
@@ -2770,7 +2884,7 @@
if (!registerFieldRead(field, DexEncodedMethod.ANNOTATION_REFERENCE)) {
return false;
}
- markStaticFieldAsLive(field, KeepReason.referencedInAnnotation(annotationHolder));
+ markStaticFieldAsLive(target, KeepReason.referencedInAnnotation(annotationHolder));
// When an annotation has a field of an enum type with a default value then Java VM
// will use the values() method on that enum class.
if (options.isGeneratingClassFiles()
@@ -2785,7 +2899,7 @@
target = holder.lookupInstanceField(field);
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target != null && target.field != field) {
- markInstanceFieldAsReachable(field, KeepReason.referencedInAnnotation(annotationHolder));
+ markInstanceFieldAsReachable(target, KeepReason.referencedInAnnotation(annotationHolder));
}
}
return false;
@@ -2793,7 +2907,7 @@
@Override
public boolean addMethod(DexMethod method) {
- DexClass holder = appView.definitionFor(method.holder);
+ DexProgramClass holder = getProgramClassOrNull(method.holder);
if (holder == null) {
return false;
}
@@ -2802,13 +2916,13 @@
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target.method == method) {
markDirectStaticOrConstructorMethodAsLive(
- target, KeepReason.referencedInAnnotation(annotationHolder));
+ holder, target, KeepReason.referencedInAnnotation(annotationHolder));
}
} else {
target = holder.lookupVirtualMethod(method);
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target != null && target.method == method) {
- markMethodAsTargeted(target, KeepReason.referencedInAnnotation(annotationHolder));
+ markMethodAsTargeted(holder, target, KeepReason.referencedInAnnotation(annotationHolder));
}
}
return false;
@@ -2881,6 +2995,13 @@
return registerEdge(getClassGraphNode(type), reason);
}
+ private KeepReasonWitness registerClass(DexProgramClass clazz, KeepReason reason) {
+ if (skipReporting(reason)) {
+ return KeepReasonWitness.INSTANCE;
+ }
+ return registerEdge(getClassGraphNode(clazz.type), reason);
+ }
+
private KeepReasonWitness registerAnnotation(DexAnnotation annotation, KeepReason reason) {
if (skipReporting(reason)) {
return KeepReasonWitness.INSTANCE;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 7493979..86058bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -4,34 +4,133 @@
package com.android.tools.r8.shaking;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.shaking.Enqueuer.Action;
-import com.google.common.collect.Queues;
-import java.util.Collection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.ArrayDeque;
import java.util.Queue;
+import java.util.function.BiPredicate;
public class EnqueuerWorklist {
- /** A queue of items that need processing. Different items trigger different actions. */
- private final Queue<Action> worklist = Queues.newArrayDeque();
+ public static class Action {
- public void add(Action action) {
- worklist.add(action);
+ public enum Kind {
+ MARK_REACHABLE_DIRECT,
+ MARK_REACHABLE_VIRTUAL,
+ MARK_REACHABLE_INTERFACE,
+ MARK_REACHABLE_SUPER,
+ MARK_REACHABLE_FIELD,
+ MARK_INSTANTIATED,
+ MARK_METHOD_LIVE,
+ MARK_METHOD_KEPT,
+ MARK_FIELD_KEPT
+ }
+
+ final Kind kind;
+ final DexItem target;
+ final DexItem context;
+ final KeepReason reason;
+
+ private Action(Kind kind, DexItem target, DexItem context, KeepReason reason) {
+ this.kind = kind;
+ this.target = target;
+ this.context = context;
+ this.reason = reason;
+ }
}
- public void addAll(Collection<Action> action) {
- worklist.addAll(action);
+ private final AppView<?> appView;
+ private final Queue<Action> queue = new ArrayDeque<>();
+
+ private final boolean restrictToProguardCompatibilityRules;
+
+ private EnqueuerWorklist(AppView<?> appView, boolean restrictToProguardCompatibilityRules) {
+ this.appView = appView;
+ this.restrictToProguardCompatibilityRules = restrictToProguardCompatibilityRules;
+ }
+
+ public static EnqueuerWorklist createWorklist(AppView<?> appView) {
+ return new EnqueuerWorklist(appView, false);
+ }
+
+ public static EnqueuerWorklist createProguardCompatibilityWorklist(AppView<?> appView) {
+ return new EnqueuerWorklist(appView, true);
}
public boolean isEmpty() {
- return worklist.isEmpty();
+ return queue.isEmpty();
}
public Action poll() {
- return worklist.poll();
+ return queue.poll();
}
- public void enqueueMarkReachableFieldAction(DexField field, KeepReason reason) {
- add(Action.markReachableField(field, reason));
+ public void transferTo(
+ EnqueuerWorklist worklist, BiPredicate<DexEncodedMethod, KeepReason> filter) {
+ while (!queue.isEmpty()) {
+ Action action = queue.poll();
+ if (action.kind == Action.Kind.MARK_METHOD_LIVE) {
+ DexEncodedMethod method = (DexEncodedMethod) action.target;
+ if (!filter.test(method, action.reason)) {
+ continue;
+ }
+ }
+ worklist.queue.add(action);
+ }
+ }
+
+ void enqueueMarkReachableDirectAction(DexMethod method, KeepReason reason) {
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ queue.add(new Action(Action.Kind.MARK_REACHABLE_DIRECT, method, null, reason));
+ }
+
+ void enqueueMarkReachableVirtualAction(DexMethod method, KeepReason reason) {
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ queue.add(new Action(Action.Kind.MARK_REACHABLE_VIRTUAL, method, null, reason));
+ }
+
+ void enqueueMarkReachableInterfaceAction(DexMethod method, KeepReason reason) {
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ queue.add(new Action(Action.Kind.MARK_REACHABLE_INTERFACE, method, null, reason));
+ }
+
+ void enqueueMarkReachableSuperAction(DexMethod method, DexEncodedMethod from) {
+ queue.add(new Action(Action.Kind.MARK_REACHABLE_SUPER, method, from, null));
+ }
+
+ public void enqueueMarkReachableFieldAction(
+ DexProgramClass clazz, DexEncodedField field, KeepReason reason) {
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ assert field.field.holder == clazz.type;
+ queue.add(new Action(Action.Kind.MARK_REACHABLE_FIELD, field, null, reason));
+ }
+
+ void enqueueMarkInstantiatedAction(DexProgramClass clazz, KeepReason reason) {
+ assert !clazz.isInterface() || clazz.accessFlags.isAnnotation();
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ queue.add(new Action(Action.Kind.MARK_INSTANTIATED, clazz, null, reason));
+ }
+
+ void enqueueMarkMethodLiveAction(
+ DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ assert method.method.holder == clazz.type;
+ queue.add(new Action(Action.Kind.MARK_METHOD_LIVE, method, null, reason));
+ }
+
+ void enqueueMarkMethodKeptAction(DexEncodedMethod method, KeepReason reason) {
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ assert method.isProgramMethod(appView);
+ queue.add(new Action(Action.Kind.MARK_METHOD_KEPT, method, null, reason));
+ }
+
+ void enqueueMarkFieldKeptAction(DexEncodedField field, KeepReason reason) {
+ assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
+ assert field.isProgramField(appView);
+ queue.add(new Action(Action.Kind.MARK_FIELD_KEPT, field, null, reason));
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 9d61070..9c6889e 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -39,7 +39,7 @@
private final Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules;
private final Set<DexEncodedField> liveFields;
private final Set<DexEncodedMethod> liveMethods;
- private final Set<DexType> liveTypes;
+ private final Set<DexProgramClass> liveTypes;
private final Mode mode;
private final RootSetBuilder rootSetBuilder;
private final Set<DexEncodedMethod> targetedMethods;
@@ -50,7 +50,7 @@
Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
Set<DexEncodedField> liveFields,
Set<DexEncodedMethod> liveMethods,
- Set<DexType> liveTypes,
+ Set<DexProgramClass> liveTypes,
Mode mode,
RootSetBuilder rootSetBuilder,
Set<DexEncodedMethod> targetedMethods) {
@@ -188,7 +188,7 @@
// A type is effectively live if (1) it is truly live, (2) the value of one of its fields has
// been inlined by the member value propagation, or (3) the return value of one of its methods
// has been forwarded by the member value propagation.
- if (liveTypes.contains(clazz.type)) {
+ if (liveTypes.contains(clazz)) {
return true;
}
for (DexEncodedField field : clazz.fields()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 38df55b..9f0fb1c 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import java.util.Collection;
@@ -67,8 +68,8 @@
return new InvokedFromLambdaCreatedIn(method);
}
- public static KeepReason isLibraryMethod(DexType implementer, DexType libraryType) {
- return new IsLibraryMethod(implementer, libraryType);
+ public static KeepReason isLibraryMethod(DexProgramClass implementer, DexType libraryType) {
+ return new IsLibraryMethod(implementer.type, libraryType);
}
public static KeepReason fieldReferencedIn(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index ee183dd..15be9af 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -12,10 +12,10 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Calculate the list of classes required in the main dex to allow legacy multidex loading.
@@ -36,11 +36,11 @@
* @param roots Classes which code may be executed before secondary dex files loading.
* @param application the dex appplication.
*/
- public MainDexListBuilder(Set<DexType> roots, DexApplication application) {
+ public MainDexListBuilder(Set<DexProgramClass> roots, DexApplication application) {
this.dexApplication = application;
this.appInfo = new AppInfoWithSubtyping(dexApplication);
// Only consider program classes for the root set.
- this.roots = roots.stream().filter(this::isProgramClass).collect(Collectors.toSet());
+ this.roots = SetUtils.mapIdentityHashSet(roots, DexProgramClass::getType);
mainDexClassesBuilder = MainDexClasses.builder(appInfo).addRoots(this.roots);
DexClass enumType = appInfo.definitionFor(appInfo.dexItemFactory().enumType);
if (enumType == null) {
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 5bf4b81..b7e70add 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -1257,7 +1257,7 @@
for (DexReference reference : noShrinking.keySet()) {
if (reference.isDexType()) {
DexType type = reference.asDexType();
- assert appInfo.liveTypes.contains(type)
+ assert appInfo.isLiveProgramType(type)
: "Expected kept type `" + type.toSourceString() + "` to be live";
}
}
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 d79ab64..b952325 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -72,14 +72,7 @@
pruneMembersAndAttributes(clazz);
continue;
}
- if (!appInfo.liveTypes.contains(clazz.type)) {
- // The class is completely unused and we can remove it.
- if (Log.ENABLED) {
- Log.debug(getClass(), "Removing class: " + clazz);
- }
- prunedTypes.add(clazz.type);
- usagePrinter.printUnusedClass(clazz);
- } else {
+ if (appInfo.isLiveProgramClass(clazz)) {
newClasses.add(clazz);
if (!appInfo.instantiatedTypes.contains(clazz.type)
&& !options.forceProguardCompatibility) {
@@ -98,6 +91,13 @@
// The class is used and must be kept. Remove the unused fields and methods from the class.
pruneUnusedInterfaces(clazz);
pruneMembersAndAttributes(clazz);
+ } else {
+ // The class is completely unused and we can remove it.
+ if (Log.ENABLED) {
+ Log.debug(getClass(), "Removing class: " + clazz);
+ }
+ prunedTypes.add(clazz.type);
+ usagePrinter.printUnusedClass(clazz);
}
}
return newClasses;
@@ -106,7 +106,8 @@
private void pruneUnusedInterfaces(DexProgramClass clazz) {
int numberOfReachableInterfaces = 0;
for (DexType type : clazz.interfaces.values) {
- if (appView.appInfo().liveTypes.contains(type)) {
+ // TODO(christofferqa): Extend unused interface removal to library classes.
+ if (isTypeLive(type)) {
numberOfReachableInterfaces++;
}
}
@@ -123,7 +124,7 @@
DexType[] reachableInterfaces = new DexType[numberOfReachableInterfaces];
int i = 0;
for (DexType type : clazz.interfaces.values) {
- if (appView.appInfo().liveTypes.contains(type)) {
+ if (isTypeLive(type)) {
reachableInterfaces[i] = type;
i++;
}
@@ -171,7 +172,7 @@
}
private boolean isTypeLive(DexType type) {
- return appView.appInfo().liveTypes.contains(type);
+ return appView.appInfo().isNonProgramTypeOrLiveProgramType(type);
}
private void clearDeadNestMembers(DexClass nestHost) {
@@ -208,20 +209,18 @@
private boolean isAttributeReferencingPrunedItem(EnclosingMethodAttribute attr) {
AppInfoWithLiveness appInfo = appView.appInfo();
- return
- (attr.getEnclosingClass() != null
- && !appInfo.liveTypes.contains(attr.getEnclosingClass()))
+ return (attr.getEnclosingClass() != null && !isTypeLive(attr.getEnclosingClass()))
|| (attr.getEnclosingMethod() != null
&& !appInfo.liveMethods.contains(attr.getEnclosingMethod()));
}
private boolean isAttributeReferencingPrunedType(InnerClassAttribute attr) {
AppInfoWithLiveness appInfo = appView.appInfo();
- if (!appInfo.liveTypes.contains(attr.getInner())) {
+ if (!isTypeLive(attr.getInner())) {
return true;
}
DexType context = attr.getLiveContext(appInfo);
- return context == null || !appInfo.liveTypes.contains(context);
+ return context == null || !isTypeLive(context);
}
private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 190b744..c961003 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -8,6 +8,7 @@
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;
+import java.util.function.Function;
public class SetUtils {
@@ -32,4 +33,12 @@
result.add(element);
return result;
}
+
+ public static <T, S> Set<T> mapIdentityHashSet(Set<S> set, Function<S, T> fn) {
+ Set<T> out = newIdentityHashSet(set.size());
+ for (S element : set) {
+ out.add(fn.apply(element));
+ }
+ return out;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index fefa10c..b31acbc 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1173,7 +1173,10 @@
"958-methodhandle-stackframe"
);
- private static Map<String, List<String>> keepRules = ImmutableMap.of();
+ private static Map<String, List<String>> keepRules =
+ ImmutableMap.of(
+ "021-string2", ImmutableList.of("-dontwarn junit.framework.**"),
+ "082-inline-execute", ImmutableList.of("-dontwarn junit.framework.**"));
private static List<String> failuresToTriage = ImmutableList.of(
// Dex file input into a jar file, not yet supported by the test framework.
@@ -1705,8 +1708,8 @@
.setMode(mode)
.setDisableTreeShaking(true)
.setDisableMinification(true)
- .addProguardConfiguration(
- ImmutableList.of("-keepattributes *"), Origin.unknown())
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
+ .addProguardConfiguration(compilationOptions.keepRules, Origin.unknown())
.setProgramConsumer(
new ClassFileConsumer() {