Account for classpath classes in -keep*,includedescriptorclasses evaluation
Fixes: b/415977217
Change-Id: Ia9dc2e37daea9420d84c9c90501780d83974c038
diff --git a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
index a021545..9fd1f91 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
@@ -34,12 +34,4 @@
default boolean isClass() {
return true;
}
-
- boolean isClasspathClass();
-
- DexClasspathClass asClasspathClass();
-
- boolean isLibraryClass();
-
- DexLibraryClass asLibraryClass();
}
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathDefinition.java b/src/main/java/com/android/tools/r8/graph/ClasspathDefinition.java
index 43a4e70..8516fbd 100644
--- a/src/main/java/com/android/tools/r8/graph/ClasspathDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathDefinition.java
@@ -21,4 +21,14 @@
default ProgramDerivedContext asProgramDerivedContext(ProgramDerivedContext witness) {
return ClasspathOrLibraryContext.create(this, witness);
}
+
+ @Override
+ default boolean isClasspathDefinition() {
+ return true;
+ }
+
+ @Override
+ default ClasspathDefinition asClasspathDefinition() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/Definition.java b/src/main/java/com/android/tools/r8/graph/Definition.java
index e03b8e8..835d298 100644
--- a/src/main/java/com/android/tools/r8/graph/Definition.java
+++ b/src/main/java/com/android/tools/r8/graph/Definition.java
@@ -76,6 +76,22 @@
return null;
}
+ default boolean isClasspathClass() {
+ return false;
+ }
+
+ default DexClasspathClass asClasspathClass() {
+ return null;
+ }
+
+ default boolean isClasspathDefinition() {
+ return false;
+ }
+
+ default ClasspathDefinition asClasspathDefinition() {
+ return null;
+ }
+
default boolean isClasspathField() {
return false;
}
@@ -96,6 +112,18 @@
return null;
}
+ default boolean isLibraryClass() {
+ return false;
+ }
+
+ default DexLibraryClass asLibraryClass() {
+ return null;
+ }
+
+ default boolean isLibraryDefinition() {
+ return false;
+ }
+
default boolean isLibraryField() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e8bc0b7..db3a696 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -675,28 +675,8 @@
return this;
}
- @Override
- public boolean isClasspathClass() {
- return false;
- }
-
- @Override
- public DexClasspathClass asClasspathClass() {
- return null;
- }
-
public abstract boolean isNotProgramClass();
- @Override
- public boolean isLibraryClass() {
- return false;
- }
-
- @Override
- public DexLibraryClass asLibraryClass() {
- return null;
- }
-
public boolean isPrivate() {
return accessFlags.isPrivate();
}
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryDefinition.java b/src/main/java/com/android/tools/r8/graph/LibraryDefinition.java
index 6767199..0dee6e9 100644
--- a/src/main/java/com/android/tools/r8/graph/LibraryDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/LibraryDefinition.java
@@ -20,4 +20,9 @@
default ProgramDerivedContext asProgramDerivedContext(ProgramDerivedContext witness) {
return ClasspathOrLibraryContext.create(this, witness);
}
+
+ @Override
+ default boolean isLibraryDefinition() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
index fb02899..93cbfd5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -74,6 +74,10 @@
this.modifiers = modifiers;
}
+ public boolean getIncludeDescriptorClasses() {
+ return modifiers.includeDescriptorClasses;
+ }
+
public ProguardKeepRuleType getType() {
return type;
}
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 39b35aa..9237d3c 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.ClasspathDefinition;
import com.android.tools.r8.graph.Definition;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
@@ -46,6 +47,7 @@
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ProgramOrClasspathDefinition;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.analysis.type.DynamicType;
@@ -366,19 +368,37 @@
}
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier;
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier;
if (rule instanceof ProguardKeepRule) {
- if (clazz.isNotProgramClass()) {
+ ProguardKeepRule keepRule = rule.asProguardKeepRule();
+ if (clazz.isLibraryClass()) {
return;
}
- switch (((ProguardKeepRule) rule).getType()) {
+ // Classpath classes may have members that refer to program classes. Therefore, we cannot
+ // skip rule evaluation in presence of `,includedescriptorclasses`.
+ if (clazz.isClasspathClass() && !keepRule.getIncludeDescriptorClasses()) {
+ return;
+ }
+ switch (keepRule.getType()) {
case KEEP_CLASS_MEMBERS:
// Members mentioned at -keepclassmembers always depend on their holder.
- preconditionSupplier = ImmutableMap.of(definition -> true, clazz.asProgramClass());
+ preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRulePreconditionMatch);
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ keepRule.getIncludeDescriptorClasses(),
+ false,
+ ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRulePreconditionMatch);
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ keepRule.getIncludeDescriptorClasses(),
+ false,
+ ifRulePreconditionMatch);
break;
case KEEP_CLASSES_WITH_MEMBERS:
if (!allRulesSatisfied(memberKeepRules, clazz)) {
@@ -392,17 +412,28 @@
// 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.asProgramClass());
+ 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(alwaysTrue(), null);
}
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRulePreconditionMatch);
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ keepRule.getIncludeDescriptorClasses(),
+ false,
+ ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, preconditionSupplier, false, ifRulePreconditionMatch);
+ clazz,
+ memberKeepRules,
+ rule,
+ preconditionSupplier,
+ keepRule.getIncludeDescriptorClasses(),
+ false,
+ ifRulePreconditionMatch);
break;
case CONDITIONAL:
throw new Unreachable("-if rule will be evaluated separately, not here.");
@@ -423,25 +454,25 @@
|| rule instanceof ProguardWhyAreYouKeepingRule) {
markClass(clazz, rule, ifRulePreconditionMatch);
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingOverriddenMethods(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardAssumeNoSideEffectRule
|| rule instanceof ProguardAssumeValuesRule) {
if (assumeInfoCollectionBuilder != null) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingOverriddenMethods(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
}
} else if (rule instanceof NoFieldTypeStrengtheningRule
|| rule instanceof NoRedundantFieldLoadEliminationRule) {
@@ -468,9 +499,9 @@
}
} else if (rule instanceof NoValuePropagationRule) {
markMatchingVisibleMethods(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
markMatchingVisibleFields(
- clazz, memberKeepRules, rule, null, true, ifRulePreconditionMatch);
+ clazz, memberKeepRules, rule, null, true, true, ifRulePreconditionMatch);
} else if (rule instanceof ProguardIdentifierNameStringRule) {
markMatchingFields(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
markMatchingMethods(clazz, memberKeepRules, rule, null, ifRulePreconditionMatch);
@@ -634,16 +665,14 @@
pendingMethodMoveInverse);
}
- private static DexProgramClass testAndGetPrecondition(
- DexDefinition definition,
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier) {
+ private static DexClass testAndGetPrecondition(
+ DexDefinition definition, Map<Predicate<DexDefinition>, DexClass> preconditionSupplier) {
if (preconditionSupplier == null) {
return null;
}
- DexProgramClass precondition = null;
+ DexClass precondition = null;
boolean conditionEverMatched = false;
- for (Entry<Predicate<DexDefinition>, DexProgramClass> entry :
- preconditionSupplier.entrySet()) {
+ for (Entry<Predicate<DexDefinition>, DexClass> entry : preconditionSupplier.entrySet()) {
if (entry.getKey().test(definition)) {
precondition = entry.getValue();
conditionEverMatched = true;
@@ -660,7 +689,8 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
Set<Wrapper<DexMethod>> methodsMarked =
@@ -669,7 +699,10 @@
worklist.add(clazz);
while (!worklist.isEmpty()) {
DexClass currentClass = worklist.pop();
- if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
+ if (!includeClasspathClasses && currentClass.isClasspathClass()) {
+ break;
+ }
+ if (!includeLibraryClasses && currentClass.isLibraryClass()) {
break;
}
// In compat mode traverse all direct methods in the hierarchy.
@@ -680,7 +713,7 @@
|| (method.isStatic() && !method.isPrivate() && !method.isInitializer())
|| options.forceProguardCompatibility,
method -> {
- DexProgramClass precondition =
+ DexClass precondition =
testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
markMethod(
method,
@@ -723,7 +756,7 @@
private final DexProgramClass originalClazz;
private final Collection<ProguardMemberRule> memberKeepRules;
private final ProguardConfigurationRule context;
- private final Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier;
+ private final Map<Predicate<DexDefinition>, DexClass> preconditionSupplier;
private final ProguardIfRulePreconditionMatch ifRulePreconditionMatch;
private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
@@ -732,7 +765,7 @@
DexProgramClass originalClazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule context,
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
assert context.isProguardKeepRule();
assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
@@ -826,7 +859,7 @@
methodToKeep,
resolutionMethod,
(rootSetBuilder) -> {
- DexProgramClass precondition =
+ DexClass precondition =
testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
rootSetBuilder.addItemToSets(
methodToKeep, context, rule, precondition, ifRulePreconditionMatch);
@@ -843,8 +876,9 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
- boolean onlyIncludeProgramClasses,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
+ boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
Set<DexClass> visited = Sets.newIdentityHashSet();
Deque<DexClass> worklist = new ArrayDeque<>();
@@ -857,13 +891,16 @@
if (!visited.add(currentClass)) {
continue;
}
- if (!onlyIncludeProgramClasses && currentClass.isNotProgramClass()) {
+ if (!includeClasspathClasses && currentClass.isClasspathClass()) {
+ continue;
+ }
+ if (!includeLibraryClasses && currentClass.isLibraryClass()) {
continue;
}
currentClass.forEachClassMethodMatching(
DexEncodedMethod::belongsToVirtualPool,
method -> {
- DexProgramClass precondition =
+ DexClass precondition =
testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
markMethod(
method, memberKeepRules, null, rule, precondition, ifRulePreconditionMatch);
@@ -876,11 +913,11 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
clazz.forEachClassMethod(
method -> {
- DexProgramClass precondition =
+ DexClass precondition =
testAndGetPrecondition(method.getDefinition(), preconditionSupplier);
markMethod(method, memberKeepRules, null, rule, precondition, ifRulePreconditionMatch);
});
@@ -890,16 +927,20 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
+ boolean includeClasspathClasses,
boolean includeLibraryClasses,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
while (clazz != null) {
- if (!includeLibraryClasses && clazz.isNotProgramClass()) {
+ if (!includeClasspathClasses && clazz.isClasspathClass()) {
+ return;
+ }
+ if (!includeLibraryClasses && clazz.isLibraryClass()) {
return;
}
clazz.forEachClassField(
field -> {
- DexProgramClass precondition =
+ DexClass precondition =
testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRulePreconditionMatch);
});
@@ -911,11 +952,11 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexProgramClass> preconditionSupplier,
+ Map<Predicate<DexDefinition>, DexClass> preconditionSupplier,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
clazz.forEachClassField(
field -> {
- DexProgramClass precondition =
+ DexClass precondition =
testAndGetPrecondition(field.getDefinition(), preconditionSupplier);
markField(field, memberKeepRules, rule, precondition, ifRulePreconditionMatch);
});
@@ -1231,7 +1272,7 @@
Collection<ProguardMemberRule> rules,
Set<Wrapper<DexMethod>> methodsMarked,
ProguardConfigurationRule context,
- DexProgramClass precondition,
+ DexClass precondition,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
if (methodsMarked != null
&& methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.getReference()))) {
@@ -1252,7 +1293,7 @@
DexClassAndField field,
Collection<ProguardMemberRule> rules,
ProguardConfigurationRule context,
- DexProgramClass precondition,
+ DexClass precondition,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
for (ProguardMemberRule rule : rules) {
if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
@@ -1304,15 +1345,17 @@
}
private void includeDescriptorClasses(
- ProgramDefinition item, ProguardKeepRuleBase rule, EnqueuerEvent preconditionEvent) {
+ ProgramOrClasspathDefinition item,
+ ProguardKeepRuleBase rule,
+ EnqueuerEvent preconditionEvent) {
if (item.isMethod()) {
- ProgramMethod method = item.asProgramMethod();
+ DexClassAndMethod method = item.asMethod();
includeDescriptor(method.getReturnType(), rule, preconditionEvent);
for (DexType value : method.getParameters()) {
includeDescriptor(value, rule, preconditionEvent);
}
} else if (item.isField()) {
- ProgramField field = item.asProgramField();
+ DexClassAndField field = item.asField();
includeDescriptor(field.getType(), rule, preconditionEvent);
} else {
assert item.isClass();
@@ -1323,18 +1366,27 @@
Definition item,
ProguardConfigurationRule context,
ProguardMemberRule rule,
- DexProgramClass precondition,
+ DexClass precondition,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
if (context.isProguardKeepRule()) {
- if (!item.isProgramDefinition()) {
- // Keep rules do not apply to non-program items.
+ if (item.isLibraryDefinition()) {
+ // Keep rules do not apply to library definitions.
return;
}
- evaluateKeepRule(
- item.asProgramDefinition(),
- context.asProguardKeepRule(),
- precondition,
- ifRulePreconditionMatch);
+ ProguardKeepRule keepRule = context.asProguardKeepRule();
+ // Keep rules do not apply the classpath definitions except in the presence of
+ // `,includedescriptorclasses`.
+ if (item.isClasspathDefinition() && !keepRule.getIncludeDescriptorClasses()) {
+ return;
+ }
+ assert item.isProgramDefinition() || item.isClasspathDefinition();
+ if (item.isProgramDefinition()) {
+ evaluateKeepRule(
+ item.asProgramDefinition(), keepRule, precondition, ifRulePreconditionMatch);
+ } else {
+ evaluateKeepRuleOnClasspath(
+ item.asClasspathDefinition(), keepRule, ifRulePreconditionMatch);
+ }
} else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
mayHaveSideEffects.put(item.getReference(), rule);
context.markAsUsed();
@@ -1709,7 +1761,7 @@
private void evaluateKeepRule(
ProgramDefinition item,
ProguardKeepRule context,
- DexProgramClass precondition,
+ DexClass precondition,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
// The reason for keeping should link to the conditional rule as a whole, if present.
ProguardKeepRuleBase whyAreYouKeepingKeepRule =
@@ -1727,7 +1779,7 @@
private void evaluateKeepRule(
ProgramDefinition item,
- DexProgramClass precondition,
+ DexClass precondition,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch,
ProguardKeepRuleModifiers modifiers,
Action markAsUsed,
@@ -1777,12 +1829,13 @@
}
EnqueuerEvent preconditionEvent;
- if (precondition != null) {
+ if (precondition != null && precondition.isProgramClass()) {
+ DexProgramClass programPrecondition = precondition.asProgramClass();
preconditionEvent =
item.getAccessFlags().isStatic()
|| (item.isMethod() && item.asMethod().getDefinition().isInstanceInitializer())
- ? new LiveClassEnqueuerEvent(precondition)
- : new InstantiatedClassEnqueuerEvent(precondition);
+ ? new LiveClassEnqueuerEvent(programPrecondition)
+ : new InstantiatedClassEnqueuerEvent(programPrecondition);
} else {
preconditionEvent = UnconditionalKeepInfoEvent.get();
}
@@ -1908,6 +1961,23 @@
assert !itemJoiner.isSet() || !itemJoiner.computeIfAbsent().isBottom();
}
+ private void evaluateKeepRuleOnClasspath(
+ ClasspathDefinition item,
+ ProguardKeepRule context,
+ ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
+ if (context.getIncludeDescriptorClasses()) {
+ // Classpath classes are unconditionally live.
+ EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
+ // The reason for keeping should link to the conditional rule as a whole, if present.
+ ProguardKeepRuleBase whyAreYouKeepingKeepRule =
+ ifRulePreconditionMatch != null
+ ? ifRulePreconditionMatch.getIfRuleWithPreconditionSet()
+ : context;
+ includeDescriptorClasses(item, whyAreYouKeepingKeepRule, preconditionEvent);
+ context.markAsUsed();
+ }
+ }
+
private RetentionInfo getRetentionFromAttributeConfig(boolean visible, boolean invisible) {
if (visible && invisible) {
return RetentionInfo.getRetainAll();
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationIncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationIncludeDescriptorClassesTest.java
new file mode 100644
index 0000000..34a28f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationIncludeDescriptorClassesTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PartialCompilationIncludeDescriptorClassesTest extends TestBase {
+
+ private static final String rule =
+ StringUtils.lines(
+ "-keep,includedescriptorclasses class " + ExcludedClass.class.getTypeName() + " {",
+ " public static void m(...);",
+ "}");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters)
+ .addInnerClasses(getClass())
+ .addKeepRules(rule)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testR8Partial() throws Exception {
+ testForR8Partial(parameters)
+ .addR8IncludedClasses(IncludedClass.class)
+ .addR8ExcludedClasses(ExcludedClass.class)
+ .addKeepRules(rule)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject includedClassSubject = inspector.clazz(IncludedClass.class);
+ assertThat(includedClassSubject, isPresentAndNotRenamed());
+
+ MethodSubject methodSubject =
+ inspector.clazz(ExcludedClass.class).uniqueMethodWithOriginalName("m");
+ assertThat(methodSubject, isPresent());
+ assertEquals(includedClassSubject.asTypeSubject(), methodSubject.getParameter(0));
+ }
+
+ static class ExcludedClass {
+
+ public static void m(IncludedClass includedClass) {}
+ }
+
+ static class IncludedClass {}
+}