blob: 40152ababb753e7d9f6d627b30a51553adadc9e6 [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.GraphLense.rewriteMutableReferenceKeys;
import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexLibraryClass;
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.GraphLense;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
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.Iterables;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class RootSetBuilder {
private final AppView<?> appView;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
private final Map<DexReference, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
private final Set<DexReference> noOptimization = Sets.newIdentityHashSet();
private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
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> keepParametersWithConstantValue = Sets.newIdentityHashSet();
private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
private final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects = new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
private final InternalOptions options;
private final DexStringCache dexStringCache = new DexStringCache();
private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
public RootSetBuilder(
AppView<?> appView,
DexApplication application,
Iterable<? extends ProguardConfigurationRule> rules) {
this.appView = appView;
this.application = application.asDirect();
this.rules = rules;
this.options = appView.options();
}
RootSetBuilder(AppView<?> appView, Collection<ProguardIfRule> ifRules) {
this(appView, appView.appInfo().app(), ifRules);
}
// 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;
}
if (!satisfyAnnotation(rule, clazz)) {
return;
}
// 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)) {
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);
markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, false);
break;
case KEEP_CLASSES_WITH_MEMBERS:
if (!allRulesSatisfied(memberKeepRules, clazz)) {
break;
}
// fallthrough;
case KEEP:
markClass(clazz, rule);
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);
markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, false);
break;
case CONDITIONAL:
throw new Unreachable("-if rule will be evaluated separately, not here.");
}
} else 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);
} else {
preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
markMatchingMethods(clazz, memberKeepRules, rule, preconditionSupplier);
}
} else if (rule instanceof ProguardWhyAreYouKeepingRule) {
markClass(clazz, rule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
} else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
|| rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
if (appView.appInfo().hasSubtyping()) {
markMatchingOverriddenMethods(
appView.appInfo().withSubtyping(), clazz, memberKeepRules, rule, null, true);
}
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
} else if (rule instanceof ClassMergingRule) {
if (allRulesSatisfied(memberKeepRules, clazz)) {
markClass(clazz, rule);
}
} else if (rule instanceof InlineRule
|| rule instanceof ConstantArgumentRule
|| rule instanceof UnusedArgumentRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ClassInlineRule) {
if (allRulesSatisfied(memberKeepRules, clazz)) {
markClass(clazz, rule);
}
} else if (rule instanceof MemberValuePropagationRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
} else if (rule instanceof ProguardAssumeValuesRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
} else {
assert rule instanceof ProguardIdentifierNameStringRule;
markMatchingFields(clazz, memberKeepRules, rule, null);
markMatchingMethods(clazz, memberKeepRules, rule, null);
}
}
}
private 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);
}
}
} else {
futures.add(
executorService.submit(
() -> {
for (DexProgramClass clazz : application.classes()) {
process(clazz, rule, ifRule);
}
if (rule.applyToNonProgramClasses()) {
for (DexLibraryClass clazz : application.libraryClasses()) {
process(clazz, rule, ifRule);
}
}
}));
}
}
public RootSet run(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();
}
return new RootSet(
noShrinking,
noOptimization,
noObfuscation,
ImmutableList.copyOf(reasonAsked.values()),
ImmutableList.copyOf(checkDiscarded.values()),
alwaysInline,
forceInline,
neverInline,
keepParametersWithConstantValue,
keepUnusedArguments,
neverClassInline,
neverMerge,
neverPropagateValue,
mayHaveSideEffects,
noSideEffects,
assumedValues,
dependentNoShrinking,
identifierNameStrings,
ifRules);
}
IfRuleEvaluator getIfRuleEvaluator(
Set<DexEncodedField> liveFields,
Set<DexEncodedMethod> liveMethods,
Set<DexEncodedMethod> targetedMethods,
ExecutorService executorService) {
return new IfRuleEvaluator(liveFields, liveMethods, targetedMethods, executorService);
}
class IfRuleEvaluator {
private final Set<DexEncodedField> liveFields;
private final Set<DexEncodedMethod> liveMethods;
private final Set<DexEncodedMethod> targetedMethods;
private final ExecutorService executorService;
private final List<Future<?>> futures = new ArrayList<>();
public IfRuleEvaluator(
Set<DexEncodedField> liveFields,
Set<DexEncodedMethod> liveMethods,
Set<DexEncodedMethod> targetedMethods,
ExecutorService executorService) {
this.liveFields = liveFields;
this.liveMethods = liveMethods;
this.targetedMethods = targetedMethods;
this.executorService = executorService;
}
public ConsequentRootSet run(Set<DexType> liveTypes) throws ExecutionException {
application.timing.begin("Find consequent items for -if rules...");
try {
if (rules != null) {
for (ProguardConfigurationRule rule : rules) {
assert rule instanceof ProguardIfRule;
ProguardIfRule ifRule = (ProguardIfRule) rule;
// Depending on which types that trigger the -if rule, the application of the subsequent
// -keep rule may vary (due to back references). So, we need to try all pairs of -if
// rule and live types.
for (DexType type : liveTypes) {
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
continue;
}
// Check if the class matches the if-rule.
evaluateIfRule(ifRule, clazz, clazz);
// Check if one of the types that have been merged into `clazz` satisfies the if-rule.
if (options.enableVerticalClassMerging && appView.verticallyMergedClasses() != null) {
for (DexType sourceType : appView.verticallyMergedClasses().getSourcesFor(type)) {
// Note that, although `sourceType` has been merged into `type`, the dex class for
// `sourceType` is still available until the second round of tree shaking. This
// way
// we can still retrieve the access flags of `sourceType`.
DexClass sourceClass = appView.definitionFor(sourceType);
assert sourceClass != null;
evaluateIfRule(ifRule, sourceClass, clazz);
}
}
}
}
ThreadUtils.awaitFutures(futures);
}
} finally {
application.timing.end();
}
return new ConsequentRootSet(
neverInline,
neverClassInline,
noShrinking,
noOptimization,
noObfuscation,
dependentNoShrinking);
}
/**
* Determines if `sourceClass` satisfies the given if-rule. If `sourceClass` has not been merged
* into another class, then `targetClass` is the same as `sourceClass`. Otherwise, `targetClass`
* denotes the class that `sourceClass` has been merged into.
*/
private void evaluateIfRule(ProguardIfRule rule, DexClass sourceClass, DexClass targetClass) {
if (!satisfyClassType(rule, sourceClass)) {
return;
}
if (!satisfyAccessFlag(rule, sourceClass)) {
return;
}
if (!satisfyAnnotation(rule, sourceClass)) {
return;
}
if (!rule.getClassNames().matches(sourceClass.type)) {
return;
}
if (rule.hasInheritanceClassName()) {
// Note that, in presence of vertical class merging, we check if the resulting class
// (i.e., the target class) satisfies the implements/extends-matcher.
if (!satisfyInheritanceRule(targetClass, rule)) {
// Try another live type since the current one doesn't satisfy the inheritance rule.
return;
}
}
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
if (memberKeepRules.isEmpty()) {
materializeIfRule(rule);
return;
}
Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet();
Iterables.addAll(
filteredMembers,
targetClass.fields(
f ->
liveFields.contains(f)
&& appView.graphLense().getOriginalFieldSignature(f.field).holder
== sourceClass.type));
Iterables.addAll(
filteredMembers,
targetClass.methods(
m ->
(liveMethods.contains(m) || targetedMethods.contains(m))
&& appView.graphLense().getOriginalMethodSignature(m.method).holder
== sourceClass.type));
// If the number of member rules to hold is more than live members, we can't make it.
if (filteredMembers.size() < memberKeepRules.size()) {
return;
}
// Depending on which members trigger the -if rule, the application of the subsequent
// -keep rule may vary (due to back references). So, we need to try literally all
// combinations of live members. But, we can at least limit the number of elements per
// combination as the size of member rules to satisfy.
Sets.combinations(filteredMembers, memberKeepRules.size())
.forEach(
combination -> {
Collection<DexEncodedField> fieldsInCombination =
DexDefinition.filterDexEncodedField(combination.stream())
.collect(Collectors.toList());
Collection<DexEncodedMethod> methodsInCombination =
DexDefinition.filterDexEncodedMethod(combination.stream())
.collect(Collectors.toList());
// Member rules are combined as AND logic: if found unsatisfied member rule, this
// combination of live members is not a good fit.
boolean satisfied =
memberKeepRules.stream()
.allMatch(
memberRule ->
ruleSatisfiedByFields(memberRule, fieldsInCombination)
|| ruleSatisfiedByMethods(memberRule, methodsInCombination));
if (satisfied) {
materializeIfRule(rule);
}
});
}
private void materializeIfRule(ProguardIfRule rule) {
ProguardIfRule materializedRule = rule.materialize();
// We need to abort class inlining of classes that could be matched by the condition of this
// -if rule.
ClassInlineRule neverClassInlineRuleForCondition =
materializedRule.neverClassInlineRuleForCondition();
if (neverClassInlineRuleForCondition != null) {
runPerRule(executorService, futures, neverClassInlineRuleForCondition, materializedRule);
}
// If the condition of the -if rule has any members, then we need to keep these members to
// ensure that the subsequent rule will be applied again in the second round of tree
// shaking.
InlineRule neverInlineRuleForCondition = materializedRule.neverInlineRuleForCondition();
if (neverInlineRuleForCondition != null) {
runPerRule(executorService, futures, neverInlineRuleForCondition, materializedRule);
}
// Keep whatever is required by the -if rule.
runPerRule(executorService, futures, materializedRule.subsequentRule, materializedRule);
}
}
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) {
Set<Wrapper<DexMethod>> methodsMarked =
options.forceProguardCompatibility || rule instanceof ProguardAssumeNoSideEffectRule
? null : new HashSet<>();
DexClass startingClass = clazz;
while (clazz != null) {
if (!includeLibraryClasses && clazz.isNotProgramClass()) {
return;
}
// In compat mode traverse all direct methods in the hierarchy.
if (clazz == startingClass || options.forceProguardCompatibility) {
clazz
.directMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
});
}
clazz
.virtualMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
});
clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
}
}
private void markMatchingOverriddenMethods(
AppInfoWithSubtyping appInfoWithSubtyping,
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
boolean onlyIncludeProgramClasses) {
Set<DexType> visited = new HashSet<>();
Deque<DexType> worklist = new ArrayDeque<>();
// Intentionally skip the current `clazz`, assuming it's covered by markMatchingVisibleMethods.
worklist.addAll(appInfoWithSubtyping.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);
});
worklist.addAll(appInfoWithSubtyping.allImmediateSubtypes(currentClazz.type));
}
}
private void markMatchingMethods(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
clazz.forEachMethod(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, null, rule, precondition);
});
}
private void markMatchingVisibleFields(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
boolean includeLibraryClasses) {
while (clazz != null) {
if (!includeLibraryClasses && clazz.isNotProgramClass()) {
return;
}
clazz.forEachField(
field -> {
DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
markField(field, memberKeepRules, rule, precondition);
});
clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
}
}
private void markMatchingFields(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
clazz.forEachField(
field -> {
DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
markField(field, memberKeepRules, rule, precondition);
});
}
// TODO(b/67934426): Test this code.
public static void writeSeeds(
AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
for (DexReference seed : appInfo.getPinnedItems()) {
if (seed.isDexType()) {
if (include.test(seed.asDexType())) {
out.println(seed.toSourceString());
}
} else if (seed.isDexField()) {
DexField field = seed.asDexField();
if (include.test(field.holder)) {
out.println(
field.holder.toSourceString()
+ ": "
+ field.type.toSourceString()
+ " "
+ field.name.toSourceString());
}
} else {
assert seed.isDexMethod();
DexMethod method = seed.asDexMethod();
if (!include.test(method.holder)) {
continue;
}
out.print(method.holder.toSourceString() + ": ");
DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
if (encodedMethod.accessFlags.isConstructor()) {
if (encodedMethod.accessFlags.isStatic()) {
out.print(Constants.CLASS_INITIALIZER_NAME);
} else {
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();
}
private boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
}
private static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) {
return rule.getClassAccessFlags().containsAll(clazz.accessFlags)
&& rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
}
private static boolean satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
return containsAnnotation(rule.getClassAnnotation(), clazz.annotations);
}
private boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
boolean extendsExpected = satisfyExtendsRule(clazz, rule);
boolean implementsExpected = false;
if (!extendsExpected) {
implementsExpected = satisfyImplementsRule(clazz, rule);
}
if (extendsExpected || implementsExpected) {
// Warn if users got it wrong, but only warn once. Also, only warn if rule is actually
// specific (there is no correct way to write "keep class X that extends or implements from
// a class or interface in package Y").
if (rule.getInheritanceClassName().matchesSpecificType()) {
if (rule.getInheritanceIsExtends()) {
if (implementsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
options.reporter.warning(
new StringDiagnostic(
"The rule `" + rule + "` uses extends but actually matches implements."));
}
} else if (extendsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
options.reporter.warning(
new StringDiagnostic(
"The rule `" + rule + "` uses implements but actually matches extends."));
}
}
return true;
}
return false;
}
private 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);
}
private 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)
&& containsAnnotation(rule.getInheritanceAnnotation(), clazz.annotations)) {
return true;
}
type = clazz.superType;
}
return false;
}
private 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)
&& containsAnnotation(rule.getInheritanceAnnotation(), ifaceClass.annotations)) {
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());
}
private boolean ruleSatisfiedByMethods(
ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
if (rule.getRuleType().includesMethods()) {
for (DexEncodedMethod method : methods) {
if (rule.matches(method, appView, dexStringCache)) {
return true;
}
}
}
return false;
}
private boolean ruleSatisfiedByMethods(ProguardMemberRule rule, DexEncodedMethod[] methods) {
return ruleSatisfiedByMethods(rule, Arrays.asList(methods));
}
private boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
if (rule.getRuleType().includesFields()) {
for (DexEncodedField field : fields) {
if (rule.matches(field, appView, dexStringCache)) {
return true;
}
}
}
return false;
}
private boolean ruleSatisfiedByFields(ProguardMemberRule rule, DexEncodedField[] fields) {
return ruleSatisfiedByFields(rule, Arrays.asList(fields));
}
static boolean containsAnnotation(ProguardTypeMatcher classAnnotation,
DexAnnotationSet annotations) {
if (classAnnotation == null) {
return true;
}
if (annotations.isEmpty()) {
return false;
}
for (DexAnnotation annotation : annotations.annotations) {
if (classAnnotation.matches(annotation.annotation.type)) {
return true;
}
}
return false;
}
private void markMethod(
DexEncodedMethod method,
Collection<ProguardMemberRule> rules,
Set<Wrapper<DexMethod>> methodsMarked,
ProguardConfigurationRule context,
DexDefinition precondition) {
if (methodsMarked != null
&& methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
// Ignore, method is overridden in sub class.
return;
}
for (ProguardMemberRule rule : rules) {
if (rule.matches(method, appView, 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.method));
}
addItemToSets(method, context, rule, precondition);
}
}
}
private void markField(
DexEncodedField field,
Collection<ProguardMemberRule> rules,
ProguardConfigurationRule context,
DexDefinition precondition) {
for (ProguardMemberRule rule : rules) {
if (rule.matches(field, appView, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context,
rule);
}
addItemToSets(field, context, rule, precondition);
}
}
}
private void markClass(DexClass clazz, ProguardConfigurationRule rule) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking class `%s` due to `%s`.", clazz.type, rule);
}
addItemToSets(clazz, rule, null, null);
}
private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRule 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.toReference(), x -> new IdentityHashMap<>())
.computeIfAbsent(type, k -> new HashSet<>())
.add(context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
noObfuscation.add(type);
}
private void includeDescriptorClasses(DexDefinition item, ProguardKeepRule context) {
if (item.isDexEncodedMethod()) {
DexMethod method = item.asDexEncodedMethod().method;
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().field;
includeDescriptor(item, field.type, context);
} else {
assert item.isDexClass();
}
}
private synchronized void addItemToSets(
DexDefinition item,
ProguardConfigurationRule context,
ProguardMemberRule rule,
DexDefinition precondition) {
if (context instanceof ProguardKeepRule) {
if (item.isDexEncodedMethod()) {
DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
if (options.isGeneratingDex()
&& encodedMethod.method.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.method.holder);
if (holder != null && holder.isInterface()) {
if (rule.isSpecific()) {
options.reporter.warning(
new StringDiagnostic(
"The rule `" + rule + "` is ignored because the targeting interface method `"
+ encodedMethod.method.toSourceString() + "` will be desugared."));
}
return;
}
}
}
ProguardKeepRule keepRule = (ProguardKeepRule) context;
ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
if (!modifiers.allowsShrinking) {
if (precondition != null) {
dependentNoShrinking
.computeIfAbsent(precondition.toReference(), x -> new IdentityHashMap<>())
.computeIfAbsent(item.toReference(), i -> new HashSet<>())
.add(keepRule);
} else {
noShrinking.computeIfAbsent(item.toReference(), i -> new HashSet<>()).add(keepRule);
}
}
if (!modifiers.allowsOptimization) {
noOptimization.add(item.toReference());
}
if (!modifiers.allowsObfuscation) {
noObfuscation.add(item.toReference());
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
}
} else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
mayHaveSideEffects.put(item.toReference(), rule);
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
noSideEffects.put(item.toReference(), rule);
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
reasonAsked.computeIfAbsent(item.toReference(), i -> i);
} else if (context instanceof ProguardAssumeValuesRule) {
assumedValues.put(item.toReference(), rule);
} else if (context instanceof ProguardCheckDiscardRule) {
checkDiscarded.computeIfAbsent(item.toReference(), i -> i);
} else if (context instanceof InlineRule) {
if (item.isDexEncodedMethod()) {
switch (((InlineRule) context).getType()) {
case ALWAYS:
alwaysInline.add(item.asDexEncodedMethod().method);
break;
case FORCE:
forceInline.add(item.asDexEncodedMethod().method);
break;
case NEVER:
neverInline.add(item.asDexEncodedMethod().method);
break;
default:
throw new Unreachable();
}
}
} else if (context instanceof ClassInlineRule) {
switch (((ClassInlineRule) context).getType()) {
case NEVER:
if (item.isDexClass()) {
neverClassInline.add(item.asDexClass().type);
}
break;
default:
throw new Unreachable();
}
} else if (context instanceof ClassMergingRule) {
switch (((ClassMergingRule) context).getType()) {
case NEVER:
if (item.isDexClass()) {
neverMerge.add(item.asDexClass().type);
}
break;
default:
throw new Unreachable();
}
} 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().field);
}
} else if (item.isDexEncodedMethod()) {
DexEncodedMethod method = item.asDexEncodedMethod();
if (method.isProgramMethod(appView)) {
neverPropagateValue.add(item.asDexEncodedMethod().method);
}
}
break;
default:
throw new Unreachable();
}
} else if (context instanceof ProguardIdentifierNameStringRule) {
if (item.isDexEncodedField()) {
identifierNameStrings.add(item.asDexEncodedField().field);
} else if (item.isDexEncodedMethod()) {
identifierNameStrings.add(item.asDexEncodedMethod().method);
}
} else if (context instanceof ConstantArgumentRule) {
if (item.isDexEncodedMethod()) {
keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
}
} else if (context instanceof UnusedArgumentRule) {
if (item.isDexEncodedMethod()) {
keepUnusedArguments.add(item.asDexEncodedMethod().method);
}
}
}
public static class RootSet {
public final Map<DexReference, Set<ProguardKeepRule>> noShrinking;
public final Set<DexReference> noOptimization;
private final Set<DexReference> noObfuscation;
public final ImmutableList<DexReference> reasonAsked;
public final ImmutableList<DexReference> checkDiscarded;
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> neverInline;
public final Set<DexMethod> keepConstantArguments;
public final Set<DexMethod> keepUnusedArguments;
public final Set<DexType> neverClassInline;
public final Set<DexType> neverMerge;
public final Set<DexReference> neverPropagateValue;
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
public final Map<DexReference, ProguardMemberRule> noSideEffects;
public final Map<DexReference, ProguardMemberRule> assumedValues;
private final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>>
dependentNoShrinking;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
private RootSet(
Map<DexReference, Set<ProguardKeepRule>> noShrinking,
Set<DexReference> noOptimization,
Set<DexReference> noObfuscation,
ImmutableList<DexReference> reasonAsked,
ImmutableList<DexReference> checkDiscarded,
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
Set<DexMethod> keepConstantArguments,
Set<DexMethod> keepUnusedArguments,
Set<DexType> neverClassInline,
Set<DexType> neverMerge,
Set<DexReference> neverPropagateValue,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules) {
this.noShrinking = noShrinking;
this.noOptimization = noOptimization;
this.noObfuscation = noObfuscation;
this.reasonAsked = reasonAsked;
this.checkDiscarded = checkDiscarded;
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.forceInline = Collections.unmodifiableSet(forceInline);
this.neverInline = neverInline;
this.keepConstantArguments = keepConstantArguments;
this.keepUnusedArguments = keepUnusedArguments;
this.neverClassInline = neverClassInline;
this.neverMerge = Collections.unmodifiableSet(neverMerge);
this.neverPropagateValue = neverPropagateValue;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
this.dependentNoShrinking = dependentNoShrinking;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
}
private RootSet(RootSet previous, GraphLense lense) {
this.noShrinking = rewriteMutableReferenceKeys(previous.noShrinking, lense::lookupReference);
this.noOptimization = lense.rewriteMutableReferencesConservatively(previous.noOptimization);
this.noObfuscation = lense.rewriteMutableReferencesConservatively(previous.noObfuscation);
this.reasonAsked = lense.rewriteReferencesConservatively(previous.reasonAsked);
this.checkDiscarded = lense.rewriteReferencesConservatively(previous.checkDiscarded);
this.alwaysInline = lense.rewriteMethodsConservatively(previous.alwaysInline);
this.forceInline = lense.rewriteMethodsConservatively(previous.forceInline);
this.neverInline = lense.rewriteMutableMethodsConservatively(previous.neverInline);
this.keepConstantArguments =
lense.rewriteMutableMethodsConservatively(previous.keepConstantArguments);
this.keepUnusedArguments =
lense.rewriteMutableMethodsConservatively(previous.keepUnusedArguments);
this.neverClassInline = lense.rewriteMutableTypesConservatively(previous.neverClassInline);
this.neverMerge = lense.rewriteTypesConservatively(previous.neverMerge);
this.neverPropagateValue =
lense.rewriteMutableReferencesConservatively(previous.neverPropagateValue);
this.mayHaveSideEffects =
rewriteMutableReferenceKeys(previous.mayHaveSideEffects, lense::lookupReference);
this.noSideEffects =
rewriteMutableReferenceKeys(previous.noSideEffects, lense::lookupReference);
this.assumedValues =
rewriteMutableReferenceKeys(previous.assumedValues, lense::lookupReference);
this.dependentNoShrinking =
rewriteDependentReferenceKeys(previous.dependentNoShrinking, lense::lookupReference);
this.identifierNameStrings =
lense.rewriteReferencesConservatively(previous.identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(previous.ifRules);
}
public RootSet rewrittenWithLense(GraphLense lense) {
return new RootSet(this, lense);
}
private static <T extends DexReference, S> Map<T, Map<T, S>> rewriteDependentReferenceKeys(
Map<T, Map<T, S>> original, Function<T, T> rewrite) {
Map<T, Map<T, S>> result = new IdentityHashMap<>();
for (T item : original.keySet()) {
result.put(rewrite.apply(item), rewriteReferenceKeys(original.get(item), rewrite));
}
return result;
}
void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
neverInline.addAll(consequentRootSet.neverInline);
neverClassInline.addAll(consequentRootSet.neverClassInline);
noOptimization.addAll(consequentRootSet.noOptimization);
noObfuscation.addAll(consequentRootSet.noObfuscation);
addDependentItems(consequentRootSet.dependentNoShrinking);
}
// Add dependent items that depend on -if rules.
private void addDependentItems(
Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentItems) {
dependentItems.forEach(
(reference, dependence) ->
dependentNoShrinking
.computeIfAbsent(reference, x -> new IdentityHashMap<>())
.putAll(dependence));
}
Map<DexReference, Set<ProguardKeepRule>> getDependentItems(DexDefinition item) {
return Collections.unmodifiableMap(
dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
}
public void copy(DexReference original, DexReference rewritten) {
if (noShrinking.containsKey(original)) {
noShrinking.put(rewritten, noShrinking.get(original));
}
if (noOptimization.contains(original)) {
noOptimization.add(rewritten);
}
if (noObfuscation.contains(original)) {
noObfuscation.add(rewritten);
}
if (noSideEffects.containsKey(original)) {
noSideEffects.put(rewritten, noSideEffects.get(original));
}
if (assumedValues.containsKey(original)) {
assumedValues.put(rewritten, assumedValues.get(original));
}
}
public void prune(DexReference reference) {
noShrinking.remove(reference);
noOptimization.remove(reference);
noObfuscation.remove(reference);
noSideEffects.remove(reference);
assumedValues.remove(reference);
}
public void move(DexReference original, DexReference rewritten) {
copy(original, rewritten);
prune(original);
}
void shouldNotBeMinified(DexReference reference) {
noObfuscation.add(reference);
}
public boolean mayBeMinified(DexReference reference, AppView<?> appView) {
return !mayNotBeMinified(reference, appView);
}
public boolean mayNotBeMinified(DexReference reference, AppView<?> appView) {
if (reference.isDexType()) {
return noObfuscation.contains(
appView.graphLense().getOriginalType(reference.asDexType()));
} else if (reference.isDexMethod()) {
return noObfuscation.contains(
appView.graphLense().getOriginalMethodSignature(reference.asDexMethod()));
} else {
assert reference.isDexField();
return noObfuscation.contains(
appView.graphLense().getOriginalFieldSignature(reference.asDexField()));
}
}
public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
for (DexReference reference : noShrinking.keySet()) {
if (reference.isDexField()) {
DexField field = reference.asDexField();
DexEncodedField encodedField = appInfo.definitionFor(field);
if (encodedField != null
&& (encodedField.isStatic() || isKeptDirectlyOrIndirectly(field.holder, 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) {
for (DexReference reference : noShrinking.keySet()) {
if (reference.isDexMethod()) {
DexMethod method = reference.asDexMethod();
assert appInfo.targetedMethods.contains(method)
: "Expected kept method `" + method.toSourceString() + "` to be targeted";
DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
if (!encodedMethod.accessFlags.isAbstract()
&& isKeptDirectlyOrIndirectly(method.holder, appInfo)) {
assert appInfo.liveMethods.contains(method)
: "Expected non-abstract kept method `"
+ method.toSourceString()
+ "` to be live";
}
}
}
return true;
}
public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
for (DexReference reference : noShrinking.keySet()) {
if (reference.isDexType()) {
DexType type = reference.asDexType();
assert appInfo.liveTypes.contains(type)
: "Expected kept type `" + type.toSourceString() + "` to be live";
}
}
return true;
}
private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
if (noShrinking.containsKey(type)) {
return true;
}
DexClass clazz = appInfo.definitionFor(type);
if (clazz == null) {
return false;
}
if (clazz.superType != null) {
return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
}
return false;
}
public boolean verifyKeptItemsAreKept(DexApplication application, AppInfo appInfo) {
Set<DexReference> pinnedItems =
appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
// Create a mapping from each required type to the set of required members on that type.
Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>();
for (DexReference reference : noShrinking.keySet()) {
// Check that `pinnedItems` is a super set of the root set.
assert pinnedItems == null || pinnedItems.contains(reference)
: "Expected reference `" + reference.toSourceString() + "` to be pinned";
if (reference.isDexType()) {
DexType type = reference.asDexType();
requiredReferencesPerType.putIfAbsent(type, Sets.newIdentityHashSet());
} else {
assert reference.isDexField() || reference.isDexMethod();
DexType holder =
reference.isDexField()
? reference.asDexField().holder
: reference.asDexMethod().holder;
requiredReferencesPerType
.computeIfAbsent(holder, key -> Sets.newIdentityHashSet())
.add(reference);
}
}
// Run through each class in the program and check that it has members it must have.
for (DexProgramClass clazz : application.classes()) {
Set<DexReference> requiredReferences =
requiredReferencesPerType.getOrDefault(clazz.type, ImmutableSet.of());
Set<DexField> fields = null;
Set<DexMethod> methods = null;
for (DexReference requiredReference : requiredReferences) {
if (requiredReference.isDexField()) {
DexField requiredField = requiredReference.asDexField();
if (fields == null) {
// Create a Set of the fields to avoid quadratic behavior.
fields =
Streams.stream(clazz.fields())
.map(DexEncodedField::getKey)
.collect(Collectors.toSet());
}
assert fields.contains(requiredField)
: "Expected field `"
+ requiredField.toSourceString()
+ "` from the root set to be present";
} else if (requiredReference.isDexMethod()) {
DexMethod requiredMethod = requiredReference.asDexMethod();
if (methods == null) {
// Create a Set of the methods to avoid quadratic behavior.
methods =
Streams.stream(clazz.methods())
.map(DexEncodedMethod::getKey)
.collect(Collectors.toSet());
}
assert methods.contains(requiredMethod)
: "Expected method `"
+ requiredMethod.toSourceString()
+ "` from the root set to be present";
} else {
assert false;
}
}
requiredReferencesPerType.remove(clazz.type);
}
// If the map is non-empty, then a type in the root set was not in the application.
if (!requiredReferencesPerType.isEmpty()) {
DexType type = requiredReferencesPerType.keySet().iterator().next();
DexClass clazz = application.definitionFor(type);
assert clazz == null || clazz.isProgramClass()
: "Unexpected library type in root set: `" + type + "`";
assert requiredReferencesPerType.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("\nnoOptimization: " + noOptimization.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());
builder.append("\n\nNo Shrinking:");
noShrinking.keySet().stream()
.sorted(Comparator.comparing(DexReference::toSourceString))
.forEach(a -> builder
.append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
builder.append("\n");
return builder.toString();
}
}
// A partial RootSet that becomes live due to the enabled -if rule.
static class ConsequentRootSet {
final Set<DexMethod> neverInline;
final Set<DexType> neverClassInline;
final Map<DexReference, Set<ProguardKeepRule>> noShrinking;
final Set<DexReference> noOptimization;
final Set<DexReference> noObfuscation;
final Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking;
private ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
Map<DexReference, Set<ProguardKeepRule>> noShrinking,
Set<DexReference> noOptimization,
Set<DexReference> noObfuscation,
Map<DexReference, Map<DexReference, Set<ProguardKeepRule>>> dependentNoShrinking) {
this.neverInline = Collections.unmodifiableSet(neverInline);
this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
this.noShrinking = Collections.unmodifiableMap(noShrinking);
this.noOptimization = Collections.unmodifiableSet(noOptimization);
this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
this.dependentNoShrinking = Collections.unmodifiableMap(dependentNoShrinking);
}
}
}