blob: 022be109032d1df1870d5fc44b5c4bc6bbaee1b8 [file] [log] [blame]
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult.isOverriding;
import static com.android.tools.r8.utils.collections.ThrowingSet.isThrowingSet;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Definition;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMember;
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 com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.DispatchTargetLookupResult;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
import com.android.tools.r8.graph.LookupMethodTarget;
import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
import com.android.tools.r8.graph.LookupTarget;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.SingleDispatchTargetLookupResult;
import com.android.tools.r8.graph.UnknownDispatchTargetLookupResult;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
import com.android.tools.r8.naming.SeedMapper;
import com.android.tools.r8.repackaging.RepackagingUtils;
import com.android.tools.r8.shaking.KeepInfo.Joiner;
import com.android.tools.r8.synthesis.CommittedItems;
import com.android.tools.r8.threading.TaskCollection;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.Visibility;
import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.ThrowingSet;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/** Encapsulates liveness and reachability information for an application. */
public class AppInfoWithLiveness extends AppInfoWithClassHierarchy
implements InstantiatedSubTypeInfo {
/** Set of reachable proto types that will be dead code eliminated. */
private final Set<DexType> deadProtoTypes;
/**
* Set of types that are mentioned in the program. We at least need an empty abstract classitem
* for these.
*/
private final Set<DexType> liveTypes;
/**
* 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 such a method is not live (i.e. not
* contained in {@link #liveMethods}, it may be marked as abstract and its implementation may be
* removed.
*/
private Set<DexMethod> targetedMethods;
/** Classes that lead to resolution errors such as non-existing or invalid targets. */
private final Set<DexType> failedClassResolutionTargets;
/** Method targets that lead to resolution errors such as non-existing or invalid targets. */
private final Set<DexMethod> failedMethodResolutionTargets;
/** Field targets that lead to resolution errors, such as non-existing or invalid targets. */
private final Set<DexField> failedFieldResolutionTargets;
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
*/
private final Set<DexMethod> bootstrapMethods;
/** Set of virtual methods that are the immediate target of an invoke-direct. */
private final Set<DexMethod> virtualMethodsTargetedByInvokeDirect;
/**
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
*/
private Set<DexMethod> liveMethods;
/**
* Information about all fields that are accessed by the program. The information includes whether
* a given field is read/written by the program, and it also includes all indirect accesses to
* each field. The latter is used, for example, during member rebinding.
*/
private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
/** Information about instantiated classes and their allocation sites. */
private final ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection;
/**
* Set of live call sites in the code. Note that if desugaring has taken place call site objects
* will have been removed from the code.
*/
public final Map<DexCallSite, ProgramMethodSet> callSites;
/** Collection of keep requirements for the program. */
private final KeepInfoCollection keepInfo;
/** All items with assumemayhavesideeffects rule. */
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
/** All methods that should be inlined if possible due to a configuration directive. */
private final Set<DexMethod> alwaysInline;
/** Items for which to print inlining decisions for (testing only). */
private final Set<DexMethod> whyAreYouNotInlining;
/** All methods that must be reprocessed (testing only). */
private final Set<DexMethod> reprocess;
/** All types that should be inlined if possible due to a configuration directive. */
public final PredicateSet<DexType> alwaysClassInline;
/**
* Set of lock candidates (i.e., types whose class reference may flow to a monitor instruction).
*/
private final Set<DexType> lockCandidates;
/**
* A map from seen init-class references to the minimum required visibility of the corresponding
* static field.
*/
public final Map<DexType, Visibility> initClassReferences;
/**
* Set of all methods including a RecordFieldValues instruction. Set only in final tree shaking.
*/
public final Set<DexMethod> recordFieldValuesReferences;
/**
* All items with -identifiernamestring rule. Bound boolean value indicates the rule is explicitly
* specified by users (<code>true</code>) or not, i.e., implicitly added by R8 (<code>false</code>
* ).
*/
public final Object2BooleanMap<DexMember<?, ?>> identifierNameStrings;
/** A set of types that have been removed by the {@link TreePruner}. */
final Set<DexType> prunedTypes;
/** A map from switchmap class types to their corresponding switchmaps. */
final Map<DexField, Int2ReferenceMap<DexField>> switchMaps;
/* A cache to improve the lookup performance of lookupSingleVirtualTarget */
private final SingleTargetLookupCache singleTargetLookupCache = new SingleTargetLookupCache();
// TODO(zerny): Clean up the constructors so we have just one.
AppInfoWithLiveness(
CommittedItems committedItems,
ClassToFeatureSplitMap classToFeatureSplitMap,
MainDexInfo mainDexInfo,
MissingClasses missingClasses,
Set<DexType> deadProtoTypes,
Set<DexType> liveTypes,
Set<DexMethod> targetedMethods,
Set<DexType> failedClassResolutionTargets,
Set<DexMethod> failedMethodResolutionTargets,
Set<DexField> failedFieldResolutionTargets,
Set<DexMethod> bootstrapMethods,
Set<DexMethod> virtualMethodsTargetedByInvokeDirect,
Set<DexMethod> liveMethods,
FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection,
Map<DexCallSite, ProgramMethodSet> callSites,
KeepInfoCollection keepInfo,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Set<DexMethod> alwaysInline,
Set<DexMethod> whyAreYouNotInlining,
Set<DexMethod> reprocess,
PredicateSet<DexType> alwaysClassInline,
Object2BooleanMap<DexMember<?, ?>> identifierNameStrings,
Set<DexType> prunedTypes,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
Set<DexType> lockCandidates,
Map<DexType, Visibility> initClassReferences,
Set<DexMethod> recordFieldValuesReferences) {
super(committedItems, classToFeatureSplitMap, mainDexInfo, missingClasses);
this.deadProtoTypes = deadProtoTypes;
this.liveTypes = liveTypes;
this.targetedMethods = targetedMethods;
this.failedClassResolutionTargets = failedClassResolutionTargets;
this.failedMethodResolutionTargets = failedMethodResolutionTargets;
this.failedFieldResolutionTargets = failedFieldResolutionTargets;
this.bootstrapMethods = bootstrapMethods;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
this.liveMethods = liveMethods;
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
this.keepInfo = keepInfo;
this.mayHaveSideEffects = mayHaveSideEffects;
this.callSites = callSites;
this.alwaysInline = alwaysInline;
this.whyAreYouNotInlining = whyAreYouNotInlining;
this.reprocess = reprocess;
this.alwaysClassInline = alwaysClassInline;
this.identifierNameStrings = identifierNameStrings;
this.prunedTypes = prunedTypes;
this.switchMaps = switchMaps;
this.lockCandidates = lockCandidates;
this.initClassReferences = initClassReferences;
this.recordFieldValuesReferences = recordFieldValuesReferences;
assert verify();
}
private AppInfoWithLiveness(AppInfoWithLiveness previous, CommittedItems committedItems) {
this(
committedItems,
previous.getClassToFeatureSplitMap(),
previous.getMainDexInfo(),
previous.getMissingClasses(),
previous.deadProtoTypes,
CollectionUtils.addAll(previous.liveTypes, committedItems.getCommittedProgramTypes()),
previous.targetedMethods,
previous.failedClassResolutionTargets,
previous.failedMethodResolutionTargets,
previous.failedFieldResolutionTargets,
previous.bootstrapMethods,
previous.virtualMethodsTargetedByInvokeDirect,
previous.liveMethods,
previous.fieldAccessInfoCollection,
previous.objectAllocationInfoCollection,
previous.callSites,
previous.keepInfo,
previous.mayHaveSideEffects,
previous.alwaysInline,
previous.whyAreYouNotInlining,
previous.reprocess,
previous.alwaysClassInline,
previous.identifierNameStrings,
previous.prunedTypes,
previous.switchMaps,
previous.lockCandidates,
previous.initClassReferences,
previous.recordFieldValuesReferences);
}
private AppInfoWithLiveness(
AppInfoWithLiveness previous, PrunedItems prunedItems, TaskCollection<?> tasks)
throws ExecutionException {
this(
previous.getSyntheticItems().commitPrunedItems(prunedItems),
previous.getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
previous.getMainDexInfo().withoutPrunedItems(prunedItems),
previous.getMissingClasses(),
previous.deadProtoTypes,
pruneClasses(previous.liveTypes, prunedItems, tasks),
pruneMethods(previous.targetedMethods, prunedItems, tasks),
pruneClasses(previous.failedClassResolutionTargets, prunedItems, tasks),
pruneMethods(previous.failedMethodResolutionTargets, prunedItems, tasks),
pruneFields(previous.failedFieldResolutionTargets, prunedItems, tasks),
pruneMethods(previous.bootstrapMethods, prunedItems, tasks),
pruneMethods(previous.virtualMethodsTargetedByInvokeDirect, prunedItems, tasks),
pruneMethods(previous.liveMethods, prunedItems, tasks),
previous.fieldAccessInfoCollection.withoutPrunedItems(prunedItems),
previous.objectAllocationInfoCollection.withoutPrunedItems(prunedItems),
pruneCallSites(previous.callSites, prunedItems),
extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
previous.mayHaveSideEffects,
pruneMethods(previous.alwaysInline, prunedItems, tasks),
pruneMethods(previous.whyAreYouNotInlining, prunedItems, tasks),
pruneMethods(previous.reprocess, prunedItems, tasks),
previous.alwaysClassInline,
pruneMapFromMembers(previous.identifierNameStrings, prunedItems, tasks),
prunedItems.hasRemovedClasses()
? CollectionUtils.mergeSets(previous.prunedTypes, prunedItems.getRemovedClasses())
: previous.prunedTypes,
previous.switchMaps,
pruneClasses(previous.lockCandidates, prunedItems, tasks),
pruneMapFromClasses(previous.initClassReferences, prunedItems, tasks),
previous.recordFieldValuesReferences);
}
private static Map<DexCallSite, ProgramMethodSet> pruneCallSites(
Map<DexCallSite, ProgramMethodSet> callSites, PrunedItems prunedItems) {
callSites
.entrySet()
.removeIf(
entry -> {
ProgramMethodSet contexts = entry.getValue();
ProgramMethodSet prunedContexts = contexts.withoutPrunedItems(prunedItems);
if (prunedContexts.isEmpty()) {
return true;
}
entry.setValue(prunedContexts);
return false;
});
return callSites;
}
private static Set<DexType> pruneClasses(
Set<DexType> methods, PrunedItems prunedItems, TaskCollection<?> tasks)
throws ExecutionException {
return pruneItems(methods, prunedItems.getRemovedClasses(), tasks);
}
private static Set<DexField> pruneFields(
Set<DexField> fields, PrunedItems prunedItems, TaskCollection<?> tasks)
throws ExecutionException {
return pruneItems(fields, prunedItems.getRemovedFields(), tasks);
}
private static Set<DexMethod> pruneMethods(
Set<DexMethod> methods, PrunedItems prunedItems, TaskCollection<?> tasks)
throws ExecutionException {
return pruneItems(methods, prunedItems.getRemovedMethods(), tasks);
}
private static <T> Set<T> pruneItems(Set<T> items, Set<T> removedItems, TaskCollection<?> tasks)
throws ExecutionException {
if (!isThrowingSet(items) && !removedItems.isEmpty()) {
tasks.submit(
() -> {
if (items.size() <= removedItems.size()) {
items.removeAll(removedItems);
} else {
removedItems.forEach(items::remove);
}
});
}
return items;
}
private static <V> Map<DexType, V> pruneMapFromClasses(
Map<DexType, V> map, PrunedItems prunedItems, TaskCollection<?> tasks)
throws ExecutionException {
return pruneMap(map, prunedItems.getRemovedClasses(), tasks);
}
private static Object2BooleanMap<DexMember<?, ?>> pruneMapFromMembers(
Object2BooleanMap<DexMember<?, ?>> map, PrunedItems prunedItems, TaskCollection<?> tasks)
throws ExecutionException {
if (prunedItems.hasRemovedMembers()) {
tasks.submit(
() -> {
prunedItems.getRemovedFields().forEach(map::removeBoolean);
prunedItems.getRemovedMethods().forEach(map::removeBoolean);
});
}
return map;
}
private static <K, V> Map<K, V> pruneMap(
Map<K, V> map, Set<K> removedItems, TaskCollection<?> tasks) throws ExecutionException {
if (!removedItems.isEmpty()) {
tasks.submit(
() -> {
if (map.size() <= removedItems.size()) {
map.keySet().removeAll(removedItems);
} else {
removedItems.forEach(map::remove);
}
});
}
return map;
}
public void notifyMemberRebindingFinished(AppView<AppInfoWithLiveness> appView) {
getFieldAccessInfoCollection().restrictToProgram(appView);
}
@Override
public void notifyMinifierFinished() {
liveMethods = ThrowingSet.get();
}
public void notifyTreePrunerFinished(Enqueuer.Mode mode) {
if (mode.isInitialTreeShaking()) {
liveMethods = ThrowingSet.get();
}
targetedMethods = ThrowingSet.get();
}
private boolean verify() {
assert keepInfo.verifyPinnedTypesAreLive(liveTypes, options());
assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
liveTypes, getMissingClasses(), this);
return true;
}
@Override
public AppInfoWithLiveness rebuildWithMainDexInfo(MainDexInfo mainDexInfo) {
return new AppInfoWithLiveness(
getSyntheticItems().commit(app()),
getClassToFeatureSplitMap(),
mainDexInfo,
getMissingClasses(),
deadProtoTypes,
liveTypes,
targetedMethods,
failedClassResolutionTargets,
failedMethodResolutionTargets,
failedFieldResolutionTargets,
bootstrapMethods,
virtualMethodsTargetedByInvokeDirect,
liveMethods,
fieldAccessInfoCollection,
objectAllocationInfoCollection,
callSites,
keepInfo,
mayHaveSideEffects,
alwaysInline,
whyAreYouNotInlining,
reprocess,
alwaysClassInline,
identifierNameStrings,
prunedTypes,
switchMaps,
lockCandidates,
initClassReferences,
recordFieldValuesReferences);
}
private static KeepInfoCollection extendPinnedItems(
AppInfoWithLiveness previous, Collection<? extends DexReference> additionalPinnedItems) {
if (additionalPinnedItems == null || additionalPinnedItems.isEmpty()) {
return previous.keepInfo;
}
return previous.keepInfo.mutate(
collection -> {
for (DexReference reference : additionalPinnedItems) {
if (reference.isDexType()) {
DexProgramClass clazz =
asProgramClassOrNull(previous.definitionFor(reference.asDexType()));
if (clazz != null) {
collection.joinClass(clazz, Joiner::disallowShrinking);
}
} else if (reference.isDexMethod()) {
DexMethod method = reference.asDexMethod();
DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(method.holder));
if (clazz != null) {
ProgramMethod definition = clazz.lookupProgramMethod(method);
if (definition != null) {
collection.joinMethod(definition, Joiner::disallowShrinking);
}
}
} else {
DexField field = reference.asDexField();
DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(field.holder));
if (clazz != null) {
ProgramField definition = clazz.lookupProgramField(field);
if (definition != null) {
collection.joinField(definition, Joiner::disallowShrinking);
}
}
}
}
});
}
public AppInfoWithLiveness(
AppInfoWithLiveness previous, Map<DexField, Int2ReferenceMap<DexField>> switchMaps) {
super(
previous.getSyntheticItems().commit(previous.app()),
previous.getClassToFeatureSplitMap(),
previous.getMainDexInfo(),
previous.getMissingClasses());
this.deadProtoTypes = previous.deadProtoTypes;
this.liveTypes = previous.liveTypes;
this.targetedMethods = previous.targetedMethods;
this.failedClassResolutionTargets = previous.failedClassResolutionTargets;
this.failedMethodResolutionTargets = previous.failedMethodResolutionTargets;
this.failedFieldResolutionTargets = previous.failedFieldResolutionTargets;
this.bootstrapMethods = previous.bootstrapMethods;
this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
this.liveMethods = previous.liveMethods;
this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
this.keepInfo = previous.keepInfo;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
this.callSites = previous.callSites;
this.alwaysInline = previous.alwaysInline;
this.whyAreYouNotInlining = previous.whyAreYouNotInlining;
this.reprocess = previous.reprocess;
this.alwaysClassInline = previous.alwaysClassInline;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = previous.prunedTypes;
this.switchMaps = switchMaps;
this.lockCandidates = previous.lockCandidates;
this.initClassReferences = previous.initClassReferences;
this.recordFieldValuesReferences = previous.recordFieldValuesReferences;
previous.markObsolete();
assert verify();
}
public static AppInfoWithLivenessModifier modifier() {
return new AppInfoWithLivenessModifier();
}
@Override
public DexClass definitionFor(DexType type) {
DexClass definition = super.definitionFor(type);
assert definition != null
|| deadProtoTypes.contains(type)
|| getMissingClasses().contains(type)
// TODO(b/150736225): Not sure how to remove these.
|| DesugaredLibraryAPIConverter.isVivifiedType(type)
: "Failed lookup of non-missing type: " + type;
return definition;
}
private CfVersion largestInputCfVersion = null;
public boolean canUseConstClassInstructions(InternalOptions options) {
if (!options.isGeneratingClassFiles()) {
return true;
}
if (largestInputCfVersion == null) {
computeLargestCfVersion();
}
return options.canUseConstClassInstructions(largestInputCfVersion);
}
private synchronized void computeLargestCfVersion() {
if (largestInputCfVersion != null) {
return;
}
for (DexProgramClass clazz : classes()) {
// Skip synthetic classes which may not have a specified version.
if (clazz.hasClassFileVersion()) {
largestInputCfVersion =
Ordered.maxIgnoreNull(largestInputCfVersion, clazz.getInitialClassFileVersion());
}
}
assert largestInputCfVersion != null;
}
public boolean isLiveProgramClass(DexProgramClass clazz) {
return liveTypes.contains(clazz.type);
}
public boolean isLiveProgramType(DexType type) {
DexClass clazz = definitionFor(type);
return clazz != null && clazz.isProgramClass() && isLiveProgramClass(clazz.asProgramClass());
}
public boolean isNonProgramTypeOrLiveProgramType(DexType type) {
if (liveTypes.contains(type)) {
return true;
}
if (prunedTypes.contains(type)) {
return false;
}
DexClass clazz = definitionFor(type);
return clazz == null || !clazz.isProgramClass();
}
public boolean isLiveMethod(DexMethod method) {
return liveMethods.contains(method);
}
public boolean isTargetedMethod(DexMethod method) {
return targetedMethods.contains(method);
}
public boolean isFailedClassResolutionTarget(DexType type) {
return failedClassResolutionTargets.contains(type);
}
public boolean isFailedMethodResolutionTarget(DexMethod method) {
return failedMethodResolutionTargets.contains(method);
}
public Set<DexMethod> getFailedMethodResolutionTargets() {
return failedMethodResolutionTargets;
}
public boolean isFailedFieldResolutionTarget(DexField field) {
return failedFieldResolutionTargets.contains(field);
}
public Set<DexField> getFailedFieldResolutionTargets() {
return failedFieldResolutionTargets;
}
public boolean isBootstrapMethod(DexMethod method) {
return bootstrapMethods.contains(method);
}
public boolean isBootstrapMethod(ProgramMethod method) {
return isBootstrapMethod(method.getReference());
}
public Set<DexMethod> getVirtualMethodsTargetedByInvokeDirect() {
return virtualMethodsTargetedByInvokeDirect;
}
public boolean isAlwaysInlineMethod(DexMethod method) {
return alwaysInline.contains(method);
}
public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
return whyAreYouNotInlining.contains(method);
}
public boolean hasNoWhyAreYouNotInliningMethods() {
return whyAreYouNotInlining.isEmpty();
}
public Set<DexMethod> getReprocessMethods() {
return reprocess;
}
/**
* Resolve the methods implemented by the lambda expression that created the {@code callSite}.
*
* <p>If {@code callSite} was not created as a result of a lambda expression (i.e. the metafactory
* is not {@code LambdaMetafactory}), the empty set is returned.
*
* <p>If the metafactory is neither {@code LambdaMetafactory} nor {@code StringConcatFactory}, a
* warning is issued.
*
* <p>The returned set of methods all have {@code callSite.methodName} as the method name.
*
* @param callSite Call site to resolve.
* @return Methods implemented by the lambda expression that created the {@code callSite}.
*/
@SuppressWarnings("ReferenceEquality")
public DexClassAndMethodSet lookupLambdaImplementedMethods(
DexCallSite callSite, AppView<AppInfoWithLiveness> appView) {
assert checkIfObsolete();
List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, appView);
if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
return DexClassAndMethodSet.empty();
}
DexClassAndMethodSet result = DexClassAndMethodSet.create();
Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
Set<DexType> visited = Sets.newIdentityHashSet();
while (!worklist.isEmpty()) {
DexType iface = worklist.removeFirst();
if (!visited.add(iface)) {
// Already visited previously. May happen due to "diamond shapes" in the interface
// hierarchy.
continue;
}
DexClass clazz = definitionFor(iface);
if (clazz == null) {
// Skip this interface. If the lambda only implements missing library interfaces and not any
// program interfaces, then minification and tree shaking are not interested in this
// DexCallSite anyway, so skipping this interface is harmless. On the other hand, if
// minification is run on a program with a lambda interface that implements both a missing
// library interface and a present program interface, then we might minify the method name
// on the program interface even though it should be kept the same as the (missing) library
// interface method. That is a shame, but minification is not suited for incomplete programs
// anyway.
continue;
}
assert clazz.isInterface();
for (DexClassAndMethod method : clazz.virtualClassMethods()) {
if (method.getName().isIdenticalTo(callSite.methodName)
&& method.getAccessFlags().isAbstract()) {
result.add(method);
}
}
Collections.addAll(worklist, clazz.interfaces.values);
}
return result;
}
/**
* Const-classes is a conservative set of types that may be lock-candidates and cannot be merged.
* When using synchronized blocks, we cannot ensure that const-class locks will not flow in. This
* can potentially cause incorrect behavior when merging classes. A conservative choice is to not
* merge any const-class classes. More info at b/142438687.
*/
public boolean isLockCandidate(DexProgramClass clazz) {
return lockCandidates.contains(clazz.getType());
}
public Set<DexType> getDeadProtoTypes() {
return deadProtoTypes;
}
public Int2ReferenceMap<DexField> getSwitchMap(DexField field) {
assert checkIfObsolete();
return switchMaps.get(field);
}
/** This method provides immutable access to `fieldAccessInfoCollection`. */
public FieldAccessInfoCollection<? extends FieldAccessInfo> getFieldAccessInfoCollection() {
return fieldAccessInfoCollection;
}
FieldAccessInfoCollectionImpl getMutableFieldAccessInfoCollection() {
return fieldAccessInfoCollection;
}
/** This method provides immutable access to `objectAllocationInfoCollection`. */
public ObjectAllocationInfoCollection getObjectAllocationInfoCollection() {
return objectAllocationInfoCollection;
}
void mutateObjectAllocationInfoCollection(
Consumer<ObjectAllocationInfoCollectionImpl.Builder> mutator) {
objectAllocationInfoCollection.mutate(mutator, this);
}
void removeFromSingleTargetLookupCache(DexClass clazz) {
singleTargetLookupCache.removeInstantiatedType(clazz.type, this);
}
private boolean isInstantiatedDirectly(DexProgramClass clazz) {
assert checkIfObsolete();
DexType type = clazz.type;
return (!clazz.isInterface() && objectAllocationInfoCollection.isInstantiatedDirectly(clazz))
// TODO(b/145344105): Model annotations in the object allocation info.
|| (clazz.isAnnotation() && liveTypes.contains(type));
}
public boolean isInstantiatedIndirectly(DexProgramClass clazz) {
assert checkIfObsolete();
return objectAllocationInfoCollection.hasInstantiatedStrictSubtype(clazz);
}
public boolean isInstantiatedDirectlyOrIndirectly(DexProgramClass clazz) {
assert checkIfObsolete();
return isInstantiatedDirectly(clazz) || isInstantiatedIndirectly(clazz);
}
public boolean isReachableOrReferencedField(DexEncodedField field) {
assert checkIfObsolete();
DexField reference = field.getReference();
FieldAccessInfo info = getFieldAccessInfoCollection().get(reference);
if (info != null) {
assert info.isRead() || info.isWritten();
return true;
}
assert getKeepInfo().getFieldInfo(field, this).isShrinkingAllowed(options());
return false;
}
public boolean isFieldRead(DexClassAndField field) {
assert checkIfObsolete();
FieldAccessInfo info = getFieldAccessInfoCollection().get(field.getReference());
if (info != null && info.isRead()) {
return true;
}
if (isPinned(field)) {
return true;
}
// For non-program classes we don't know whether a field is read.
return !field.isProgramField();
}
public boolean isFieldWritten(DexClassAndField field) {
assert checkIfObsolete();
return isFieldWrittenByFieldPutInstruction(field) || isPinned(field);
}
public boolean isFieldWrittenByFieldPutInstruction(DexClassAndField field) {
assert checkIfObsolete();
FieldAccessInfo info = getFieldAccessInfoCollection().get(field.getReference());
if (info != null && info.isWritten()) {
// The field is written directly by the program itself.
return true;
}
// For non-program classes we don't know whether a field is rewritten.
return !field.isProgramField();
}
public boolean isFieldOnlyWrittenInMethod(DexClassAndField field, DexEncodedMethod method) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
if (isPinned(field)) {
return false;
}
return isFieldOnlyWrittenInMethodIgnoringPinning(field, method);
}
public boolean isFieldOnlyWrittenInMethodIgnoringPinning(
DexClassAndField field, DexEncodedMethod method) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.getReference());
return fieldAccessInfo != null
&& fieldAccessInfo.isWritten()
&& !fieldAccessInfo.isWrittenOutside(method);
}
@SuppressWarnings("ReferenceEquality")
public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexClassAndField field) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
if (isPinned(field)) {
return false;
}
FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.getReference());
if (fieldAccessInfo == null || !fieldAccessInfo.isWritten()) {
return false;
}
DexType holder = field.getHolderType();
return fieldAccessInfo.isWrittenOnlyInMethodSatisfying(
method ->
method.getHolderType() == holder && method.getDefinition().isInstanceInitializer());
}
public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexClassAndField field) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
DexEncodedMethod staticInitializer =
definitionFor(field.getHolderType()).asProgramClass().getClassInitializer();
return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
}
public boolean isInstantiatedInterface(DexProgramClass clazz) {
assert checkIfObsolete();
return objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz);
}
@Override
public boolean hasLiveness() {
assert checkIfObsolete();
return true;
}
@Override
public AppInfoWithLiveness withLiveness() {
assert checkIfObsolete();
return this;
}
public boolean isRepackagingAllowed(DexProgramClass clazz, AppView<?> appView) {
if (!keepInfo.getInfo(clazz).isRepackagingAllowed(options())) {
return false;
}
if (RepackagingUtils.isPackageNameKept(clazz, appView.options())) {
return false;
}
SeedMapper applyMappingSeedMapper = appView.getApplyMappingSeedMapper();
return applyMappingSeedMapper == null || !applyMappingSeedMapper.hasMapping(clazz.type);
}
@Deprecated
public boolean isPinnedWithDefinitionLookup(DexReference reference) {
assert checkIfObsolete();
return keepInfo.isPinnedWithDefinitionLookup(reference, options(), this);
}
@Deprecated
public boolean isPinned(DexDefinition definition) {
return keepInfo.isPinned(definition, options(), this);
}
@Deprecated
public boolean isPinned(DexProgramClass clazz) {
return keepInfo.isPinned(clazz, options());
}
@Deprecated
public boolean isPinned(Definition definition) {
assert definition != null;
return definition.isProgramDefinition()
&& keepInfo.isPinned(definition.asProgramDefinition(), options());
}
public boolean hasPinnedInstanceInitializer(DexType type) {
assert type.isClassType();
DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
if (clazz != null) {
for (ProgramMethod method : clazz.directProgramMethods()) {
if (method.getDefinition().isInstanceInitializer() && isPinned(method)) {
return true;
}
}
}
return false;
}
public KeepInfoCollection getKeepInfo() {
return keepInfo;
}
/**
* Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the given
* DexApplication object.
*/
@Override
public AppInfoWithLiveness prunedCopyFrom(
PrunedItems prunedItems, ExecutorService executorService, Timing timing)
throws ExecutionException {
assert getClass() == AppInfoWithLiveness.class;
assert checkIfObsolete();
if (prunedItems.isEmpty()) {
assert app() == prunedItems.getPrunedApp();
return this;
}
timing.begin("Pruning AppInfoWithLiveness");
if (prunedItems.hasRemovedClasses()) {
// Rebuild the hierarchy.
objectAllocationInfoCollection.mutate(
mutator -> mutator.removeAllocationsForPrunedItems(prunedItems), this);
keepInfo.mutate(keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems));
} else if (prunedItems.hasRemovedMembers()) {
keepInfo.mutate(keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems));
}
TaskCollection<?> tasks = new TaskCollection<>(options(), executorService);
AppInfoWithLiveness appInfoWithLiveness = new AppInfoWithLiveness(this, prunedItems, tasks);
tasks.await();
timing.end();
return appInfoWithLiveness;
}
public AppInfoWithLiveness rebuildWithLiveness(DexApplication application) {
return rebuildWithLiveness(getSyntheticItems().commit(application));
}
public AppInfoWithLiveness rebuildWithLiveness(CommittedItems committedItems) {
return new AppInfoWithLiveness(this, committedItems);
}
public AppInfoWithLiveness rewrittenWithLens(
DirectMappedDexApplication application,
NonIdentityGraphLens lens,
GraphLens appliedLens,
Timing timing) {
assert checkIfObsolete();
// Switchmap classes should never be affected by renaming.
assert lens.assertFieldsNotModified(
switchMaps.keySet().stream()
.map(this::resolveField)
.filter(FieldResolutionResult::isSingleFieldResolutionResult)
.map(FieldResolutionResult::getResolvedField)
.collect(Collectors.toList()));
CommittedItems committedItems =
getSyntheticItems().commitRewrittenWithLens(application, lens, timing);
DexDefinitionSupplier definitionSupplier =
committedItems.getApplication().getDefinitionsSupplier(committedItems);
return new AppInfoWithLiveness(
committedItems,
getClassToFeatureSplitMap().rewrittenWithLens(lens, timing),
getMainDexInfo().rewrittenWithLens(getSyntheticItems(), lens, timing),
getMissingClasses(),
deadProtoTypes,
lens.rewriteReferences(liveTypes),
lens.rewriteReferences(targetedMethods),
lens.rewriteReferences(failedClassResolutionTargets),
lens.rewriteReferences(failedMethodResolutionTargets),
lens.rewriteFields(failedFieldResolutionTargets, timing),
lens.rewriteReferences(bootstrapMethods),
lens.rewriteReferences(virtualMethodsTargetedByInvokeDirect),
lens.rewriteReferences(liveMethods),
fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens, timing),
objectAllocationInfoCollection.rewrittenWithLens(
definitionSupplier, lens, appliedLens, timing),
lens.rewriteCallSites(callSites, definitionSupplier, timing),
keepInfo.rewrite(definitionSupplier, lens, application.options, timing),
// Take any rule in case of collisions.
lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)),
lens.rewriteReferences(alwaysInline),
lens.rewriteReferences(whyAreYouNotInlining),
lens.rewriteReferences(reprocess),
alwaysClassInline.rewriteItems(lens::lookupType),
lens.rewriteReferenceKeys(identifierNameStrings),
// Don't rewrite pruned types - the removed types are identified by their original name.
prunedTypes,
lens.rewriteFieldKeys(switchMaps),
lens.rewriteReferences(lockCandidates),
rewriteInitClassReferences(lens),
lens.rewriteReferences(recordFieldValuesReferences));
}
public Map<DexType, Visibility> rewriteInitClassReferences(GraphLens lens) {
return lens.rewriteTypeKeys(
initClassReferences,
(minimumRequiredVisibilityForCurrentMethod,
otherMinimumRequiredVisibilityForCurrentMethod) -> {
assert !minimumRequiredVisibilityForCurrentMethod.isPrivate();
assert !otherMinimumRequiredVisibilityForCurrentMethod.isPrivate();
if (minimumRequiredVisibilityForCurrentMethod.isPublic()
|| otherMinimumRequiredVisibilityForCurrentMethod.isPublic()) {
return Visibility.PUBLIC;
}
if (minimumRequiredVisibilityForCurrentMethod.isProtected()
|| otherMinimumRequiredVisibilityForCurrentMethod.isProtected()) {
return Visibility.PROTECTED;
}
return Visibility.PACKAGE_PRIVATE;
});
}
/**
* Returns true if the given type was part of the original program but has been removed during
* tree shaking.
*/
public boolean wasPruned(DexType type) {
assert checkIfObsolete();
return prunedTypes.contains(type);
}
public Set<DexType> getPrunedTypes() {
assert checkIfObsolete();
return prunedTypes;
}
public DexClassAndMethod lookupSingleTarget(
AppView<AppInfoWithLiveness> appView,
InvokeType type,
DexMethod target,
SingleResolutionResult<?> resolutionResult,
ProgramMethod context,
LibraryModeledPredicate modeledPredicate) {
assert checkIfObsolete();
if (!target.getHolderType().isClassType()) {
return null;
}
switch (type) {
case INTERFACE:
case VIRTUAL:
return lookupSingleVirtualTarget(
appView,
target,
resolutionResult,
context,
type.isInterface(),
modeledPredicate,
DynamicType.unknown());
case DIRECT:
return lookupDirectTarget(target, context, appView);
case STATIC:
return lookupStaticTarget(target, context, appView);
case SUPER:
return lookupSuperTarget(target, context, appView);
default:
return null;
}
}
/** For mapping invoke virtual instruction to single target method. */
public DexClassAndMethod lookupSingleVirtualTargetForTesting(
AppView<AppInfoWithLiveness> appView,
DexMethod method,
ProgramMethod context,
boolean isInterface,
LibraryModeledPredicate modeledPredicate,
DynamicType dynamicReceiverType) {
assert checkIfObsolete();
SingleResolutionResult<?> resolutionResult =
appView.appInfo().resolveMethodLegacy(method, isInterface).asSingleResolution();
if (resolutionResult != null) {
return lookupSingleVirtualTarget(
appView,
method,
resolutionResult,
context,
isInterface,
modeledPredicate,
dynamicReceiverType);
}
return null;
}
public DexClassAndMethod lookupSingleVirtualTarget(
AppView<AppInfoWithLiveness> appView,
DexMethod method,
SingleResolutionResult<?> resolutionResult,
ProgramMethod context,
boolean isInterface,
LibraryModeledPredicate modeledPredicate,
DynamicType dynamicReceiverType) {
assert checkIfObsolete();
assert dynamicReceiverType != null;
if (method.getHolderType().isArrayType()) {
return null;
}
TypeElement staticReceiverType = method.getHolderType().toTypeElement(appView);
if (!appView
.getOpenClosedInterfacesCollection()
.isDefinitelyInstanceOfStaticType(appView, () -> dynamicReceiverType, staticReceiverType)) {
return null;
}
DexClass initialResolutionHolder = resolutionResult.getInitialResolutionHolder();
if (initialResolutionHolder.isInterface() != isInterface) {
return null;
}
DexType refinedReceiverType =
TypeAnalysis.toRefinedReceiverType(dynamicReceiverType, method, appView);
DexClass refinedReceiverClass = definitionFor(refinedReceiverType);
if (refinedReceiverClass == null) {
// The refined receiver is not defined in the program and we cannot determine the target.
return null;
}
if (singleTargetLookupCache.hasPositiveCacheHit(refinedReceiverType, method)) {
return singleTargetLookupCache.getPositiveCacheHit(refinedReceiverType, method);
}
if (!dynamicReceiverType.hasDynamicLowerBoundType()
&& singleTargetLookupCache.hasNegativeCacheHit(refinedReceiverType, method)) {
return null;
}
if (resolutionResult
.isAccessibleForVirtualDispatchFrom(context.getHolder(), appView)
.isFalse()) {
return null;
}
// If the resolved method is final, return the resolution.
DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
if (resolvedMethod.getHolder().isFinal() || resolvedMethod.getAccessFlags().isFinal()) {
if (!resolvedMethod.isLibraryMethod()
|| modeledPredicate.isModeled(resolvedMethod.getHolderType())) {
return singleTargetLookupCache.addToCache(refinedReceiverType, method, resolvedMethod);
}
}
DispatchTargetLookupResult exactTarget =
getMethodTargetFromExactRuntimeInformation(
refinedReceiverType,
dynamicReceiverType.getDynamicLowerBoundType(),
resolutionResult,
refinedReceiverClass);
if (exactTarget != null) {
// We are not caching single targets here because the cache does not include the
// lower bound dimension.
return exactTarget.isSingleResult()
? exactTarget.asSingleResult().getSingleDispatchTarget()
: null;
}
if (refinedReceiverClass.isNotProgramClass()) {
// The refined receiver is not defined in the program and we cannot determine the target.
singleTargetLookupCache.addNoSingleTargetToCache(refinedReceiverType, method);
return null;
}
DexClass resolvedHolder = resolutionResult.getResolvedHolder();
// TODO(b/148769279): Disable lookup single target on lambda's for now.
if (resolvedHolder.isInterface()
&& resolvedHolder.isProgramClass()
&& objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(
resolvedHolder.asProgramClass())) {
singleTargetLookupCache.addNoSingleTargetToCache(refinedReceiverType, method);
return null;
}
DexClassAndMethod singleMethodTarget = null;
DexProgramClass refinedLowerBound = null;
if (dynamicReceiverType.hasDynamicLowerBoundType()) {
DexClass refinedLowerBoundClass =
definitionFor(dynamicReceiverType.getDynamicLowerBoundType().getClassType());
if (refinedLowerBoundClass != null) {
refinedLowerBound = refinedLowerBoundClass.asProgramClass();
// TODO(b/154822960): Check if the lower bound is a subtype of the upper bound.
if (refinedLowerBound != null && !isSubtype(refinedLowerBound.type, refinedReceiverType)) {
refinedLowerBound = null;
}
}
}
LookupResultSuccess lookupResult =
resolutionResult
.lookupVirtualDispatchTargets(
context.getHolder(),
appView,
refinedReceiverClass.asProgramClass(),
refinedLowerBound)
.asLookupResultSuccess();
if (lookupResult != null && !lookupResult.isIncomplete()) {
LookupTarget singleTarget = lookupResult.getSingleLookupTarget();
if (singleTarget != null && singleTarget.isMethodTarget()) {
singleMethodTarget = singleTarget.asMethodTarget().getTarget();
}
}
if (!dynamicReceiverType.hasDynamicLowerBoundType()) {
singleTargetLookupCache.addToCache(refinedReceiverType, method, singleMethodTarget);
}
return singleMethodTarget;
}
@SuppressWarnings("ReferenceEquality")
private DispatchTargetLookupResult getMethodTargetFromExactRuntimeInformation(
DexType refinedReceiverType,
ClassTypeElement receiverLowerBoundType,
SingleResolutionResult<?> resolution,
DexClass refinedReceiverClass) {
// If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
// runtime type information. In this case, the invoke will dispatch to the resolution result
// from the runtime type of the receiver.
if (receiverLowerBoundType != null
&& receiverLowerBoundType.getClassType() == refinedReceiverType) {
if (refinedReceiverClass.isProgramClass()) {
LookupMethodTarget methodTarget =
resolution.lookupVirtualDispatchTarget(refinedReceiverClass.asProgramClass(), this);
if (methodTarget == null
|| (methodTarget.getTarget().isProgramMethod()
&& !getKeepInfo()
.getMethodInfo(methodTarget.getTarget().asProgramMethod())
.isOptimizationAllowed(options()))) {
// TODO(b/150640456): We should maybe only consider program methods.
return new UnknownDispatchTargetLookupResult(resolution);
}
return new SingleDispatchTargetLookupResult(methodTarget.getTarget(), resolution);
} else {
// TODO(b/150640456): We should maybe only consider program methods.
// If we resolved to a method on the refined receiver in the library, then we report the
// method as a single target as well. This is a bit iffy since the library could change
// implementation, but we use this for library modelling.
DexClassAndMethod resolvedMethod = resolution.getResolutionPair();
DexClassAndMethod targetOnReceiver =
refinedReceiverClass.lookupVirtualClassMethod(resolvedMethod.getReference());
if (targetOnReceiver != null && isOverriding(resolvedMethod, targetOnReceiver)) {
return new SingleDispatchTargetLookupResult(targetOnReceiver, resolution);
}
return new UnknownDispatchTargetLookupResult(resolution);
}
}
return null;
}
public AppInfoWithLiveness withSwitchMaps(Map<DexField, Int2ReferenceMap<DexField>> switchMaps) {
assert checkIfObsolete();
assert this.switchMaps.isEmpty();
return new AppInfoWithLiveness(this, switchMaps);
}
/**
* Visit all class definitions of classpath classes that are referenced in the compilation unit.
*
* <p>TODO(b/139464956): Only traverse the classpath types referenced from the live program.
* Conservatively traces all classpath classes for now.
*/
public void forEachReferencedClasspathClass(Consumer<DexClasspathClass> fn) {
app().asDirect().classpathClasses().forEach(fn);
}
@Override
public void forEachInstantiatedSubType(
DexType type,
Consumer<DexProgramClass> subTypeConsumer,
Consumer<LambdaDescriptor> callSiteConsumer) {
objectAllocationInfoCollection.forEachInstantiatedSubType(
type, subTypeConsumer, callSiteConsumer, this);
}
public void forEachInstantiatedSubTypeInChain(
DexProgramClass refinedReceiverUpperBound,
DexProgramClass refinedReceiverLowerBound,
Consumer<DexProgramClass> subTypeConsumer,
Consumer<LambdaDescriptor> callSiteConsumer) {
List<DexProgramClass> subTypes =
computeProgramClassRelationChain(refinedReceiverLowerBound, refinedReceiverUpperBound);
for (DexProgramClass subType : subTypes) {
if (isInstantiatedOrPinned(subType)) {
subTypeConsumer.accept(subType);
}
}
}
private boolean isInstantiatedOrPinned(DexProgramClass clazz) {
return isInstantiatedDirectly(clazz) || isPinned(clazz) || isInstantiatedInterface(clazz);
}
public boolean isPinnedNotProgramOrLibraryOverride(DexDefinition definition) {
if (isPinned(definition)) {
return true;
}
if (definition.isDexEncodedMethod()) {
DexEncodedMethod method = definition.asDexEncodedMethod();
return !method.isProgramMethod(this) || method.isLibraryMethodOverride().isPossiblyTrue();
}
assert definition.isDexClass();
DexClass clazz = definition.asDexClass();
return clazz.isNotProgramClass() || isInstantiatedInterface(clazz.asProgramClass());
}
public boolean verifyNoIteratingOverPrunedClasses() {
classes()
.forEach(
clazz -> {
assert !wasPruned(clazz.type) : clazz.type + " was not pruned";
});
return true;
}
}