Remove redundant work in rule evaluation
This reduces build speed for the Chrome benchmark by 8% when running locally.
Bug: b/422947619
Change-Id: I1e8921b296ae0f35ba56bb3dc3f677a84d276d19
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 0b201a2..7f2238d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -228,7 +228,9 @@
boolean classNameResult = memberRule.getClassNames().matches(source.type);
assert classNameResult;
if (memberRule.hasInheritanceClassName()) {
- boolean inheritanceResult = rootSetBuilder.satisfyInheritanceRule(target, memberRule);
+ boolean inheritanceResult =
+ memberRule.satisfyInheritanceRule(
+ target, appView, rootSetBuilder::handleMatchedAnnotation);
assert inheritanceResult;
}
}
@@ -278,7 +280,7 @@
}
if (rule.hasInheritanceClassName()) {
// Try another live type since the current one doesn't satisfy the inheritance rule.
- return rootSetBuilder.satisfyInheritanceRule(clazz, rule);
+ return rule.satisfyInheritanceRule(clazz, appView, rootSetBuilder::handleMatchedAnnotation);
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index 9555058..fd68364 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -38,6 +38,8 @@
return new SingleClassNameList(matcher);
}
+ public abstract boolean isMatchAnyClassPattern();
+
public abstract int size();
public static class Builder {
@@ -154,6 +156,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return false;
+ }
+
+ @Override
public int size() {
return 0;
}
@@ -207,6 +214,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return className.isMatchAnyClassPattern();
+ }
+
+ @Override
public int size() {
return 1;
}
@@ -300,6 +312,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return false;
+ }
+
+ @Override
public int size() {
return classNames.size();
}
@@ -412,6 +429,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return false;
+ }
+
+ @Override
public int size() {
return classNames.size();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 98f41cd..00e4830 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -264,6 +265,19 @@
return memberRules;
}
+ public void getFieldAndMethodRules(
+ Collection<ProguardMemberRule> fieldRules, Collection<ProguardMemberRule> methodRules) {
+ for (ProguardMemberRule memberRule : memberRules) {
+ ProguardMemberType ruleType = memberRule.getRuleType();
+ if (ruleType.includesFields()) {
+ fieldRules.add(memberRule);
+ }
+ if (ruleType.includesMethods()) {
+ methodRules.add(memberRule);
+ }
+ }
+ }
+
public boolean getInheritanceIsExtends() {
return inheritanceIsExtends;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index d8e0105..84518dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.shaking;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyAccessFlag;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyAnnotation;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyClassType;
+import static com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder.satisfyNonSyntheticClass;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -15,8 +19,9 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
-import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Iterables;
import java.util.List;
@@ -30,6 +35,7 @@
// TODO(b/164019179): Since we are using the rule language for tracing main dex we can end up in
// a situation where the references to types are dead.
private boolean canReferenceDeadTypes = false;
+ private OptionalBool trivialAllClassMatch = OptionalBool.unknown();
ProguardConfigurationRule(
Origin origin,
@@ -61,21 +67,27 @@
memberRules);
}
- public boolean isTrivalAllClassMatch() {
- BooleanBox booleanBox = new BooleanBox(true);
- getClassNames()
- .forEachTypeMatcher(
- unused -> booleanBox.set(false),
- proguardTypeMatcher -> !proguardTypeMatcher.isMatchAnyClassPattern());
- return booleanBox.get()
+ public boolean isTrivialAllClassMatch() {
+ if (trivialAllClassMatch.isUnknown()) {
+ trivialAllClassMatch = OptionalBool.of(computeIsTrivialAllClassMatch());
+ }
+ assert trivialAllClassMatch.isTrue() == computeIsTrivialAllClassMatch();
+ return trivialAllClassMatch.isTrue();
+ }
+
+ public boolean isTrivialAllClassMatchWithNoMembersRules() {
+ return isTrivialAllClassMatch() && !hasMemberRules();
+ }
+
+ public boolean computeIsTrivialAllClassMatch() {
+ return getClassNames().isMatchAnyClassPattern()
&& getClassAnnotations().isEmpty()
&& getClassAccessFlags().isDefaultFlags()
&& getNegatedClassAccessFlags().isDefaultFlags()
&& !getClassTypeNegated()
&& getClassType() == ProguardClassType.CLASS
&& getInheritanceAnnotations().isEmpty()
- && getInheritanceClassName() == null
- && getMemberRules().isEmpty();
+ && !hasInheritanceClassName();
}
public boolean isUsed() {
@@ -267,4 +279,141 @@
super.append(builder);
return builder;
}
+
+ public boolean testClassCondition(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ if (!satisfyNonSyntheticClass(clazz, appView)) {
+ return false;
+ }
+ if (!satisfyClassType(this, clazz)) {
+ return false;
+ }
+ if (!satisfyAccessFlag(this, clazz)) {
+ return false;
+ }
+ AnnotationMatchResult annotationMatchResult = satisfyAnnotation(this, clazz);
+ if (annotationMatchResult == null) {
+ return false;
+ }
+ annotationMatchResultConsumer.accept(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 (hasInheritanceClassName()
+ && !satisfyInheritanceRule(clazz, appView, annotationMatchResultConsumer)) {
+ return false;
+ }
+ return getClassNames().matches(clazz.type);
+ }
+
+ public boolean satisfyInheritanceRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ return satisfyExtendsRule(clazz, appView, annotationMatchResultConsumer)
+ || satisfyImplementsRule(clazz, appView, annotationMatchResultConsumer);
+ }
+
+ private boolean satisfyExtendsRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ if (anySuperTypeMatchesExtendsRule(clazz.superType, appView, annotationMatchResultConsumer)) {
+ 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, false, appView);
+ }
+
+ private boolean anySuperTypeMatchesExtendsRule(
+ DexType type,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ while (type != null) {
+ DexClass clazz = appView.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 (getInheritanceClassName().matches(clazz.type, appView)) {
+ AnnotationMatchResult annotationMatchResult =
+ RootSetBuilder.containsAllAnnotations(getInheritanceAnnotations(), clazz);
+ if (annotationMatchResult != null) {
+ annotationMatchResultConsumer.accept(annotationMatchResult);
+ return true;
+ }
+ }
+ type = clazz.superType;
+ }
+ return false;
+ }
+
+ private boolean satisfyImplementsRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ if (anyImplementedInterfaceMatchesImplementsRule(
+ clazz, appView, annotationMatchResultConsumer)) {
+ 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, true, appView);
+ }
+
+ private boolean anyImplementedInterfaceMatchesImplementsRule(
+ DexClass clazz,
+ AppView<?> appView,
+ Consumer<AnnotationMatchResult> annotationMatchResultConsumer) {
+ // TODO(herhut): Maybe it would be better to do this breadth first.
+ for (DexType iface : clazz.getInterfaces()) {
+ DexClass ifaceClass = appView.definitionFor(iface);
+ if (ifaceClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ continue;
+ }
+ // 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 (getInheritanceClassName().matches(iface, appView)) {
+ AnnotationMatchResult annotationMatchResult =
+ RootSetBuilder.containsAllAnnotations(getInheritanceAnnotations(), ifaceClass);
+ if (annotationMatchResult != null) {
+ annotationMatchResultConsumer.accept(annotationMatchResult);
+ return true;
+ }
+ }
+ if (anyImplementedInterfaceMatchesImplementsRule(
+ ifaceClass, appView, annotationMatchResultConsumer)) {
+ return true;
+ }
+ }
+ if (!clazz.hasSuperType()) {
+ return false;
+ }
+ DexClass superClass = appView.definitionFor(clazz.getSuperType());
+ return superClass != null
+ && anyImplementedInterfaceMatchesImplementsRule(
+ superClass, appView, annotationMatchResultConsumer);
+ }
+
+ private boolean anySourceMatchesInheritanceRuleDirectly(
+ DexClass clazz, boolean isInterface, AppView<?> appView) {
+ // 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.getVerticallyMergedClasses() != null
+ && appView.getVerticallyMergedClasses().getSourcesFor(clazz.type).stream()
+ .filter(
+ sourceType ->
+ appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
+ .anyMatch(getInheritanceClassName()::matches);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
index 81919d3..000cfc3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRulePreconditionMatch.java
@@ -33,7 +33,7 @@
public void disallowOptimizationsForReevaluation(
Enqueuer enqueuer, ConsequentRootSetBuilder rootSetBuilder) {
if (enqueuer.getMode().isInitialTreeShaking()
- && !ifRule.isTrivalAllClassMatch()
+ && !ifRule.isTrivialAllClassMatchWithNoMembersRules()
&& classMatch.isProgramClass()) {
disallowClassOptimizationsForReevaluation(rootSetBuilder);
disallowMethodOptimizationsForReevaluation(rootSetBuilder);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 908ab5f..2541fcb 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -342,9 +342,10 @@
private void process(
Collection<DexClass> classes,
ProguardConfigurationRule rule,
- ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
for (DexClass clazz : classes) {
- process(clazz, rule, ifRulePreconditionMatch);
+ process(clazz, rule, ifRulePreconditionMatch, timing);
}
}
@@ -352,35 +353,15 @@
private void process(
DexClass clazz,
ProguardConfigurationRule rule,
- ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
- if (!satisfyNonSyntheticClass(clazz, appView)) {
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ if (!rule.testClassCondition(clazz, appView, this::handleMatchedAnnotation)) {
return;
}
- 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();
+ Collection<ProguardMemberRule> fieldKeepRules = new ArrayList<>();
+ Collection<ProguardMemberRule> methodKeepRules = new ArrayList<>();
+ rule.getFieldAndMethodRules(fieldKeepRules, methodKeepRules);
Map<Predicate<DexDefinition>, DexClass> preconditionSupplier;
if (rule instanceof ProguardKeepRule) {
ProguardKeepRule keepRule = rule.asProguardKeepRule();
@@ -398,7 +379,7 @@
preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
markMatchingVisibleMethods(
clazz,
- memberKeepRules,
+ methodKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -406,7 +387,7 @@
ifRulePreconditionMatch);
markMatchingVisibleFields(
clazz,
- memberKeepRules,
+ fieldKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -433,7 +414,7 @@
}
markMatchingVisibleMethods(
clazz,
- memberKeepRules,
+ methodKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -441,7 +422,7 @@
ifRulePreconditionMatch);
markMatchingVisibleFields(
clazz,
- memberKeepRules,
+ fieldKeepRules,
rule,
preconditionSupplier,
keepRule.getIncludeDescriptorClasses(),
@@ -467,29 +448,29 @@
|| rule instanceof ProguardWhyAreYouKeepingRule) {
markClass(clazz, rule, ifRulePreconditionMatch);
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingOverriddenMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardAssumeNoSideEffectRule
|| rule instanceof ProguardAssumeValuesRule) {
if (assumeInfoCollectionBuilder != null) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch, timing);
markMatchingOverriddenMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch, timing);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch, timing);
}
} else if (rule instanceof NoFieldTypeStrengtheningRule
|| rule instanceof NoRedundantFieldLoadEliminationRule) {
- markMatchingFields(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
} else if (rule instanceof InlineRule
|| rule instanceof KeepConstantArgumentRule
|| rule instanceof KeepUnusedReturnValueRule
@@ -501,7 +482,7 @@
|| rule instanceof ReprocessMethodRule
|| rule instanceof WhyAreYouNotInliningRule
|| rule.isMaximumRemovedAndroidLogLevelRule()) {
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
} else if (rule instanceof ClassInlineRule
|| rule instanceof NoUnusedInterfaceRemovalRule
|| rule instanceof NoVerticalClassMergingRule
@@ -512,15 +493,15 @@
}
} else if (rule instanceof NoValuePropagationRule) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, methodKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
+ clazz, fieldKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardIdentifierNameStringRule) {
- markMatchingFields(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
+ markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
} else {
assert rule instanceof ConvertCheckNotNullRule;
- markMatchingMethods(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
+ markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
}
}
@@ -538,7 +519,7 @@
DexClass clazz = application.definitionFor(type);
// Ignore keep rule iff it does not reference a class in the app.
if (clazz != null) {
- process(clazz, rule, ifRulePreconditionMatch);
+ process(clazz, rule, ifRulePreconditionMatch, timing);
}
}
timing.end();
@@ -575,8 +556,9 @@
List<DexClass> job = new ArrayList<>(classes);
tasks.submitUnchecked(
() -> {
- Timing threadTiming = timing.createThreadTiming("Process rule", options);
- process(job, rule, ifRulePreconditionMatch);
+ Timing threadTiming =
+ timing.createThreadTiming("Process rule: " + rule, options);
+ process(job, rule, ifRulePreconditionMatch, threadTiming);
threadTiming.end().notifyThreadTimingFinished();
});
classes.clear();
@@ -588,7 +570,7 @@
tasks.submitUnchecked(
() -> {
Timing threadTiming = timing.createThreadTiming("Process rule", options);
- process(classes, rule, ifRulePreconditionMatch);
+ process(classes, rule, ifRulePreconditionMatch, threadTiming);
threadTiming.end().notifyThreadTimingFinished();
});
}
@@ -599,12 +581,12 @@
Timing threadTiming = timing.createThreadTiming("Evaluate non-program", options);
if (rule.isApplicableToClasspathClasses()) {
for (DexClasspathClass clazz : application.classpathClasses()) {
- process(clazz, rule, ifRulePreconditionMatch);
+ process(clazz, rule, ifRulePreconditionMatch, threadTiming);
}
}
if (rule.isApplicableToLibraryClasses()) {
for (DexLibraryClass clazz : application.libraryClasses()) {
- process(clazz, rule, ifRulePreconditionMatch);
+ process(clazz, rule, ifRulePreconditionMatch, threadTiming);
}
}
threadTiming.end().notifyThreadTimingFinished();
@@ -626,7 +608,7 @@
subtypingInfo,
application.classes(),
alwaysTrue(),
- clazz -> process(clazz, rule, ifRulePreconditionMatch));
+ clazz -> process(clazz, rule, ifRulePreconditionMatch, threadTiming));
threadTiming.end().notifyThreadTimingFinished();
});
}
@@ -788,13 +770,37 @@
Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
boolean includeClasspathClasses,
boolean includeLibraryClasses,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ timing.begin("Mark visible methods");
+ markMatchingVisibleMethods(
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ includeClasspathClasses,
+ includeLibraryClasses,
+ ifRulePreconditionMatch);
+ timing.end();
+ }
+
+ private void markMatchingVisibleMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
+ boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ if (memberKeepRules.isEmpty()) {
+ return;
+ }
+ // TODO(b/422947619): Evaluate the impact of matching the rule against the superclass.
+ boolean includeSuperclasses = !rule.isTrivialAllClassMatch();
Set<Wrapper<DexMethod>> methodsMarked =
options.forceProguardCompatibility ? null : new HashSet<>();
- Deque<DexClass> worklist = new ArrayDeque<>();
- worklist.add(clazz);
- while (!worklist.isEmpty()) {
- DexClass currentClass = worklist.pop();
+ DexClass currentClass = clazz;
+ do {
if (!includeClasspathClasses && currentClass.isClasspathClass()) {
break;
}
@@ -805,7 +811,7 @@
currentClass.forEachClassMethodMatching(
method ->
method.belongsToVirtualPool()
- || currentClass == clazz
+ || method.getHolderType().isIdenticalTo(clazz.getType())
|| (method.isStatic() && !method.isPrivate() && !method.isInitializer())
|| options.forceProguardCompatibility,
method -> {
@@ -819,13 +825,12 @@
precondition,
ifRulePreconditionMatch);
});
- if (currentClass.superType != null) {
- DexClass dexClass = application.definitionFor(currentClass.superType);
- if (dexClass != null) {
- worklist.add(dexClass);
- }
+ if (currentClass.hasSuperType() && includeSuperclasses) {
+ currentClass = application.definitionFor(currentClass.getSuperType());
+ } else {
+ break;
}
- }
+ } while (currentClass != null);
// TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
// fullmode.
if (clazz.isProgramClass()
@@ -978,13 +983,35 @@
Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
boolean includeClasspathClasses,
boolean includeLibraryClasses,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ timing.begin("Mark overridden methods");
+ markMatchingOverriddenMethods(
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ includeClasspathClasses,
+ includeLibraryClasses,
+ ifRulePreconditionMatch);
+ timing.end();
+ }
+
+ private void markMatchingOverriddenMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
+ boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ if (memberKeepRules.isEmpty()) {
+ return;
+ }
Set<DexClass> visited = Sets.newIdentityHashSet();
- Deque<DexClass> worklist = new ArrayDeque<>();
// Intentionally skip the current `clazz`, assuming it's covered by
// markMatchingVisibleMethods.
- worklist.addAll(subtypingInfo.getSubclasses(clazz));
-
+ Deque<DexClass> worklist = new ArrayDeque<>(subtypingInfo.getSubclasses(clazz));
while (!worklist.isEmpty()) {
DexClass currentClass = worklist.poll();
if (!visited.add(currentClass)) {
@@ -1012,14 +1039,30 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
clazz.forEachClassMethod(
- method -> {
- DexClass precondition =
- testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
- markMethod(method, memberKeepRules, null, rule, precondition, ifRulePreconditionMatch);
- });
+ method -> markMethod(method, memberKeepRules, null, rule, null, ifRulePreconditionMatch));
+ }
+
+ private void markMatchingVisibleFields(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
+ boolean includeLibraryClasses,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
+ Timing timing) {
+ timing.begin("Mark visible fields");
+ markMatchingVisibleFields(
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ includeClasspathClasses,
+ includeLibraryClasses,
+ ifRulePreconditionMatch);
+ timing.end();
}
private void markMatchingVisibleFields(
@@ -1030,6 +1073,11 @@
boolean includeClasspathClasses,
boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ if (memberKeepRules.isEmpty()) {
+ return;
+ }
+ // TODO(b/422947619): Evaluate the impact of matching the rule against the superclass.
+ boolean includeSuperclasses = !rule.isTrivialAllClassMatch();
while (clazz != null) {
if (!includeClasspathClasses && clazz.isClasspathClass()) {
return;
@@ -1043,7 +1091,11 @@
testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRulePreconditionMatch);
});
- clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
+ if (clazz.hasSuperType() && includeSuperclasses) {
+ clazz = application.definitionFor(clazz.superType);
+ } else {
+ break;
+ }
}
}
@@ -1051,14 +1103,9 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
clazz.forEachClassField(
- field -> {
- DexClass precondition =
- testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
- markField(field, memberKeepRules, rule, precondition, ifRulePreconditionMatch);
- });
+ field -> markField(field, memberKeepRules, rule, null, ifRulePreconditionMatch));
}
// TODO(b/67934426): Test this code.
@@ -1147,105 +1194,6 @@
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.getVerticallyMergedClasses() != null
- && appView.getVerticallyMergedClasses().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) {
@@ -1745,8 +1693,8 @@
if (rule.getMemberRules().isEmpty()) {
evaluateCheckDiscardClassAndAllMembersRule(clazz, rule);
} else if (clazz.hasFields() || clazz.hasMethods()) {
- markMatchingFields(clazz, rule.getMemberRules(), rule, null, null);
- markMatchingMethods(clazz, rule.getMemberRules(), rule, null, null);
+ markMatchingFields(clazz, rule.getMemberRules(), rule, null);
+ markMatchingMethods(clazz, rule.getMemberRules(), rule, null);
classesWithCheckDiscardedMembers.add(clazz);
}
}