blob: c860d833a74ef8a7623bfece4e262d0d49c92c90 [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 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.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.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
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.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
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.Sets;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
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 AppInfo appInfo;
private final DirectMappedDexApplication application;
private final Collection<ProguardConfigurationRule> rules;
private final Map<DexItem, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
private final Set<DexItem> noOptimization = Sets.newIdentityHashSet();
private final Set<DexItem> noObfuscation = Sets.newIdentityHashSet();
private final Set<DexItem> reasonAsked = Sets.newIdentityHashSet();
private final Set<DexItem> keepPackageName = Sets.newIdentityHashSet();
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
private final InternalOptions options;
private final DexStringCache dexStringCache = new DexStringCache();
private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
public RootSetBuilder(
AppInfo appInfo,
DexApplication application,
List<ProguardConfigurationRule> rules,
InternalOptions options) {
this.appInfo = appInfo;
this.application = application.asDirect();
this.rules = rules == null ? null : Collections.unmodifiableCollection(rules);
this.options = options;
}
RootSetBuilder(
AppInfo appInfo,
Set<ProguardIfRule> ifRules,
InternalOptions options) {
this.appInfo = appInfo;
this.application = appInfo.app.asDirect();
this.rules = Collections.unmodifiableCollection(ifRules);
this.options = options;
}
private boolean anySuperTypeMatches(
DexType type,
Function<DexType, DexClass> definitionFor,
ProguardTypeMatcher name,
ProguardTypeMatcher annotation) {
while (type != null) {
DexClass clazz = definitionFor.apply(type);
if (clazz == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
if (name.matches(clazz.type) && containsAnnotation(annotation, clazz.annotations)) {
return true;
}
type = clazz.superType;
}
return false;
}
private boolean anyImplementedInterfaceMatches(
DexClass clazz,
Function<DexType, DexClass> definitionFor,
ProguardTypeMatcher className,
ProguardTypeMatcher annotation) {
if (clazz == null) {
return false;
}
for (DexType iface : clazz.interfaces.values) {
DexClass ifaceClass = definitionFor.apply(iface);
if (ifaceClass == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
// TODO(herhut): Maybe it would be better to do this breadth first.
if ((className.matches(iface) && containsAnnotation(annotation, ifaceClass.annotations))
|| anyImplementedInterfaceMatches(ifaceClass, definitionFor, className, annotation)) {
return true;
}
}
if (clazz.superType == null) {
return false;
}
DexClass superClass = definitionFor.apply(clazz.superType);
if (superClass == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
return anyImplementedInterfaceMatches(superClass, definitionFor, className, annotation);
}
// Process a class with the keep rule.
private void process(
DexClass clazz,
ProguardConfigurationRule rule,
ProguardIfRule ifRule) {
if (rule.getClassType().matches(clazz) == rule.getClassTypeNegated()) {
return;
}
if (!rule.getClassAccessFlags().containsAll(clazz.accessFlags)) {
return;
}
if (!rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags)) {
return;
}
if (!containsAnnotation(rule.getClassAnnotation(), clazz.annotations)) {
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()) {
boolean extendsExpected =
anySuperTypeMatches(
clazz.superType,
application::definitionFor,
rule.getInheritanceClassName(),
rule.getInheritanceAnnotation());
boolean implementsExpected = false;
if (!extendsExpected) {
implementsExpected =
anyImplementedInterfaceMatches(
clazz,
application::definitionFor,
rule.getInheritanceClassName(),
rule.getInheritanceAnnotation());
}
if (!extendsExpected && !implementsExpected) {
return;
}
// Warn if users got it wrong, but only warn once.
if (extendsExpected && !rule.getInheritanceIsExtends()) {
if (rulesThatUseExtendsOrImplementsWrong.add(rule)) {
options.reporter.warning(
new StringDiagnostic(
"The rule `" + rule + "` uses implements but actually matches extends."));
}
} else if (implementsExpected && rule.getInheritanceIsExtends()) {
if (rulesThatUseExtendsOrImplementsWrong.add(rule)) {
options.reporter.warning(
new StringDiagnostic(
"The rule `" + rule + "` uses extends but actually matches implements."));
}
}
}
if (rule.getClassNames().matches(clazz.type)) {
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
if (rule instanceof ProguardKeepRule) {
switch (((ProguardKeepRule) rule).getType()) {
case KEEP_CLASS_MEMBERS: {
// If we're handling -if consequent part, that means precondition already met.
DexType precondition = ifRule != null ? null : clazz.type;
markMatchingVisibleMethods(clazz, memberKeepRules, rule, precondition);
markMatchingFields(clazz, memberKeepRules, rule, precondition);
break;
}
case KEEP_CLASSES_WITH_MEMBERS: {
if (!allRulesSatisfied(memberKeepRules, clazz)) {
break;
}
// fallthrough;
}
case KEEP: {
markClass(clazz, rule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
break;
}
case CONDITIONAL:
assert 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 {
markMatchingFields(clazz, memberKeepRules, rule, clazz.type);
markMatchingMethods(clazz, memberKeepRules, rule, clazz.type);
}
} else if (rule instanceof ProguardWhyAreYouKeepingRule
|| rule instanceof ProguardKeepPackageNamesRule) {
markClass(clazz, rule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAlwaysInlineRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ProguardAssumeValuesRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
} 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.applyToLibraryClasses()) {
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,
reasonAsked,
keepPackageName,
checkDiscarded,
alwaysInline,
noSideEffects,
assumedValues,
dependentNoShrinking,
identifierNameStrings,
ifRules);
}
ConsequentRootSet runForIfRules(
ExecutorService executorService,
Set<DexType> liveTypes,
Set<DexEncodedMethod> liveMethods,
Set<DexEncodedField> liveFields) throws ExecutionException {
application.timing.begin("Find consequent items for -if rules...");
Function<DexType, DexClass> definitionForWithLiveTypes = type -> {
DexClass clazz = appInfo.definitionFor(type);
if (clazz != null && liveTypes.contains(clazz.type)) {
return clazz;
}
return null;
};
try {
List<Future<?>> futures = new ArrayList<>();
if (rules != null) {
for (ProguardConfigurationRule rule : rules) {
assert rule instanceof ProguardIfRule;
ProguardIfRule ifRule = (ProguardIfRule) rule;
// Depending on which types 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 currentLiveType : liveTypes) {
if (ifRule.hasInheritanceClassName()) {
if (!satisfyInheritanceRule(currentLiveType, definitionForWithLiveTypes, ifRule)) {
// Try another live type since the current one doesn't satisfy the inheritance rule.
continue;
}
}
if (ifRule.getClassNames().matches(currentLiveType)) {
Collection<ProguardMemberRule> memberKeepRules = ifRule.getMemberRules();
if (memberKeepRules.isEmpty()) {
ProguardIfRule materializedRule = ifRule.materialize();
runPerRule(
executorService, futures, materializedRule.subsequentRule, materializedRule);
// No member rule to satisfy. Move on to the next live type.
continue;
}
Set<DexItem> filteredFields = liveFields.stream()
.filter(f -> f.field.getHolder() == currentLiveType)
.collect(Collectors.toSet());
Set<DexItem> filteredMethods = liveMethods.stream()
.filter(m -> m.method.getHolder() == currentLiveType)
.collect(Collectors.toSet());
// If the number of member rules to hold is more than live members, we can't make it.
if (filteredFields.size() + filteredMethods.size() < memberKeepRules.size()) {
continue;
}
// 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.
// TODO(b/79486261): Some of those are equivalent from the point of view of -if rule.
Set<Set<DexItem>> combinationsOfMembers = Sets.combinations(
Sets.union(filteredFields, filteredMethods), memberKeepRules.size());
for (Set<DexItem> combination : combinationsOfMembers) {
Set<DexEncodedField> fieldsInCombination = combination.stream()
.filter(item -> item instanceof DexEncodedField)
.map(item -> (DexEncodedField) item)
.collect(Collectors.toSet());
Set<DexEncodedMethod> methodsInCombination = combination.stream()
.filter(item -> item instanceof DexEncodedMethod)
.map(item -> (DexEncodedMethod) item)
.collect(Collectors.toSet());
// Member rules are combined as AND logic: if found unsatisfied member rule, this
// combination of live members is not a good fit.
boolean satisfied = true;
for (ProguardMemberRule memberRule : memberKeepRules) {
if (!ruleSatisfiedByFields(memberRule, fieldsInCombination)
&& !ruleSatisfiedByMethods(memberRule, methodsInCombination)) {
satisfied = false;
break;
}
}
if (satisfied) {
ProguardIfRule materializedRule = ifRule.materialize();
runPerRule(
executorService, futures, materializedRule.subsequentRule, materializedRule);
}
}
}
}
}
ThreadUtils.awaitFutures(futures);
}
} finally {
application.timing.end();
}
return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation);
}
private void markMatchingVisibleMethods(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>();
Arrays.stream(clazz.directMethods()).forEach(method ->
markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
while (clazz != null) {
Arrays.stream(clazz.virtualMethods()).forEach(method ->
markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
}
}
private void markMatchingMethods(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
Arrays.stream(clazz.directMethods()).forEach(method ->
markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
Arrays.stream(clazz.virtualMethods()).forEach(method ->
markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
}
private void markMatchingFields(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
clazz.forEachField(field -> markField(field, memberKeepRules, rule, onlyIfClassKept));
}
// TODO(67934426): Test this code.
public static void writeSeeds(
AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
for (DexItem seed : appInfo.getPinnedItems()) {
if (seed instanceof DexType) {
if (include.test((DexType) seed)) {
out.println(seed.toSourceString());
}
} else if (seed instanceof DexField) {
DexField field = ((DexField) seed);
if (include.test(field.clazz)) {
out.println(
field.clazz.toSourceString()
+ ": "
+ field.type.toSourceString()
+ " "
+ field.name.toSourceString());
}
} else if (seed instanceof DexMethod) {
DexMethod method = (DexMethod) seed;
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(")");
} else {
throw new Unreachable();
}
}
out.close();
}
private boolean satisfyInheritanceRule(
DexType type,
Function<DexType, DexClass> definitionFor,
ProguardConfigurationRule rule) {
DexClass clazz = definitionFor.apply(type);
if (clazz == null) {
return false;
}
return
anySuperTypeMatches(
clazz.superType,
definitionFor,
rule.getInheritanceClassName(),
rule.getInheritanceAnnotation())
|| anyImplementedInterfaceMatches(
clazz,
definitionFor,
rule.getInheritanceClassName(),
rule.getInheritanceAnnotation());
}
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, dexStringCache)) {
return true;
}
}
}
return false;
}
private boolean ruleSatisfiedByMethods(ProguardMemberRule rule, DexEncodedMethod[] methods) {
if (rule.getRuleType().includesMethods()) {
for (DexEncodedMethod method : methods) {
if (rule.matches(method, dexStringCache)) {
return true;
}
}
}
return false;
}
private boolean ruleSatisfiedByFields(
ProguardMemberRule rule,
Iterable<DexEncodedField> fields) {
if (rule.getRuleType().includesFields()) {
for (DexEncodedField field : fields) {
if (rule.matches(field, dexStringCache)) {
return true;
}
}
}
return false;
}
private boolean ruleSatisfiedByFields(ProguardMemberRule rule, DexEncodedField[] fields) {
if (rule.getRuleType().includesFields()) {
for (DexEncodedField field : fields) {
if (rule.matches(field, dexStringCache)) {
return true;
}
}
}
return false;
}
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,
DexItem precondition) {
if ((methodsMarked != null)
&& methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
return;
}
for (ProguardMemberRule rule : rules) {
if (rule.matches(method, 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,
DexItem precondition) {
for (ProguardMemberRule rule : rules) {
if (rule.matches(field, 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(DexItem item, DexType type, ProguardKeepRule context) {
if (type.isArrayType()) {
type = type.toBaseType(application.dexItemFactory);
}
if (type.isPrimitiveType()) {
return;
}
DexClass definition = appInfo.definitionFor(type);
if (definition == null || definition.isLibraryClass()) {
return;
}
// Keep the type if the item is also kept.
dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
.put(definition, context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
noObfuscation.add(type);
}
private void includeDescriptorClasses(DexItem item, ProguardKeepRule context) {
if (item instanceof DexEncodedMethod) {
DexMethod method = ((DexEncodedMethod) item).method;
includeDescriptor(item, method.proto.returnType, context);
for (DexType value : method.proto.parameters.values) {
includeDescriptor(item, value, context);
}
} else if (item instanceof DexEncodedField) {
DexField field = ((DexEncodedField) item).field;
includeDescriptor(item, field.type, context);
} else {
assert item instanceof DexClass;
}
}
private synchronized void addItemToSets(
DexItem item,
ProguardConfigurationRule context,
ProguardMemberRule rule,
DexItem precondition) {
if (context instanceof ProguardKeepRule) {
ProguardKeepRule keepRule = (ProguardKeepRule) context;
ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
if (!modifiers.allowsShrinking) {
if (precondition != null) {
dependentNoShrinking.computeIfAbsent(precondition, x -> new IdentityHashMap<>())
.put(item, keepRule);
} else {
noShrinking.put(item, keepRule);
}
}
if (!modifiers.allowsOptimization) {
noOptimization.add(item);
}
if (!modifiers.allowsObfuscation) {
if (item instanceof DexClass) {
noObfuscation.add(((DexClass) item).type);
} else {
noObfuscation.add(item);
}
}
if (modifiers.includeDescriptorClasses) {
includeDescriptorClasses(item, keepRule);
}
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
noSideEffects.put(item, rule);
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
reasonAsked.add(item);
} else if (context instanceof ProguardKeepPackageNamesRule) {
keepPackageName.add(item);
} else if (context instanceof ProguardAssumeValuesRule) {
assumedValues.put(item, rule);
} else if (context instanceof ProguardCheckDiscardRule) {
checkDiscarded.add(item);
} else if (context instanceof ProguardAlwaysInlineRule) {
alwaysInline.add(item);
} else if (context instanceof ProguardIdentifierNameStringRule) {
if (item instanceof DexEncodedField) {
identifierNameStrings.add(((DexEncodedField) item).field);
} else if (item instanceof DexEncodedMethod) {
identifierNameStrings.add(((DexEncodedMethod) item).method);
}
}
}
public static class RootSet {
public final Map<DexItem, ProguardKeepRule> noShrinking;
public final Set<DexItem> noOptimization;
public final Set<DexItem> noObfuscation;
public final Set<DexItem> reasonAsked;
public final Set<DexItem> keepPackageName;
public final Set<DexItem> checkDiscarded;
public final Set<DexItem> alwaysInline;
public final Map<DexItem, ProguardMemberRule> noSideEffects;
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
public final Set<DexItem> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
assert item instanceof DexType
|| item instanceof DexEncodedMethod
|| item instanceof DexEncodedField;
return item instanceof DexType
|| item instanceof DexEncodedMethod
|| item instanceof DexEncodedField;
}
private boolean legalNoObfuscationItems(Set<DexItem> items) {
assert items.stream().allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
private boolean legalDependentNoShrinkingItems(
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
assert dependentNoShrinking.keySet().stream()
.allMatch(this::isTypeEncodedMethodOrEncodedField);
return true;
}
private RootSet(
Map<DexItem, ProguardKeepRule> noShrinking,
Set<DexItem> noOptimization,
Set<DexItem> noObfuscation,
Set<DexItem> reasonAsked,
Set<DexItem> keepPackageName,
Set<DexItem> checkDiscarded,
Set<DexItem> alwaysInline,
Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
Set<DexItem> identifierNameStrings,
Set<ProguardIfRule> ifRules) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
this.noOptimization = noOptimization;
this.noObfuscation = noObfuscation;
this.reasonAsked = Collections.unmodifiableSet(reasonAsked);
this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
this.assumedValues = Collections.unmodifiableMap(assumedValues);
this.dependentNoShrinking = dependentNoShrinking;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
assert legalNoObfuscationItems(noObfuscation);
assert legalDependentNoShrinkingItems(dependentNoShrinking);
}
Map<DexItem, ProguardKeepRule> getDependentItems(DexItem item) {
assert item instanceof DexType
|| item instanceof DexEncodedMethod
|| item instanceof DexEncodedField;
return Collections
.unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
}
private boolean isStaticMember(Entry<DexItem, ProguardKeepRule> entry) {
if (entry.getKey() instanceof DexEncodedMethod) {
return ((DexEncodedMethod) entry.getKey()).accessFlags.isStatic();
}
if (entry.getKey() instanceof DexEncodedField) {
return ((DexEncodedField) entry.getKey()).accessFlags.isStatic();
}
return false;
}
Map<DexItem, ProguardKeepRule> getDependentStaticMembers(DexItem item) {
assert item instanceof DexType;
return getDependentItems(item).entrySet().stream()
.filter(this::isStaticMember)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
@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("\nkeepPackageName: " + keepPackageName.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(DexItem::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 Map<DexItem, ProguardKeepRule> noShrinking;
final Set<DexItem> noOptimization;
final Set<DexItem> noObfuscation;
private ConsequentRootSet(
Map<DexItem, ProguardKeepRule> noShrinking,
Set<DexItem> noOptimization,
Set<DexItem> noObfuscation) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
this.noOptimization = Collections.unmodifiableSet(noOptimization);
this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
}
}
}