Only mark the class of keepclassmembers instantiated in compatibility mode.

Bug: 119076934
Bug: 132318609
Change-Id: I587af948494a6732369473a4e71c8da3ec2c6c4a
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2501f9f..7582343 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -888,11 +888,10 @@
       } else {
         assert !deferredAnnotations.containsKey(holder.type);
       }
-
-      Map<DexReference, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
-      enqueueHolderIfDependentNonStaticMember(holder, dependentItems);
-      // Add all dependent members to the workqueue.
-      enqueueRootItems(dependentItems);
+      rootSet.forEachDependentStaticMember(holder, appView, this::enqueueRootItem);
+      if (forceProguardCompatibility) {
+        enqueueHolderIfDependentNonStaticMember(holder, rootSet.getDependentItems(holder));
+      }
     }
   }
 
@@ -1052,8 +1051,8 @@
     transitionMethodsForInstantiatedClass(clazz.type);
     // For all instance fields visible from the class, mark them live if we have seen a read.
     transitionFieldsForInstantiatedClass(clazz.type);
-    // Add all dependent members to the workqueue.
-    enqueueRootItems(rootSet.getDependentItems(clazz));
+    // Add all dependent instance members to the workqueue.
+    rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueRootItem);
   }
 
   /**
@@ -1686,7 +1685,7 @@
           // and enqueue it as well. This is -if version of workaround for b/115867670.
           consequentRootSet.dependentNoShrinking.forEach(
               (precondition, dependentItems) -> {
-                if (precondition.isDexType()) {
+                if (precondition.isDexType() && forceProguardCompatibility) {
                   DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
                   enqueueHolderIfDependentNonStaticMember(preconditionHolder, dependentItems);
                 }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 40152ab..fa8b7ed 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -57,6 +57,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -1199,6 +1200,30 @@
           dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
     }
 
+    public void forEachDependentStaticMember(
+        DexDefinition item,
+        AppView<?> appView,
+        BiConsumer<DexDefinition, Set<ProguardKeepRule>> fn) {
+      getDependentItems(item).forEach((reference, reasons) -> {
+        DexDefinition definition = appView.definitionFor(reference);
+        if (definition != null && !definition.isDexClass() && definition.isStaticMember()) {
+          fn.accept(definition, reasons);
+        }
+      });
+    }
+
+    public void forEachDependentNonStaticMember(
+        DexDefinition item,
+        AppView<?> appView,
+        BiConsumer<DexDefinition, Set<ProguardKeepRule>> fn) {
+      getDependentItems(item).forEach((reference, reasons) -> {
+        DexDefinition definition = appView.definitionFor(reference);
+        if (definition != null && !definition.isDexClass() && !definition.isStaticMember()) {
+          fn.accept(definition, reasons);
+        }
+      });
+    }
+
     public void copy(DexReference original, DexReference rewritten) {
       if (noShrinking.containsKey(original)) {
         noShrinking.put(rewritten, noShrinking.get(original));
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index d63626a..45b369a 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -257,10 +257,15 @@
     return createRunResult(result);
   }
 
+  @Deprecated
   public Dex2OatTestRunResult runDex2Oat() throws IOException {
     return runDex2Oat(ToolHelper.getDexVm());
   }
 
+  public Dex2OatTestRunResult runDex2Oat(TestRuntime runtime) throws IOException {
+    return runDex2Oat(runtime.asDex().getVm());
+  }
+
   public Dex2OatTestRunResult runDex2Oat(DexVm vm) throws IOException {
     assert getBackend() == DEX;
     Path tmp = state.getNewTempFolder();
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
index efd4467..44e3756 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
@@ -37,13 +37,16 @@
   private static Class<?> BAR_CLASS = CompatKeepClassMemberNamesTest.Bar.class;
   private static Collection<Class<?>> CLASSES = ImmutableList.of(MAIN_CLASS, BAR_CLASS);
 
-  private static String EXPLICIT_RULE =
+  private static String KEEP_RULE =
       "class "
           + Bar.class.getTypeName()
           + " { static "
           + Bar.class.getTypeName()
           + " instance(); void <init>(); int i; }";
 
+  private static String KEEP_RULE_NON_STATIC =
+      "class " + Bar.class.getTypeName() + " { void <init>(); int i; }";
+
   private static String EXPECTED = StringUtils.lines("42", "null");
 
   @Parameters(name = "{0}")
@@ -84,6 +87,7 @@
   }
 
   private static void assertBarGetInstanceIsNotInlined(CodeInspector inspector) {
+    assertTrue(inspector.clazz(BAR_CLASS).uniqueMethodWithName("instance").isPresent());
     assertTrue(
         inspector
             .clazz(MAIN_CLASS)
@@ -142,6 +146,7 @@
               assertTrue(inspector.clazz(BAR_CLASS).isPresent());
               assertBarGetInstanceIsNotInlined(inspector);
               assertTrue(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
+              assertTrue(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
             })
         .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED);
@@ -159,11 +164,60 @@
   }
 
   @Test
-  @Ignore("b/119076934")
-  // TODO(b/119076934): Fails because the compat rule is applied regardless of mode, keeping Bar.
   public void testWithMembersRuleFullR8() throws Exception {
-    // In full mode for R8 we do *not* expect a -keepclassmembers to cause retention of the class.
-    assertBarIsAbsent(buildWithMembersRule(testForR8(parameters.getBackend())).compile());
+    // When a class is only referenced (not instantiated), full mode R8 will only keep the static
+    // members specified by the -keepclassmembers rule.
+    buildWithMembersRule(testForR8(parameters.getBackend()))
+        .compile()
+        .inspect(
+            inspector -> {
+              assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
+              assertTrue(inspector.clazz(BAR_CLASS).isPresent());
+              assertBarGetInstanceIsNotInlined(inspector);
+              assertFalse(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
+              assertFalse(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
+            });
+  }
+
+  @Test
+  public void testWithMembersRuleAndKeepBarRuleFullR8() throws Exception {
+    // If we keep the Bar class too, we get the same behavior in full as in PG/Compat.
+    assertMembersRuleCompatResult(
+        buildWithMembersRule(testForR8(parameters.getBackend()))
+            .addKeepClassRules(BAR_CLASS)
+            .compile());
+  }
+
+  // Tests for non-static -keepclassmembers and *no* minification.
+
+  private <
+          C extends BaseCompilerCommand,
+          B extends BaseCompilerCommand.Builder<C, B>,
+          CR extends TestCompileResult<CR, RR>,
+          RR extends TestRunResult<RR>,
+          T extends TestShrinkerBuilder<C, B, CR, RR, T>>
+      T buildWithNonStaticMembersRule(TestShrinkerBuilder<C, B, CR, RR, T> builder) {
+    return builder
+        .addProgramClasses(CLASSES)
+        .addKeepMainRule(MAIN_CLASS)
+        .addKeepRules("-keepclassmembers " + KEEP_RULE_NON_STATIC)
+        .noMinification();
+  }
+
+  @Test
+  public void testWithNonStaticMembersRulePG() throws Exception {
+    assertBarIsAbsent(buildWithNonStaticMembersRule(testForProguard()).compile());
+  }
+
+  @Test
+  public void testWithNonStaticMembersRuleCompatR8() throws Exception {
+    assertBarIsAbsent(
+        buildWithNonStaticMembersRule(testForR8Compat(parameters.getBackend())).compile());
+  }
+
+  @Test
+  public void testWithNonStaticMembersRuleFullR8() throws Exception {
+    assertBarIsAbsent(buildWithNonStaticMembersRule(testForR8(parameters.getBackend())).compile());
   }
 
   // Tests for -keepclassmembers and minification.
@@ -178,7 +232,7 @@
     return builder
         .addProgramClasses(CLASSES)
         .addKeepMainRule(MAIN_CLASS)
-        .addKeepRules("-keepclassmembers " + EXPLICIT_RULE);
+        .addKeepRules("-keepclassmembers " + KEEP_RULE);
   }
 
   private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>>
@@ -221,11 +275,19 @@
   }
 
   @Test
-  @Ignore("b/119076934")
-  // TODO(b/119076934): Fails because the compat rule is applied regardless of mode, keeping Bar.
   public void testWithMembersRuleEnableMinificationFullR8() throws Exception {
-    assertBarIsAbsent(
-        buildWithMembersRuleEnableMinification(testForR8(parameters.getBackend())).compile());
+    // When a class is only referenced (not instantiated), full mode R8 will only keep the static
+    // members specified by the -keepclassmembers rule.
+    buildWithMembersRuleEnableMinification(testForR8(parameters.getBackend()))
+        .compile()
+        .inspect(
+            inspector -> {
+              assertTrue(inspector.clazz(MAIN_CLASS).isPresent());
+              assertTrue(inspector.clazz(BAR_CLASS).isPresent());
+              assertBarGetInstanceIsNotInlined(inspector);
+              assertFalse(inspector.clazz(BAR_CLASS).uniqueFieldWithName("i").isPresent());
+              assertFalse(inspector.clazz(BAR_CLASS).uniqueMethodWithName("<init>").isPresent());
+            });
   }
 
   // Tests for "-keepclassmembers class Bar", i.e, with no members specified.
@@ -325,7 +387,7 @@
     return builder
         .addProgramClasses(CLASSES)
         .addKeepMainRule(MAIN_CLASS)
-        .addKeepRules("-keepclassmembernames " + EXPLICIT_RULE);
+        .addKeepRules("-keepclassmembernames " + KEEP_RULE);
   }
 
   private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>>
diff --git a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
index e24eb44..c1b4039 100644
--- a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
+++ b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
@@ -44,9 +44,12 @@
     Class<?> mainClass = genericTypeLive ? MainGenericTypeLive.class : MainGenericTypeNotLive.class;
     testForR8(backend)
         .minification(minify)
-        .addProgramClasses(A.class, GenericType.class, mainClass)
+        .addProgramClasses(GetClassUtil.class, A.class, GenericType.class, mainClass)
         .addKeepMainRule(mainClass)
         .addKeepRules(
+            "-keep class " + GetClassUtil.class.getTypeName() + " {",
+            "  static java.lang.Class getClass(java.lang.Object);",
+            "}",
             "-keepclassmembers @" + Marker.class.getTypeName() + " class * {",
             "  <fields>;",
             "}",
@@ -96,17 +99,25 @@
 
 }
 
+// GetClassUtil is used below to ensure that the types remain instantiated.
+class GetClassUtil {
+
+  public static Class<?> getClass(Object o) {
+    return o.getClass();
+  }
+}
+
 class MainGenericTypeNotLive {
 
   public static void main(String[] args) {
-    System.out.println(A.class);
+    System.out.println(GetClassUtil.getClass(new A()));
   }
 }
 
 class MainGenericTypeLive {
 
   public static void main(String[] args) {
-    System.out.println(A.class);
-    System.out.println(GenericType.class);
+    System.out.println(GetClassUtil.getClass(new A()));
+    System.out.println(GetClassUtil.getClass(new GenericType()));
   }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
index 3737ea0..f1d0cda 100644
--- a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
@@ -3,63 +3,71 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.b113138046;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.BooleanUtils;
 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 com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class NativeMethodTest extends TestBase {
 
-  private void test(List<String> config) throws Exception {
-    R8Command.Builder builder = R8Command.builder();
-    List<Path> classes = ToolHelper.getClassFilesForTestDirectory(
-        ToolHelper.getPackageDirectoryForTestPackage(Outer.class.getPackage()),
-        p -> !p.getFileName().toString().startsWith(this.getClass().getSimpleName()));
-    builder.addProgramFiles(classes);
-    builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
-    builder.addProguardConfiguration(config, Origin.unknown());
-    Path appOut = temp.newFile("out.zip").toPath();
-    builder.setOutput(appOut, OutputMode.DexIndexed);
-    AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
-      options.enableInlining = false;
-    });
-    Path oatOut = temp.newFile("out.oat").toPath();
-    ToolHelper.runDex2Oat(appOut, oatOut);
+  @Parameters(name = "{0}, compat:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        BooleanUtils.values());
+  }
 
-    CodeInspector inspector = new CodeInspector(processedApp);
+  private final TestParameters parameters;
+  private final boolean compatMode;
+
+  public NativeMethodTest(TestParameters parameters, boolean compatMode) {
+    this.parameters = parameters;
+    this.compatMode = compatMode;
+  }
+
+  private void test(List<String> config, boolean expectedFooPresence) throws Exception {
+    R8TestCompileResult compileResult =
+        (compatMode ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClassesAndInnerClasses(Keep.class, Data.class, Handler.class, Outer.class)
+            .addKeepRules(config)
+            .addOptionsModification(options -> options.enableInlining = false)
+            .compile();
+    compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
+    CodeInspector inspector = compileResult.inspector();
     boolean innerFound = false;
     for (ClassSubject clazz : inspector.allClasses()) {
       innerFound = clazz.getOriginalName().endsWith("Inner");
       if (!innerFound) {
         continue;
       }
-      MethodSubject nativeFoo = clazz.method("void", "foo",
-          ImmutableList.of(Handler.class.getCanonicalName()));
-      assertThat(nativeFoo, isPresent());
-      assertThat(nativeFoo, not(isRenamed()));
-      DexEncodedMethod method = nativeFoo.getMethod();
-      assertTrue(method.accessFlags.isNative());
-      assertNull(method.getCode());
+      MethodSubject nativeFoo = clazz.method(
+          "void", "foo", ImmutableList.of(Handler.class.getCanonicalName()));
+      assertEquals(expectedFooPresence, nativeFoo.isPresent());
+      if (expectedFooPresence) {
+        assertThat(nativeFoo, not(isRenamed()));
+        DexEncodedMethod method = nativeFoo.getMethod();
+        assertTrue(method.accessFlags.isNative());
+        assertNull(method.getCode());
+      }
       break;
     }
     assertTrue(innerFound);
@@ -80,7 +88,7 @@
         "-printmapping",
         "-keepattributes InnerClasses,EnclosingMethod,Signature",
         "-allowaccessmodification");
-    test(config);
+    test(config, compatMode);
   }
 
   @Test
@@ -101,6 +109,6 @@
         "-printmapping",
         "-keepattributes InnerClasses,EnclosingMethod,Signature",
         "-allowaccessmodification");
-    test(config);
+    test(config, true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index fc6738b..5c74e28 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.DataResource;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -49,6 +50,11 @@
           || this == R8_CF;
     }
 
+    public boolean isFullModeR8() {
+      return this == R8
+          || this == R8_CF;
+    }
+
     public boolean generatesDex() {
       return this == PROGUARD6_THEN_D8
           || this == R8_COMPAT
@@ -192,6 +198,7 @@
       Backend backend)
       throws Exception {
     CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
+    builder.setProguardMapConsumer(StringConsumer.emptyConsumer());
     ToolHelper.allowTestProguardOptions(builder);
     builder.addProguardConfiguration(
         ImmutableList.of(proguardConfig, toPrintMappingRule(proguardMap)), Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index 6dc7ac0..bc3c3bd 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -287,6 +287,18 @@
         "  java.lang.Object readResolve();",
         "}");
 
+    if (shrinker.isFullModeR8()) {
+      // R8 full mode does not keep the default constructor unless explicitly specified.
+      config = ImmutableList.<String>builder()
+          .addAll(config)
+          .addAll(ImmutableList.of(
+              "-keepclassmembers class * implements java.io.Externalizable {",
+              "  public void <init>();",
+              "}"
+          ))
+          .build();
+    }
+
     AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_EXTERNALIZABLE, config);
 
     // TODO(b/117302947): Need to update ART binary.
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index 9596513..ba67472 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.graph.invokesuper.Consumer;
@@ -69,7 +70,8 @@
 
   @Parameters(name = "shrinker: {0}")
   public static Collection<Object> data() {
-    return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+    return ImmutableList.of(
+        Shrinker.PROGUARD6, Shrinker.R8_COMPAT, Shrinker.R8_COMPAT_CF, Shrinker.R8, Shrinker.R8_CF);
   }
 
   public B115867670(Shrinker shrinker) {
@@ -101,6 +103,7 @@
     for (Class clazz : new Class[] {Foo.class, Foo.Interaction.class, Foo.Request.class}) {
       ClassSubject cls = inspector.clazz(clazz);
       assertThat(cls, isPresent());
+      assertFalse("Class " + clazz.getSimpleName() + " should not be abstract", cls.isAbstract());
       assertEquals(1, cls.asFoundClassSubject().allFields().size());
       cls.forAllFields(field -> assertThat(field, not(isRenamed())));
     }
@@ -112,6 +115,7 @@
       ClassSubject cls = inspector.clazz(clazz);
       assertThat(cls, isPresent());
       assertThat(cls, isRenamed());
+      assertFalse("Class " + clazz.getSimpleName() + " should not be abstract", cls.isAbstract());
       assertEquals(1, cls.asFoundClassSubject().allFields().size());
       cls.forAllFields(field -> assertThat(field, isRenamed()));
     }
@@ -139,44 +143,71 @@
   @Test
   public void testDependentWithKeepClassMembers() throws Exception {
     runTest(
-        "-keepclassmembers @" + pkg + ".JsonClass class ** { <fields>; }",
+        // In full mode, keepclassmembers does not imply the class is instantiated.
+        addFullModeKeepClassIfReferenced(
+            "-keepclassmembers @" + pkg + ".JsonClass class ** { <fields>; }"),
         this::checkKeepClassMembers);
   }
 
   @Test
   public void testDependentWithKeepClassMembersAllowObfuscation() throws Exception {
     runTest(
-        "-keepclassmembers,allowobfuscation @" + pkg + ".JsonClass class ** { <fields>; }",
+        // In full mode, keepclassmembers does not imply the class is instantiated.
+        addFullModeKeepClassIfReferenced(
+            "-keepclassmembers,allowobfuscation @" + pkg + ".JsonClass class ** { <fields>; }"),
         this::checkKeepClassMembersRenamed);
   }
 
   @Test
   public void testDependentWithIfKeepClassMembers() throws Exception {
     runTest(
-        "-if @" + pkg + ".JsonClass class * -keepclassmembers class <1> { <fields>; }",
+        // In full mode, keepclassmembers does not imply the class is instantiated.
+        addFullModeKeepClassIfReferenced(
+            "-if @" + pkg + ".JsonClass class * -keepclassmembers class <1> { <fields>; }"),
         this::checkKeepClassMembers);
   }
 
   @Test
   public void testDependentWithIfKeepClassMembersAllowObfuscation() throws Exception {
     runTest(
+        // In full mode, keepclassmembers does not imply the class is instantiated.
+        addFullModeKeepClassIfReferenced(
         "-if @"
             + pkg
-            + ".JsonClass class * -keepclassmembers,allowobfuscation class <1> { <fields>; }",
+            + ".JsonClass class * -keepclassmembers,allowobfuscation class <1> { <fields>; }"),
         this::checkKeepClassMembersRenamed);
   }
 
   @Test
   public void testDependentWithIfKeep() throws Exception {
     runTest(
-        "-if @" + pkg + ".JsonClass class * -keep class <1> { <fields>; }",
+        addFullModeKeepAllAttributes(
+            "-if @" + pkg + ".JsonClass class * -keep class <1> { <fields>; }"),
         this::checkKeepClassMembers);
   }
 
   @Test
   public void testDependentWithIfKeepAllowObfuscation() throws Exception {
     runTest(
-        "-if @" + pkg + ".JsonClass class * -keep,allowobfuscation class <1> { <fields>; }",
+        addFullModeKeepAllAttributes(
+            "-if @" + pkg + ".JsonClass class * -keep,allowobfuscation class <1> { <fields>; }"),
         this::checkKeepClassMembersRenamed);
   }
+
+  // In full mode we need to explicitly keep the class (mark instantiated) if it is referenced.
+  String addFullModeKeepClassIfReferenced(String rules) {
+    return shrinker.isFullModeR8()
+        ? addFullModeKeepAllAttributes(rules
+            + "\n-if @"
+            + JsonClass.class.getTypeName()
+            + " class * -keep,allowobfuscation class <1>")
+        : rules;
+  }
+
+  // TODO(b/132318609): Keep the attributes. Potentially remove this once resolved.
+  String addFullModeKeepAllAttributes(String rules) {
+    return shrinker.isFullModeR8()
+        ? rules + "\n-keepattributes *"
+        : rules;
+  }
 }