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 {}
+}