blob: 72a0a0b9a15f2cb783968554f91fae9f970c9d16 [file] [log] [blame]
// Copyright (c) 2016, 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.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
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.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexLibraryClass;
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.GraphLens;
import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.Consumer3;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OriginWithPosition;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.io.PrintStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class RootSetUtils {
public static class RootSetBuilder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final SubtypingInfo subtypingInfo;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
private final MutableItemsWithRules softPinned = new MutableItemsWithRules();
private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned =
new IdentityHashMap<>();
private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
new IdentityHashMap<>();
private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
new ConcurrentLinkedQueue<>();
private final InternalOptions options;
private final DexStringCache dexStringCache = new DexStringCache();
private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings =
new LinkedHashMap<>();
private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
private RootSetBuilder(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
Iterable<? extends ProguardConfigurationRule> rules) {
this.appView = appView;
this.subtypingInfo = subtypingInfo;
this.application = appView.appInfo().app().asDirect();
this.rules = rules;
this.options = appView.options();
}
private RootSetBuilder(
AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
this(appView, subtypingInfo, null);
}
void handleMatchedAnnotation(AnnotationMatchResult annotation) {
// Intentionally empty.
}
// Process a class with the keep rule.
private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
if (!satisfyClassType(rule, clazz)) {
return;
}
if (!satisfyAccessFlag(rule, clazz)) {
return;
}
AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz);
if (annotationMatchResult == null) {
return;
}
handleMatchedAnnotation(annotationMatchResult);
// In principle it should make a difference whether the user specified in a class
// spec that a class either extends or implements another type. However, proguard
// seems not to care, so users have started to use this inconsistently. We are thus
// inconsistent, as well, but tell them.
// TODO(herhut): One day make this do what it says.
if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
return;
}
if (!rule.getClassNames().matches(clazz.type)) {
return;
}
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
if (rule instanceof ProguardKeepRule) {
if (clazz.isNotProgramClass()) {
return;
}
switch (((ProguardKeepRule) rule).getType()) {
case KEEP_CLASS_MEMBERS:
// Members mentioned at -keepclassmembers always depend on their holder.
preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
markMatchingVisibleMethods(
clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
markMatchingVisibleFields(
clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
break;
case KEEP_CLASSES_WITH_MEMBERS:
if (!allRulesSatisfied(memberKeepRules, clazz)) {
break;
}
// fall through;
case KEEP:
markClass(clazz, rule, ifRule);
preconditionSupplier = new HashMap<>();
if (ifRule != null) {
// Static members in -keep are pinned no matter what.
preconditionSupplier.put(DexDefinition::isStaticMember, null);
// Instance members may need to be kept even though the holder is not instantiated.
preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
} else {
// Members mentioned at -keep should always be pinned as long as that -keep rule is
// not triggered conditionally.
preconditionSupplier.put((definition -> true), null);
}
markMatchingVisibleMethods(
clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
markMatchingVisibleFields(
clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
break;
case CONDITIONAL:
throw new Unreachable("-if rule will be evaluated separately, not here.");
}
return;
}
// Only the ordinary keep rules are supported in a conditional rule.
assert ifRule == null;
if (rule instanceof ProguardIfRule) {
throw new Unreachable("-if rule will be evaluated separately, not here.");
} else if (rule instanceof ProguardCheckDiscardRule) {
if (memberKeepRules.isEmpty()) {
markClass(clazz, rule, ifRule);
} else {
preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
markMatchingVisibleMethods(
clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
markMatchingVisibleFields(
clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
}
} else if (rule instanceof ProguardWhyAreYouKeepingRule) {
markClass(clazz, rule, ifRule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
} else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
|| rule instanceof ProguardAssumeNoSideEffectRule
|| rule instanceof ProguardAssumeValuesRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingOverriddenMethods(
appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
} else if (rule instanceof InlineRule
|| rule instanceof ConstantArgumentRule
|| rule instanceof UnusedArgumentRule
|| rule instanceof ReprocessMethodRule
|| rule instanceof WhyAreYouNotInliningRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
} else if (rule instanceof ClassInlineRule
|| rule instanceof NoUnusedInterfaceRemovalRule
|| rule instanceof NoVerticalClassMergingRule
|| rule instanceof NoHorizontalClassMergingRule
|| rule instanceof ReprocessClassInitializerRule) {
if (allRulesSatisfied(memberKeepRules, clazz)) {
markClass(clazz, rule, ifRule);
}
} else if (rule instanceof MemberValuePropagationRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
} else {
assert rule instanceof ProguardIdentifierNameStringRule;
markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
}
}
void runPerRule(
ExecutorService executorService,
List<Future<?>> futures,
ProguardConfigurationRule rule,
ProguardIfRule ifRule) {
List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
if (specifics != null) {
// This keep rule only lists specific type matches.
// This means there is no need to iterate over all classes.
for (DexType type : specifics) {
DexClass clazz = application.definitionFor(type);
// Ignore keep rule iff it does not reference a class in the app.
if (clazz != null) {
process(clazz, rule, ifRule);
}
}
return;
}
futures.add(
executorService.submit(
() -> {
for (DexProgramClass clazz :
rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) {
process(clazz, rule, ifRule);
}
if (rule.applyToNonProgramClasses()) {
for (DexLibraryClass clazz : application.libraryClasses()) {
process(clazz, rule, ifRule);
}
}
}));
}
public RootSet build(ExecutorService executorService) throws ExecutionException {
application.timing.begin("Build root set...");
try {
List<Future<?>> futures = new ArrayList<>();
// Mark all the things explicitly listed in keep rules.
if (rules != null) {
for (ProguardConfigurationRule rule : rules) {
if (rule instanceof ProguardIfRule) {
ProguardIfRule ifRule = (ProguardIfRule) rule;
ifRules.add(ifRule);
} else {
runPerRule(executorService, futures, rule, null);
}
}
ThreadUtils.awaitFutures(futures);
}
} finally {
application.timing.end();
}
generateAssumeNoSideEffectsWarnings();
if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
.visit(appView.appInfo().classes(), this::propagateAssumeRules);
}
if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
appView,
subtypingInfo,
alwaysClassInline,
noVerticalClassMerging,
noHorizontalClassMerging,
alwaysInline,
bypassClinitforInlining);
}
assert Sets.intersection(neverInline, alwaysInline).isEmpty()
&& Sets.intersection(neverInline, forceInline).isEmpty()
: "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
return new RootSet(
noShrinking,
softPinned,
noObfuscation,
ImmutableList.copyOf(reasonAsked.values()),
ImmutableList.copyOf(checkDiscarded.values()),
alwaysInline,
forceInline,
neverInline,
neverInlineDueToSingleCaller,
bypassClinitforInlining,
whyAreYouNotInlining,
keepParametersWithConstantValue,
keepUnusedArguments,
reprocess,
neverReprocess,
alwaysClassInline,
neverClassInline,
noUnusedInterfaceRemoval,
noVerticalClassMerging,
noHorizontalClassMerging,
neverPropagateValue,
mayHaveSideEffects,
noSideEffects,
assumedValues,
dependentNoShrinking,
dependentSoftPinned,
dependentKeepClassCompatRule,
identifierNameStrings,
ifRules,
Lists.newArrayList(delayedRootSetActionItems));
}
private void propagateAssumeRules(DexClass clazz) {
Set<DexType> subTypes = subtypingInfo.allImmediateSubtypes(clazz.type);
if (subTypes.isEmpty()) {
return;
}
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
// If the method has a body, it may have side effects. Don't do bottom-up propagation.
if (encodedMethod.hasCode()) {
assert !encodedMethod.shouldNotHaveCode();
continue;
}
propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, noSideEffects);
propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, assumedValues);
}
}
private void propagateAssumeRules(
DexType type,
DexMethod reference,
Set<DexType> subTypes,
Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
ProguardMemberRule ruleToBePropagated = null;
for (DexType subType : subTypes) {
DexMethod referenceInSubType =
appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
// Those rules are bound to definitions, not references. If the current subtype does not
// override the method, and when the retrieval of bound rule fails, it is unclear whether it
// is due to the lack of the definition or it indeed means no matching rules. Similar to how
// we apply those assume rules, here we use a resolved target.
DexEncodedMethod target =
appView
.appInfo()
.unsafeResolveMethodDueToDexFormat(referenceInSubType)
.getSingleTarget();
// But, the resolution should not be landed on the current type we are visiting.
if (target == null || target.getHolderType() == type) {
continue;
}
ProguardMemberRule ruleInSubType = assumeRulePool.get(target.getReference());
// We are looking for the greatest lower bound of assume rules from all sub types.
// If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
if (ruleInSubType == null) {
ruleToBePropagated = null;
break;
}
if (ruleToBePropagated == null) {
ruleToBePropagated = ruleInSubType;
} else {
// TODO(b/133208961): Introduce comparison/meet of assume rules.
if (!ruleToBePropagated.equals(ruleInSubType)) {
ruleToBePropagated = null;
break;
}
}
}
if (ruleToBePropagated != null) {
assumeRulePool.put(reference, ruleToBePropagated);
}
}
ConsequentRootSet buildConsequentRootSet() {
return new ConsequentRootSet(
neverInline,
neverInlineDueToSingleCaller,
neverClassInline,
noShrinking,
softPinned,
noObfuscation,
dependentNoShrinking,
dependentSoftPinned,
dependentKeepClassCompatRule,
Lists.newArrayList(delayedRootSetActionItems));
}
private static DexDefinition testAndGetPrecondition(
DexDefinition definition,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
if (preconditionSupplier == null) {
return null;
}
DexDefinition precondition = null;
boolean conditionEverMatched = false;
for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
if (entry.getKey().test(definition)) {
precondition = entry.getValue();
conditionEverMatched = true;
break;
}
}
// If precondition-supplier is given, there should be at least one predicate that holds.
// Actually, there should be only one predicate as we break the loop when it is found.
assert conditionEverMatched;
return precondition;
}
private void markMatchingVisibleMethods(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
boolean includeLibraryClasses,
ProguardIfRule ifRule) {
Set<Wrapper<DexMethod>> methodsMarked =
options.forceProguardCompatibility ? null : new HashSet<>();
Stack<DexClass> worklist = new Stack<>();
worklist.add(clazz);
while (!worklist.isEmpty()) {
DexClass currentClass = worklist.pop();
if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
break;
}
// In compat mode traverse all direct methods in the hierarchy.
currentClass
.getMethodCollection()
.forEachDirectMethodMatching(
method ->
currentClass == clazz
|| (method.isStatic() && !method.isPrivate() && !method.isInitializer())
|| options.forceProguardCompatibility,
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
});
currentClass
.virtualMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
});
if (currentClass.superType != null) {
DexClass dexClass = application.definitionFor(currentClass.superType);
if (dexClass != null) {
worklist.add(dexClass);
}
}
}
// TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
// fullmode.
if (clazz.isProgramClass()
&& rule.isProguardKeepRule()
&& !rule.asProguardKeepRule().getModifiers().allowsShrinking) {
new SynthesizeMissingInterfaceMethodsForMemberRules(
clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
.run();
}
}
/**
* Utility class for visiting all super interfaces to ensure we keep method definitions
* specified by proguard rules. If possible, we generate a forwarding bridge to the resolved
* target. If not, we specifically synthesize a keep rule for the interface method.
*/
private class SynthesizeMissingInterfaceMethodsForMemberRules {
private final DexProgramClass originalClazz;
private final Collection<ProguardMemberRule> memberKeepRules;
private final ProguardConfigurationRule context;
private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
private final ProguardIfRule ifRule;
private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
private SynthesizeMissingInterfaceMethodsForMemberRules(
DexProgramClass originalClazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule context,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
ProguardIfRule ifRule) {
assert context.isProguardKeepRule();
assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
this.originalClazz = originalClazz;
this.memberKeepRules = memberKeepRules;
this.context = context;
this.preconditionSupplier = preconditionSupplier;
this.ifRule = ifRule;
}
void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
// Intentionally empty.
}
void run() {
visitAllSuperInterfaces(originalClazz.type);
}
private void visitAllSuperInterfaces(DexType type) {
DexClass clazz = appView.definitionFor(type);
if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) {
return;
}
for (DexType iface : clazz.interfaces.values) {
visitAllSuperInterfaces(iface);
}
if (!clazz.isInterface()) {
visitAllSuperInterfaces(clazz.superType);
return;
}
if (originalClazz == clazz) {
return;
}
for (DexEncodedMethod method : clazz.virtualMethods()) {
// Check if we already added this.
Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.getReference());
if (!seenMethods.add(wrapped)) {
continue;
}
for (ProguardMemberRule rule : memberKeepRules) {
if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
tryAndKeepMethodOnClass(method, rule);
}
}
}
}
private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
SingleResolutionResult resolutionResult =
appView
.appInfo()
.resolveMethodOn(originalClazz, method.getReference())
.asSingleResolution();
if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
return;
}
if (resolutionResult.getResolvedHolder() == originalClazz
|| resolutionResult.getResolvedHolder().isNotProgramClass()) {
return;
}
if (!resolutionResult.getResolvedHolder().isInterface()) {
// TODO(b/143643942): For fullmode, this check should probably be removed.
return;
}
ProgramMethod resolutionMethod =
new ProgramMethod(
resolutionResult.getResolvedHolder().asProgramClass(),
resolutionResult.getResolvedMethod());
ProgramMethod methodToKeep =
canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
? new ProgramMethod(
originalClazz,
resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
: resolutionMethod;
delayedRootSetActionItems.add(
new InterfaceMethodSyntheticBridgeAction(
methodToKeep,
resolutionMethod,
(rootSetBuilder) -> {
if (Log.ENABLED) {
Log.verbose(
getClass(),
"Marking method `%s` due to `%s { %s }`.",
methodToKeep,
context,
rule);
}
DexDefinition precondition =
testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
rootSetBuilder.addItemToSets(
methodToKeep.getDefinition(), context, rule, precondition, ifRule);
}));
}
}
private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
return appView.options().isGeneratingDex()
|| ArrayUtils.contains(holder.interfaces.values, target.getHolderType());
}
private void markMatchingOverriddenMethods(
AppInfoWithClassHierarchy appInfoWithSubtyping,
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
boolean onlyIncludeProgramClasses,
ProguardIfRule ifRule) {
Set<DexType> visited = new HashSet<>();
Deque<DexType> worklist = new ArrayDeque<>();
// Intentionally skip the current `clazz`, assuming it's covered by
// markMatchingVisibleMethods.
worklist.addAll(subtypingInfo.allImmediateSubtypes(clazz.type));
while (!worklist.isEmpty()) {
DexType currentType = worklist.poll();
if (!visited.add(currentType)) {
continue;
}
DexClass currentClazz = appView.definitionFor(currentType);
if (currentClazz == null) {
continue;
}
if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) {
continue;
}
currentClazz
.virtualMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
});
worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
}
}
private void markMatchingMethods(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
ProguardIfRule ifRule) {
clazz.forEachMethod(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
});
}
private void markMatchingVisibleFields(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
boolean includeLibraryClasses,
ProguardIfRule ifRule) {
while (clazz != null) {
if (!includeLibraryClasses && clazz.isNotProgramClass()) {
return;
}
clazz.forEachField(
field -> {
DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRule);
});
clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
}
}
private void markMatchingFields(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
ProguardIfRule ifRule) {
clazz.forEachField(
field -> {
DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRule);
});
}
// TODO(b/67934426): Test this code.
public static void writeSeeds(
AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
appInfo
.getKeepInfo()
.forEachPinnedType(
type -> {
if (include.test(type)) {
out.println(type.toSourceString());
}
});
appInfo
.getKeepInfo()
.forEachPinnedField(
field -> {
if (include.test(field.holder)) {
out.println(
field.holder.toSourceString()
+ ": "
+ field.type.toSourceString()
+ " "
+ field.name.toSourceString());
}
});
appInfo
.getKeepInfo()
.forEachPinnedMethod(
method -> {
if (!include.test(method.holder)) {
return;
}
DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method));
DexEncodedMethod definition = method.lookupOnClass(holder);
if (definition == null) {
assert method.match(appInfo.dexItemFactory().deserializeLambdaMethod);
return;
}
out.print(method.holder.toSourceString() + ": ");
if (definition.isClassInitializer()) {
out.print(Constants.CLASS_INITIALIZER_NAME);
} else if (definition.isInstanceInitializer()) {
String holderName = method.holder.toSourceString();
String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
out.print(constrName);
} else {
out.print(
method.proto.returnType.toSourceString()
+ " "
+ method.name.toSourceString());
}
boolean first = true;
out.print("(");
for (DexType param : method.proto.parameters.values) {
if (!first) {
out.print(",");
}
first = false;
out.print(param.toSourceString());
}
out.println(")");
});
out.close();
}
static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
}
static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) {
return rule.getClassAccessFlags().containsAll(clazz.accessFlags)
&& rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
}
static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
return containsAllAnnotations(rule.getClassAnnotations(), clazz);
}
boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
if (satisfyExtendsRule(clazz, rule)) {
return true;
}
return satisfyImplementsRule(clazz, rule);
}
boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
return true;
}
// It is possible that this class used to inherit from another class X, but no longer does it,
// because X has been merged into `clazz`.
return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
}
boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
while (type != null) {
DexClass clazz = application.definitionFor(type);
if (clazz == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
// TODO(b/110141157): Should the vertical class merger move annotations from the source to
// the target class? If so, it is sufficient only to apply the annotation-matcher to the
// annotations of `class`.
if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
AnnotationMatchResult annotationMatchResult =
containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
if (annotationMatchResult != null) {
handleMatchedAnnotation(annotationMatchResult);
return true;
}
}
type = clazz.superType;
}
return false;
}
boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
return true;
}
// It is possible that this class used to implement an interface I, but no longer does it,
// because I has been merged into `clazz`.
return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
}
private boolean anyImplementedInterfaceMatchesImplementsRule(
DexClass clazz, ProguardConfigurationRule rule) {
// TODO(herhut): Maybe it would be better to do this breadth first.
if (clazz == null) {
return false;
}
for (DexType iface : clazz.interfaces.values) {
DexClass ifaceClass = application.definitionFor(iface);
if (ifaceClass == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
// TODO(b/110141157): Should the vertical class merger move annotations from the source to
// the target class? If so, it is sufficient only to apply the annotation-matcher to the
// annotations of `ifaceClass`.
if (rule.getInheritanceClassName().matches(iface, appView)) {
AnnotationMatchResult annotationMatchResult =
containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
if (annotationMatchResult != null) {
handleMatchedAnnotation(annotationMatchResult);
return true;
}
}
if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
return true;
}
}
if (clazz.superType == null) {
return false;
}
DexClass superClass = application.definitionFor(clazz.superType);
if (superClass == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
}
private boolean anySourceMatchesInheritanceRuleDirectly(
DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
// TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
// the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
return appView.verticallyMergedClasses() != null
&& appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
.filter(
sourceType ->
appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
.anyMatch(rule.getInheritanceClassName()::matches);
}
private boolean allRulesSatisfied(
Collection<ProguardMemberRule> memberKeepRules, DexClass clazz) {
for (ProguardMemberRule rule : memberKeepRules) {
if (!ruleSatisfied(rule, clazz)) {
return false;
}
}
return true;
}
/**
* Checks whether the given rule is satisfied by this clazz, not taking superclasses into
* account.
*/
private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
return ruleSatisfiedByMethods(rule, clazz.directMethods())
|| ruleSatisfiedByMethods(rule, clazz.virtualMethods())
|| ruleSatisfiedByFields(rule, clazz.staticFields())
|| ruleSatisfiedByFields(rule, clazz.instanceFields());
}
boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
if (rule.getRuleType().includesMethods()) {
for (DexEncodedMethod method : methods) {
if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
return true;
}
}
}
return false;
}
boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
if (rule.getRuleType().includesFields()) {
for (DexEncodedField field : fields) {
if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
return true;
}
}
}
return false;
}
static AnnotationMatchResult containsAllAnnotations(
List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
return containsAllAnnotations(annotationMatchers, clazz.annotations());
}
static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
boolean containsAllAnnotations(
List<ProguardTypeMatcher> annotationMatchers,
DexEncodedMember<D, R> member,
Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
AnnotationMatchResult annotationMatchResult =
containsAllAnnotations(annotationMatchers, member.annotations());
if (annotationMatchResult != null) {
matchedAnnotationsConsumer.accept(annotationMatchResult);
return true;
}
if (member.isDexEncodedMethod()) {
DexEncodedMethod method = member.asDexEncodedMethod();
for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
annotationMatchResult =
containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
if (annotationMatchResult != null) {
matchedAnnotationsConsumer.accept(annotationMatchResult);
return true;
}
}
}
return false;
}
private static AnnotationMatchResult containsAllAnnotations(
List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
if (annotationMatchers.isEmpty()) {
return AnnotationsIgnoredMatchResult.getInstance();
}
List<DexAnnotation> matchedAnnotations = new ArrayList<>();
for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
DexAnnotation matchedAnnotation =
getFirstAnnotationThatMatches(annotationMatcher, annotations);
if (matchedAnnotation == null) {
return null;
}
matchedAnnotations.add(matchedAnnotation);
}
return new ConcreteAnnotationMatchResult(matchedAnnotations);
}
private static DexAnnotation getFirstAnnotationThatMatches(
ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) {
for (DexAnnotation annotation : annotations.annotations) {
if (annotationMatcher.matches(annotation.getAnnotationType())) {
return annotation;
}
}
return null;
}
private void markMethod(
DexEncodedMethod method,
Collection<ProguardMemberRule> rules,
Set<Wrapper<DexMethod>> methodsMarked,
ProguardConfigurationRule context,
DexDefinition precondition,
ProguardIfRule ifRule) {
if (methodsMarked != null
&& methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.getReference()))) {
// Ignore, method is overridden in sub class.
return;
}
for (ProguardMemberRule rule : rules) {
if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(
getClass(), "Marking method `%s` due to `%s { %s }`.", method, context, rule);
}
if (methodsMarked != null) {
methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.getReference()));
}
addItemToSets(method, context, rule, precondition, ifRule);
}
}
}
private void markField(
DexEncodedField field,
Collection<ProguardMemberRule> rules,
ProguardConfigurationRule context,
DexDefinition precondition,
ProguardIfRule ifRule) {
for (ProguardMemberRule rule : rules) {
if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context, rule);
}
addItemToSets(field, context, rule, precondition, ifRule);
}
}
}
private void markClass(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking class `%s` due to `%s`.", clazz.type, rule);
}
addItemToSets(clazz, rule, null, null, ifRule);
}
private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) {
if (type.isVoidType()) {
return;
}
if (type.isArrayType()) {
type = type.toBaseType(appView.dexItemFactory());
}
if (type.isPrimitiveType()) {
return;
}
DexClass definition = appView.definitionFor(type);
if (definition == null || definition.isNotProgramClass()) {
return;
}
// Keep the type if the item is also kept.
dependentNoShrinking
.computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
.addClassWithRule(type, context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
if (appView.options().isMinificationEnabled()) {
noObfuscation.add(type);
}
}
private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
if (item.isDexEncodedMethod()) {
DexMethod method = item.asDexEncodedMethod().getReference();
includeDescriptor(item, method.proto.returnType, context);
for (DexType value : method.proto.parameters.values) {
includeDescriptor(item, value, context);
}
} else if (item.isDexEncodedField()) {
DexField field = item.asDexEncodedField().getReference();
includeDescriptor(item, field.type, context);
} else {
assert item.isDexClass();
}
}
private synchronized void addItemToSets(
DexDefinition item,
ProguardConfigurationRule context,
ProguardMemberRule rule,
DexDefinition precondition,
ProguardIfRule ifRule) {
if (context instanceof ProguardKeepRule) {
if (item.isDexEncodedField()) {
DexEncodedField encodedField = item.asDexEncodedField();
if (encodedField.getOptimizationInfo().cannotBeKept()) {
// We should only ever get here with if rules.
assert ifRule != null;
return;
}
} else if (item.isDexEncodedMethod()) {
DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
if (encodedMethod.isClassInitializer() && !options.debug) {
// Don't keep class initializers.
return;
}
if (encodedMethod.getOptimizationInfo().cannotBeKept()) {
// We should only ever get here with if rules.
assert ifRule != null;
return;
}
if (options.isGeneratingDex()
&& encodedMethod.getReference().isLambdaDeserializeMethod(appView.dexItemFactory())) {
// Don't keep lambda deserialization methods.
return;
}
// If desugaring is enabled, private and static interface methods will be moved to a
// companion class. So we don't need to add them to the root set in the beginning.
if (options.isInterfaceMethodDesugaringEnabled()
&& encodedMethod.hasCode()
&& (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
DexClass holder = appView.definitionFor(encodedMethod.getHolderType());
if (holder != null && holder.isInterface()) {
if (rule.isSpecific()) {
options.reporter.warning(
new StringDiagnostic(
"The rule `"
+ rule
+ "` is ignored because the targeting interface method `"
+ encodedMethod.getReference().toSourceString()
+ "` will be desugared."));
}
return;
}
}
}
// The reason for keeping should link to the conditional rule as a whole, if present.
ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
// The modifiers are specified on the actual keep rule (ie, the consequent/context).
ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
// In compatibility mode, for a match on instance members a referenced class becomes live.
if (options.forceProguardCompatibility
&& !modifiers.allowsShrinking
&& precondition != null
&& precondition.isDexClass()) {
if (!item.isDexClass() && !item.isStaticMember()) {
dependentKeepClassCompatRule
.computeIfAbsent(precondition.asDexClass().getType(), i -> new HashSet<>())
.add(keepRule);
context.markAsUsed();
}
}
if (!modifiers.allowsShrinking) {
if (precondition != null) {
dependentNoShrinking
.computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
.addReferenceWithRule(item.getReference(), keepRule);
} else {
noShrinking.addReferenceWithRule(item.getReference(), keepRule);
}
context.markAsUsed();
} else if (!modifiers.allowsOptimization) {
if (precondition != null) {
dependentSoftPinned
.computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
.addReferenceWithRule(item.getReference(), keepRule);
} else {
softPinned.addReferenceWithRule(item.getReference(), keepRule);
}
}
if (!modifiers.allowsOptimization) {
// The -dontoptimize flag has only effect through the keep all rule, but we still
// need to mark the rule as used.
context.markAsUsed();
}
if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
noObfuscation.add(item.getReference());
context.markAsUsed();
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
context.markAsUsed();
}
} else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
mayHaveSideEffects.put(item.getReference(), rule);
context.markAsUsed();
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
if (item.isDexEncodedMember()) {
DexEncodedMember<?, ?> member = item.asDexEncodedMember();
if (member.getHolderType() == appView.dexItemFactory().objectType) {
assert member.isDexEncodedMethod();
reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
member.asDexEncodedMethod(), (ProguardAssumeNoSideEffectRule) context);
} else {
noSideEffects.put(member.getReference(), rule);
if (member.isDexEncodedMethod()) {
DexEncodedMethod method = member.asDexEncodedMethod();
if (method.isClassInitializer()) {
feedback.classInitializerMayBePostponed(method);
}
}
}
context.markAsUsed();
}
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
reasonAsked.computeIfAbsent(item.getReference(), i -> i);
context.markAsUsed();
} else if (context instanceof ProguardAssumeValuesRule) {
if (item.isDexEncodedMember()) {
assumedValues.put(item.asDexEncodedMember().getReference(), rule);
context.markAsUsed();
}
} else if (context instanceof ProguardCheckDiscardRule) {
checkDiscarded.computeIfAbsent(item.getReference(), i -> i);
context.markAsUsed();
} else if (context instanceof InlineRule) {
if (item.isDexEncodedMethod()) {
DexMethod reference = item.asDexEncodedMethod().getReference();
switch (((InlineRule) context).getType()) {
case ALWAYS:
alwaysInline.add(reference);
break;
case FORCE:
forceInline.add(reference);
break;
case NEVER:
neverInline.add(reference);
break;
case NEVER_SINGLE_CALLER:
neverInlineDueToSingleCaller.add(reference);
break;
default:
throw new Unreachable();
}
context.markAsUsed();
}
} else if (context instanceof WhyAreYouNotInliningRule) {
if (!item.isDexEncodedMethod()) {
throw new Unreachable();
}
whyAreYouNotInlining.add(item.asDexEncodedMethod().getReference());
context.markAsUsed();
} else if (context.isClassInlineRule()) {
ClassInlineRule classInlineRule = context.asClassInlineRule();
DexClass clazz = item.asDexClass();
if (clazz == null) {
throw new IllegalStateException(
"Unexpected -"
+ classInlineRule.typeString()
+ " rule for a non-class type: `"
+ item.getReference().toSourceString()
+ "`");
}
switch (classInlineRule.getType()) {
case ALWAYS:
alwaysClassInline.addElement(item.asDexClass().type);
break;
case NEVER:
neverClassInline.add(item.asDexClass().type);
break;
default:
throw new Unreachable();
}
context.markAsUsed();
} else if (context instanceof NoUnusedInterfaceRemovalRule) {
noUnusedInterfaceRemoval.add(item.asDexClass().type);
context.markAsUsed();
} else if (context instanceof NoVerticalClassMergingRule) {
noVerticalClassMerging.add(item.asDexClass().type);
context.markAsUsed();
} else if (context instanceof NoHorizontalClassMergingRule) {
noHorizontalClassMerging.add(item.asDexClass().type);
context.markAsUsed();
} else if (context instanceof MemberValuePropagationRule) {
switch (((MemberValuePropagationRule) context).getType()) {
case NEVER:
// Only add members from propgram classes to `neverPropagateValue` since class member
// values from library types are not propagated by default.
if (item.isDexEncodedField()) {
DexEncodedField field = item.asDexEncodedField();
if (field.isProgramField(appView)) {
neverPropagateValue.add(item.asDexEncodedField().getReference());
context.markAsUsed();
}
} else if (item.isDexEncodedMethod()) {
DexEncodedMethod method = item.asDexEncodedMethod();
if (method.isProgramMethod(appView)) {
neverPropagateValue.add(item.asDexEncodedMethod().getReference());
context.markAsUsed();
}
}
break;
default:
throw new Unreachable();
}
} else if (context instanceof ProguardIdentifierNameStringRule) {
if (item.isDexEncodedField()) {
identifierNameStrings.add(item.asDexEncodedField().getReference());
context.markAsUsed();
} else if (item.isDexEncodedMethod()) {
identifierNameStrings.add(item.asDexEncodedMethod().getReference());
context.markAsUsed();
}
} else if (context instanceof ConstantArgumentRule) {
if (item.isDexEncodedMethod()) {
keepParametersWithConstantValue.add(item.asDexEncodedMethod().getReference());
context.markAsUsed();
}
} else if (context instanceof ReprocessClassInitializerRule) {
DexProgramClass clazz = item.asProgramClass();
if (clazz != null && clazz.hasClassInitializer()) {
switch (context.asReprocessClassInitializerRule().getType()) {
case ALWAYS:
reprocess.add(clazz.getClassInitializer().getReference());
break;
case NEVER:
neverReprocess.add(clazz.getClassInitializer().getReference());
break;
default:
throw new Unreachable();
}
context.markAsUsed();
}
} else if (context.isReprocessMethodRule()) {
if (item.isDexEncodedMethod()) {
DexEncodedMethod method = item.asDexEncodedMethod();
switch (context.asReprocessMethodRule().getType()) {
case ALWAYS:
reprocess.add(method.getReference());
break;
case NEVER:
neverReprocess.add(method.getReference());
break;
default:
throw new Unreachable();
}
context.markAsUsed();
}
} else if (context instanceof UnusedArgumentRule) {
if (item.isDexEncodedMethod()) {
keepUnusedArguments.add(item.asDexEncodedMethod().getReference());
context.markAsUsed();
}
} else {
throw new Unreachable();
}
}
private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
DexEncodedMethod method, ProguardAssumeNoSideEffectRule context) {
assert method.getHolderType() == options.dexItemFactory().objectType;
OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
assumeNoSideEffectsWarnings
.computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::compareTo))
.add(method.getReference());
}
private boolean isWaitOrNotifyMethod(DexMethod method) {
return method.name == options.itemFactory.waitMethodName
|| method.name == options.itemFactory.notifyMethodName
|| method.name == options.itemFactory.notifyAllMethodName;
}
private void generateAssumeNoSideEffectsWarnings() {
if (appView.getDontWarnConfiguration().matches(options.itemFactory.objectType)) {
// Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or
// wait() anyway.
return;
}
assumeNoSideEffectsWarnings.forEach(
(originWithPosition, methods) -> {
boolean matchesWaitOrNotifyMethods =
methods.stream().anyMatch(this::isWaitOrNotifyMethod);
if (!matchesWaitOrNotifyMethods) {
// We model the remaining methods on java.lang.Object, and thus there should be no
// need
// to warn in this case.
return;
}
options.reporter.warning(
new AssumeNoSideEffectsRuleForObjectMembersDiagnostic.Builder()
.addMatchedMethods(methods)
.setOrigin(originWithPosition.getOrigin())
.setPosition(originWithPosition.getPosition())
.build());
});
}
}
abstract static class RootSetBase {
final Set<DexMethod> neverInline;
final Set<DexMethod> neverInlineDueToSingleCaller;
final Set<DexType> neverClassInline;
final MutableItemsWithRules noShrinking;
final MutableItemsWithRules softPinned;
final Set<DexReference> noObfuscation;
final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
final Map<DexReference, MutableItemsWithRules> dependentSoftPinned;
final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
final List<DelayedRootSetActionItem> delayedRootSetActionItems;
RootSetBase(
Set<DexMethod> neverInline,
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexType> neverClassInline,
MutableItemsWithRules noShrinking,
MutableItemsWithRules softPinned,
Set<DexReference> noObfuscation,
Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
this.neverInline = neverInline;
this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
this.neverClassInline = neverClassInline;
this.noShrinking = noShrinking;
this.softPinned = softPinned;
this.noObfuscation = noObfuscation;
this.dependentNoShrinking = dependentNoShrinking;
this.dependentSoftPinned = dependentSoftPinned;
this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
this.delayedRootSetActionItems = delayedRootSetActionItems;
}
public void forEachClassWithDependentItems(
DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
for (DexReference reference : dependentNoShrinking.keySet()) {
if (reference.isDexType()) {
DexType type = reference.asDexType();
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
if (clazz != null) {
consumer.accept(clazz);
}
}
}
}
public void forEachMemberWithDependentItems(
DexDefinitionSupplier definitions,
BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
dependentNoShrinking.forEach(
(reference, dependentItems) -> {
if (reference.isDexMember()) {
DexMember<?, ?> member = reference.asDexMember();
DexProgramClass holder =
asProgramClassOrNull(definitions.definitionForHolder(member));
if (holder != null) {
DexEncodedMember<?, ?> definition = holder.lookupMember(member);
if (definition != null) {
consumer.accept(definition, dependentItems);
}
}
}
});
}
public void forEachDependentInstanceConstructor(
DexProgramClass clazz,
AppView<?> appView,
BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
getDependentItems(clazz)
.forEachMethod(
(reference, reasons) -> {
DexProgramClass holder =
asProgramClassOrNull(appView.definitionForHolder(reference));
if (holder != null) {
ProgramMethod method = holder.lookupProgramMethod(reference);
if (method != null && method.getDefinition().isInstanceInitializer()) {
fn.accept(method, reasons);
}
}
});
}
public void forEachDependentMember(
DexDefinition item,
AppView<?> appView,
Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
getDependentItems(item)
.forEachMember(
(reference, reasons) -> {
DexProgramClass holder =
asProgramClassOrNull(appView.definitionForHolder(reference));
if (holder != null) {
ProgramMember<?, ?> member = holder.lookupProgramMember(reference);
if (member != null) {
fn.accept(item, member, reasons);
}
}
});
}
public void forEachDependentNonStaticMember(
DexDefinition item,
AppView<?> appView,
Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
forEachDependentMember(
item,
appView,
(precondition, member, reasons) -> {
if (!member.getDefinition().isStatic()) {
fn.accept(precondition, member, reasons);
}
});
}
public void forEachDependentStaticMember(
DexDefinition item,
AppView<?> appView,
Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
forEachDependentMember(
item,
appView,
(precondition, member, reasons) -> {
if (member.getDefinition().isStatic()) {
fn.accept(precondition, member, reasons);
}
});
}
ItemsWithRules getDependentItems(DexDefinition item) {
ItemsWithRules found = dependentNoShrinking.get(item.getReference());
return found != null ? found : ItemsWithRules.empty();
}
Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
return dependentKeepClassCompatRule.get(type);
}
}
abstract static class ItemsWithRules {
public static ItemsWithRules empty() {
return MutableItemsWithRules.EMPTY;
}
public abstract boolean containsClass(DexType type);
public abstract boolean containsField(DexField field);
public abstract boolean containsMethod(DexMethod method);
public final boolean containsReference(DexReference reference) {
return reference.apply(this::containsClass, this::containsField, this::containsMethod);
}
public abstract void forEachClass(Consumer<? super DexType> consumer);
public abstract void forEachClass(
BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer);
public abstract void forEachField(Consumer<? super DexField> consumer);
public abstract void forEachField(
BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer);
public abstract void forEachMember(
BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
public abstract void forEachMethod(
BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
return reference.apply(
this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
}
}
static class MutableItemsWithRules extends ItemsWithRules {
private static final ItemsWithRules EMPTY =
new MutableItemsWithRules(
Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
MutableItemsWithRules() {
this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
}
private MutableItemsWithRules(
Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
this.classesWithRules = classesWithRules;
this.fieldsWithRules = fieldsWithRules;
this.methodsWithRules = methodsWithRules;
}
public void addAll(ItemsWithRules items) {
items.forEachClass(this::addClassWithRules);
items.forEachField(this::addFieldWithRules);
items.forEachMethod(this::addMethodWithRules);
}
public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
}
public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
}
public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
}
public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
}
public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
}
public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
}
public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
reference.accept(
this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
}
public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
reference.accept(
this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
}
@Override
public boolean containsClass(DexType type) {
return classesWithRules.containsKey(type);
}
@Override
public boolean containsField(DexField field) {
return fieldsWithRules.containsKey(field);
}
@Override
public boolean containsMethod(DexMethod method) {
return methodsWithRules.containsKey(method);
}
public void forEachReference(Consumer<DexReference> consumer) {
forEachClass(consumer);
forEachMember(consumer);
}
@Override
public void forEachClass(Consumer<? super DexType> consumer) {
classesWithRules.keySet().forEach(consumer);
}
@Override
public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) {
classesWithRules.forEach(consumer);
}
@Override
public void forEachField(Consumer<? super DexField> consumer) {
fieldsWithRules.keySet().forEach(consumer);
}
@Override
public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
fieldsWithRules.forEach(consumer);
}
@Override
public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) {
forEachField(consumer);
forEachMethod(consumer);
}
@Override
public void forEachMember(
BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
forEachField(consumer);
forEachMethod(consumer);
}
@Override
public void forEachMethod(Consumer<? super DexMethod> consumer) {
methodsWithRules.keySet().forEach(consumer);
}
@Override
public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
methodsWithRules.forEach(consumer);
}
@Override
public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
return classesWithRules.get(type);
}
@Override
public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
return fieldsWithRules.get(field);
}
@Override
public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
return methodsWithRules.get(method);
}
public void removeClass(DexType type) {
classesWithRules.remove(type);
}
public void removeField(DexField field) {
fieldsWithRules.remove(field);
}
public void removeMethod(DexMethod method) {
methodsWithRules.remove(method);
}
public void removeReference(DexReference reference) {
reference.accept(this::removeClass, this::removeField, this::removeMethod);
}
public void putAll(ItemsWithRules items) {
items.forEachClass(this::putClassWithRules);
items.forEachField(this::putFieldWithRules);
items.forEachMethod(this::putMethodWithRules);
}
public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
classesWithRules.put(type, rules);
}
public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
fieldsWithRules.put(field, rules);
}
public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
methodsWithRules.put(method, rules);
}
public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
reference.accept(
this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
}
public int size() {
return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
}
public void forEachReference(
BiConsumer<? super DexReference, Set<ProguardKeepRuleBase>> consumer) {
forEachClass(consumer);
forEachMember(consumer);
}
private MutableItemsWithRules prune(Set<DexType> prunedClasses) {
MutableItemsWithRules prunedItemsWithRules = new MutableItemsWithRules();
forEachReference(
(reference, rules) -> {
if (!prunedClasses.contains(reference.getContextType())) {
prunedItemsWithRules.addReferenceWithRules(reference, rules);
}
});
return prunedItemsWithRules;
}
private MutableItemsWithRules rewrittenWithLens(GraphLens graphLens) {
if (graphLens.isIdentityLens()) {
return this;
}
MutableItemsWithRules rewrittenItemsWithRules = new MutableItemsWithRules();
forEachReference(
(reference, rules) ->
rewriteAndApplyIfNotPrimitiveType(
graphLens,
reference,
rewritten -> rewrittenItemsWithRules.addReferenceWithRules(rewritten, rules)));
return rewrittenItemsWithRules;
}
}
public static class RootSet extends RootSetBase {
public final ImmutableList<DexReference> reasonAsked;
public final ImmutableList<DexReference> checkDiscarded;
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> bypassClinitForInlining;
public final Set<DexMethod> whyAreYouNotInlining;
public final Set<DexMethod> keepConstantArguments;
public final Set<DexMethod> keepUnusedArguments;
public final Set<DexMethod> reprocess;
public final Set<DexMethod> neverReprocess;
public final PredicateSet<DexType> alwaysClassInline;
public final Set<DexType> noUnusedInterfaceRemoval;
public final Set<DexType> noVerticalClassMerging;
public final Set<DexType> noHorizontalClassMerging;
public final Set<DexReference> neverPropagateValue;
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
private RootSet(
MutableItemsWithRules noShrinking,
MutableItemsWithRules softPinned,
Set<DexReference> noObfuscation,
ImmutableList<DexReference> reasonAsked,
ImmutableList<DexReference> checkDiscarded,
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexMethod> bypassClinitForInlining,
Set<DexMethod> whyAreYouNotInlining,
Set<DexMethod> keepConstantArguments,
Set<DexMethod> keepUnusedArguments,
Set<DexMethod> reprocess,
Set<DexMethod> neverReprocess,
PredicateSet<DexType> alwaysClassInline,
Set<DexType> neverClassInline,
Set<DexType> noUnusedInterfaceRemoval,
Set<DexType> noVerticalClassMerging,
Set<DexType> noHorizontalClassMerging,
Set<DexReference> neverPropagateValue,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
super(
neverInline,
neverInlineDueToSingleCaller,
neverClassInline,
noShrinking,
softPinned,
noObfuscation,
dependentNoShrinking,
dependentSoftPinned,
dependentKeepClassCompatRule,
delayedRootSetActionItems);
this.reasonAsked = reasonAsked;
this.checkDiscarded = checkDiscarded;
this.alwaysInline = alwaysInline;
this.forceInline = forceInline;
this.bypassClinitForInlining = bypassClinitForInlining;
this.whyAreYouNotInlining = whyAreYouNotInlining;
this.keepConstantArguments = keepConstantArguments;
this.keepUnusedArguments = keepUnusedArguments;
this.reprocess = reprocess;
this.neverReprocess = neverReprocess;
this.alwaysClassInline = alwaysClassInline;
this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
this.noVerticalClassMerging = noVerticalClassMerging;
this.noHorizontalClassMerging = noHorizontalClassMerging;
this.neverPropagateValue = neverPropagateValue;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
}
public void checkAllRulesAreUsed(InternalOptions options) {
List<ProguardConfigurationRule> rules = options.getProguardConfiguration().getRules();
if (rules != null) {
for (ProguardConfigurationRule rule : rules) {
if (!rule.isUsed()) {
String message =
"Proguard configuration rule does not match anything: `" + rule.toString() + "`";
StringDiagnostic diagnostic = new StringDiagnostic(message, rule.getOrigin());
if (options.testing.reportUnusedProguardConfigurationRules) {
options.reporter.info(diagnostic);
}
}
}
}
}
void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
neverInline.addAll(consequentRootSet.neverInline);
neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
neverClassInline.addAll(consequentRootSet.neverClassInline);
noObfuscation.addAll(consequentRootSet.noObfuscation);
if (addNoShrinking) {
noShrinking.addAll(consequentRootSet.noShrinking);
}
addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned);
consequentRootSet.dependentKeepClassCompatRule.forEach(
(type, rules) ->
dependentKeepClassCompatRule
.computeIfAbsent(type, k -> new HashSet<>())
.addAll(rules));
delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
}
// Add dependent items that depend on -if rules.
static void addDependentItems(
Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd,
Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) {
dependentItemsToAdd.forEach(
(reference, dependence) ->
dependentItemsToAddTo
.computeIfAbsent(reference, x -> new MutableItemsWithRules())
.putAll(dependence));
}
public void copy(DexReference original, DexReference rewritten) {
if (noShrinking.containsReference(original)) {
noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
}
if (noObfuscation.contains(original)) {
noObfuscation.add(rewritten);
}
if (original.isDexMember()) {
assert rewritten.isDexMember();
DexMember<?, ?> originalMember = original.asDexMember();
if (noSideEffects.containsKey(originalMember)) {
noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember));
}
if (assumedValues.containsKey(originalMember)) {
assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember));
}
}
}
public void prune(DexReference reference) {
noShrinking.removeReference(reference);
noObfuscation.remove(reference);
noSideEffects.remove(reference);
assumedValues.remove(reference);
}
public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
pruneDeadReferences(alwaysInline, definitions, enqueuer);
pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
}
private static void pruneDeadReferences(
Set<? extends DexReference> references,
DexDefinitionSupplier definitions,
Enqueuer enqueuer) {
references.removeIf(
reference -> {
if (reference.isDexType()) {
DexClass definition = definitions.definitionFor(reference.asDexType());
return definition == null || !enqueuer.isTypeLive(definition);
}
assert reference.isDexMember();
DexMember<?, ?> member = reference.asDexMember();
DexClass holder = definitions.definitionForHolder(member);
DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
if (definition == null) {
return true;
}
if (holder.isProgramClass()) {
if (definition.isDexEncodedField()) {
DexEncodedField field = definition.asDexEncodedField();
return !enqueuer.isFieldReferenced(field);
}
assert definition.isDexEncodedMethod();
DexEncodedMethod method = definition.asDexEncodedMethod();
return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
}
return !enqueuer.isNonProgramTypeLive(holder);
});
}
public void move(DexReference original, DexReference rewritten) {
copy(original, rewritten);
prune(original);
}
void shouldNotBeMinified(DexReference reference) {
noObfuscation.add(reference);
}
public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
noShrinking.forEachField(
reference -> {
DexClass holder = appInfo.definitionForHolder(reference);
DexEncodedField field = reference.lookupOnClass(holder);
if (field != null
&& (field.isStatic()
|| isKeptDirectlyOrIndirectly(field.getHolderType(), appInfo))) {
assert appInfo.isFieldRead(field)
: "Expected kept field `" + field.toSourceString() + "` to be read";
assert appInfo.isFieldWritten(field)
: "Expected kept field `" + field.toSourceString() + "` to be written";
}
});
return true;
}
public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
noShrinking.forEachMethod(
reference -> {
assert appInfo.isTargetedMethod(reference)
: "Expected kept method `" + reference.toSourceString() + "` to be targeted";
DexEncodedMethod method =
appInfo.definitionForHolder(reference).lookupMethod(reference);
if (!method.isAbstract()
&& isKeptDirectlyOrIndirectly(method.getHolderType(), appInfo)) {
assert appInfo.isLiveMethod(reference)
: "Expected non-abstract kept method `"
+ reference.toSourceString()
+ "` to be live";
}
});
return true;
}
public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
noShrinking.forEachClass(
type -> {
assert appInfo.isLiveProgramType(type)
: "Expected kept type `" + type.toSourceString() + "` to be live";
});
return true;
}
private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
if (noShrinking.containsClass(type)) {
return true;
}
DexClass clazz = appInfo.definitionFor(type);
if (clazz == null) {
return false;
}
if (clazz.superType != null) {
return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
}
return false;
}
public boolean verifyKeptItemsAreKept(AppView<? extends AppInfoWithClassHierarchy> appView) {
AppInfoWithClassHierarchy appInfo = appView.appInfo();
GraphLens lens = appView.graphLens();
// Create a mapping from each required type to the set of required members on that type.
Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
noShrinking.forEachClass(
type -> {
DexType rewrittenType = lens.lookupType(type);
assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType)
: "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned";
requiredMembersPerType.computeIfAbsent(rewrittenType, key -> Sets.newIdentityHashSet());
});
noShrinking.forEachMember(
member -> {
DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member);
assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember)
: "Expected reference `" + rewrittenMember.toSourceString() + "` to be pinned";
requiredMembersPerType
.computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet())
.add(rewrittenMember);
});
// Run through each class in the program and check that it has members it must have.
for (DexProgramClass clazz : appView.appInfo().classes()) {
Set<DexMember<?, ?>> requiredMembers =
requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of());
Set<DexField> fields = null;
Set<DexMethod> methods = null;
for (DexMember<?, ?> requiredMember : requiredMembers) {
if (requiredMember.isDexField()) {
DexField requiredField = requiredMember.asDexField();
if (fields == null) {
// Create a Set of the fields to avoid quadratic behavior.
fields =
Streams.stream(clazz.fields())
.map(DexEncodedField::getReference)
.collect(Collectors.toSet());
}
assert fields.contains(requiredField)
: "Expected field `"
+ requiredField.toSourceString()
+ "` from the root set to be present";
} else {
DexMethod requiredMethod = requiredMember.asDexMethod();
if (methods == null) {
// Create a Set of the methods to avoid quadratic behavior.
methods =
Streams.stream(clazz.methods())
.map(DexEncodedMethod::getReference)
.collect(Collectors.toSet());
}
assert methods.contains(requiredMethod)
: "Expected method `"
+ requiredMethod.toSourceString()
+ "` from the root set to be present";
}
}
requiredMembersPerType.remove(clazz.type);
}
// If the map is non-empty, then a type in the root set was not in the application.
if (!requiredMembersPerType.isEmpty()) {
DexType type = requiredMembersPerType.keySet().iterator().next();
DexClass clazz = appView.definitionFor(type);
assert clazz == null || clazz.isProgramClass()
: "Unexpected library type in root set: `" + type + "`";
assert requiredMembersPerType.isEmpty()
: "Expected type `" + type.toSourceString() + "` to be present";
}
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("RootSet");
builder.append("\nnoShrinking: " + noShrinking.size());
builder.append("\nnoObfuscation: " + noObfuscation.size());
builder.append("\nreasonAsked: " + reasonAsked.size());
builder.append("\ncheckDiscarded: " + checkDiscarded.size());
builder.append("\nnoSideEffects: " + noSideEffects.size());
builder.append("\nassumedValues: " + assumedValues.size());
builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
builder.append("\nifRules: " + ifRules.size());
return builder.toString();
}
public static RootSetBuilder builder(
AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
return new RootSetBuilder(appView, subtypingInfo);
}
public static RootSetBuilder builder(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
Iterable<? extends ProguardConfigurationRule> rules) {
return new RootSetBuilder(appView, subtypingInfo, rules);
}
}
static class ConsequentRootSetBuilder extends RootSetBuilder {
private final Enqueuer enqueuer;
private ConsequentRootSetBuilder(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
Enqueuer enqueuer) {
super(appView, subtypingInfo, null);
this.enqueuer = enqueuer;
}
@Override
void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
if (enqueuer.getMode().isInitialTreeShaking()
&& annotationMatchResult.isConcreteAnnotationMatchResult()) {
enqueuer.retainAnnotationForFinalTreeShaking(
annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations());
}
}
}
// A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
// keep rules.
public static class ConsequentRootSet extends RootSetBase {
ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexType> neverClassInline,
MutableItemsWithRules noShrinking,
MutableItemsWithRules softPinned,
Set<DexReference> noObfuscation,
Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
super(
neverInline,
neverInlineDueToSingleCaller,
neverClassInline,
noShrinking,
softPinned,
noObfuscation,
dependentNoShrinking,
dependentSoftPinned,
dependentKeepClassCompatRule,
delayedRootSetActionItems);
}
static ConsequentRootSetBuilder builder(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
Enqueuer enqueuer) {
return new ConsequentRootSetBuilder(appView, subtypingInfo, enqueuer);
}
}
public static class MainDexRootSetBuilder extends RootSetBuilder {
private MainDexRootSetBuilder(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
Iterable<? extends ProguardConfigurationRule> rules) {
super(appView, subtypingInfo, rules);
}
@Override
public MainDexRootSet build(ExecutorService executorService) throws ExecutionException {
// Call the super builder to have if-tests calculated automatically.
RootSet rootSet = super.build(executorService);
return new MainDexRootSet(
rootSet.noShrinking,
rootSet.reasonAsked,
rootSet.checkDiscarded,
rootSet.dependentNoShrinking,
rootSet.ifRules,
rootSet.delayedRootSetActionItems);
}
}
public static class MainDexRootSet extends RootSet {
public MainDexRootSet(
MutableItemsWithRules noShrinking,
ImmutableList<DexReference> reasonAsked,
ImmutableList<DexReference> checkDiscarded,
Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
Set<ProguardIfRule> ifRules,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
super(
noShrinking,
new MutableItemsWithRules(),
Collections.emptySet(),
reasonAsked,
checkDiscarded,
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
PredicateSet.empty(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptyMap(),
Collections.emptyMap(),
Collections.emptyMap(),
dependentNoShrinking,
Collections.emptyMap(),
Collections.emptyMap(),
Collections.emptySet(),
ifRules,
delayedRootSetActionItems);
}
@Override
void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
if (addNoShrinking) {
noShrinking.addAll(consequentRootSet.noShrinking);
}
addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
consequentRootSet.dependentKeepClassCompatRule.forEach(
(type, rules) ->
dependentKeepClassCompatRule
.computeIfAbsent(type, k -> new HashSet<>())
.addAll(rules));
}
public static MainDexRootSetBuilder builder(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
Iterable<? extends ProguardConfigurationRule> rules) {
return new MainDexRootSetBuilder(appView, subtypingInfo, rules);
}
@Override
void shouldNotBeMinified(DexReference reference) {
// Do nothing.
}
public MainDexRootSet rewrittenWithLens(GraphLens graphLens) {
if (graphLens.isIdentityLens()) {
return this;
}
Map<DexReference, MutableItemsWithRules> rewrittenDependent = new IdentityHashMap<>();
dependentNoShrinking.forEach(
(reference, rules) -> {
// Rewriting a reference can result in us having to merge items with rules.
rewriteAndApplyIfNotPrimitiveType(
graphLens,
reference,
rewritten -> {
MutableItemsWithRules rewrittenRules =
rewrittenDependent.computeIfAbsent(
graphLens.lookupReference(reference),
rewrittenRef -> new MutableItemsWithRules());
rewrittenRules.addAll(rules.rewrittenWithLens(graphLens));
});
});
ImmutableList.Builder<DexReference> rewrittenCheckDiscarded = ImmutableList.builder();
checkDiscarded.forEach(
reference ->
rewriteAndApplyIfNotPrimitiveType(
graphLens, reference, rewrittenCheckDiscarded::add));
ImmutableList.Builder<DexReference> rewrittenReasonAsked = ImmutableList.builder();
reasonAsked.forEach(
reference ->
rewriteAndApplyIfNotPrimitiveType(graphLens, reference, rewrittenReasonAsked::add));
// TODO(b/164019179): If rules can now reference dead items. These should be pruned or
// rewritten
ifRules.forEach(ProguardIfRule::canReferenceDeadTypes);
// All delayed root set actions should have been processed at this point.
assert delayedRootSetActionItems.isEmpty();
return new MainDexRootSet(
noShrinking.rewrittenWithLens(graphLens),
rewrittenReasonAsked.build(),
rewrittenCheckDiscarded.build(),
rewrittenDependent,
ifRules,
delayedRootSetActionItems);
}
public MainDexRootSet withoutPrunedItems(PrunedItems prunedItems) {
if (prunedItems.isEmpty()) {
return this;
}
Map<DexReference, MutableItemsWithRules> prunedDependent = new IdentityHashMap<>();
dependentNoShrinking.forEach(
(ref, rules) -> {
if (prunedItems.getRemovedClasses().contains(ref.getContextType())) {
// The dependent reference has been pruned and cannot lead to any additional items
return;
}
prunedDependent.put(ref, rules.prune(prunedItems.getRemovedClasses()));
});
// TODO(b/164019179): If rules can now reference dead items. These should be pruned or
// rewritten
ifRules.forEach(ProguardIfRule::canReferenceDeadTypes);
// All delayed root set actions should have been processed at this point.
assert delayedRootSetActionItems.isEmpty();
return new MainDexRootSet(
noShrinking.prune(prunedItems.getRemovedClasses()),
reasonAsked,
checkDiscarded,
prunedDependent,
ifRules,
delayedRootSetActionItems);
}
}
}