Only include default <init>() for empty member rules in compat mode

RELNOTES:

This fixes a bug in R8 full mode where keep rules with absent or empty member rules would implicitly be converted into a stronger keep rule that has a member rule matching the default constructor.

Example:

1. The rule -keep class A would implicitly be converted to -keep class A { void <init>(); }.

2. The rule -keep class A {} would implicitly be converted to -keep class A { void <init>(); }.


For consistency with the R8 full mode documentation in compatibility-faq.md ("The default constructor (<init>()) is not implicitly kept when a class is kept."), this change limits the implicit inclusion of void <init>() to Proguard compatibility mode.

Note that starting with this change, -keepclassmembers rules with no member rules (e.g., -keepclassmembers class A) do no longer have any effect in R8 full mode.

Fixes: b/132318799
Fixes: b/323136645
Change-Id: Ic6049e561bcd35b7a45ee9c8c5b94e1780e538a1
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 819b349..adfaf29 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -669,11 +669,13 @@
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(
               factory, reporter, parserOptionsBuilder.build(), inputDependencyGraphConsumer);
+      ProguardConfiguration.Builder configurationBuilder =
+          parser
+              .getConfigurationBuilder()
+              .setForceProguardCompatibility(forceProguardCompatibility);
       if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
       }
-      ProguardConfiguration.Builder configurationBuilder = parser.getConfigurationBuilder();
-      configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
 
       if (getMode() == CompilationMode.DEBUG) {
         disableMinification = true;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 98bb147..165e520 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -273,8 +273,13 @@
       keepDirectories.addPattern(pattern);
     }
 
-    public void setForceProguardCompatibility(boolean forceProguardCompatibility) {
+    public boolean isForceProguardCompatibility() {
+      return forceProguardCompatibility;
+    }
+
+    public Builder setForceProguardCompatibility(boolean forceProguardCompatibility) {
       this.forceProguardCompatibility = forceProguardCompatibility;
+      return this;
     }
 
     public void setConfigurationDebugging(boolean configurationDebugging) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index b2dcea5..d59e2ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -846,15 +846,21 @@
           .setStart(start);
       parseRuleTypeAndModifiers(keepRuleBuilder);
       parseClassSpec(keepRuleBuilder);
-      if (keepRuleBuilder.getMemberRules().isEmpty()) {
-        // If there are no member rules, a default rule for the parameterless constructor
-        // applies. So we add that here.
-        ProguardMemberRule.Builder defaultRuleBuilder = ProguardMemberRule.builder();
-        defaultRuleBuilder.setName(
-            IdentifierPatternWithWildcards.withoutWildcards(Constants.INSTANCE_INITIALIZER_NAME));
-        defaultRuleBuilder.setRuleType(ProguardMemberType.INIT);
-        defaultRuleBuilder.setArguments(Collections.emptyList());
-        keepRuleBuilder.getMemberRules().add(defaultRuleBuilder.build());
+      if (configurationBuilder.isForceProguardCompatibility()
+          && keepRuleBuilder.getMemberRules().isEmpty()
+          && keepRuleBuilder.getKeepRuleType() != ProguardKeepRuleType.KEEP_CLASSES_WITH_MEMBERS) {
+        // If there are no member rules, a default rule for the parameterless constructor applies in
+        // compatibility mode.
+        keepRuleBuilder
+            .getMemberRules()
+            .add(
+                ProguardMemberRule.builder()
+                    .setName(
+                        IdentifierPatternWithWildcards.withoutWildcards(
+                            Constants.INSTANCE_INITIALIZER_NAME))
+                    .setRuleType(ProguardMemberType.INIT)
+                    .setArguments(Collections.emptyList())
+                    .build());
       }
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
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..308ab6e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -22,6 +22,10 @@
       super();
     }
 
+    public ProguardKeepRuleType getKeepRuleType() {
+      return type;
+    }
+
     public B setType(ProguardKeepRuleType type) {
       this.type = type;
       return self();
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 1bd723a..bb999d8 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
@@ -333,7 +333,8 @@
 
   @Test
   public void testWithMembersStarRuleFullR8() throws Exception {
-    testWithMembersStarRule(testForR8(parameters.getBackend()));
+    testWithMembersStarRule(
+        testForR8(parameters.getBackend()).allowUnusedProguardConfigurationRules());
   }
 
   // Tests for "-keepclassmembernames" and *no* minification.
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java
index 08fde0f..16bc26e 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesExtendsTest.java
@@ -40,7 +40,7 @@
   @Parameter(2)
   public boolean repackage;
 
-  static final String EXPECTED = StringUtils.lines("Success!");
+  static final String EXPECTED = StringUtils.lines("Sub1", "Sub2");
 
   @Parameters(name = "{0}, keepPermittedSubclasses = {1}, repackage = {2}")
   public static List<Object[]> data() {
@@ -131,15 +131,26 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new Sub1();
-      new Sub2();
-      System.out.println("Success!");
+      System.out.println(new Sub1());
+      System.out.println(new Sub2());
     }
   }
 
   public abstract static class Super /* permits Sub1, Sub2 */ {}
 
-  public static class Sub1 extends Super {}
+  public static class Sub1 extends Super {
 
-  public static class Sub2 extends Super {}
+    @Override
+    public String toString() {
+      return "Sub1";
+    }
+  }
+
+  public static class Sub2 extends Super {
+
+    @Override
+    public String toString() {
+      return "Sub2";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesHorizontalMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesHorizontalMergeTest.java
index 270637d..9366ecf 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesHorizontalMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesHorizontalMergeTest.java
@@ -29,7 +29,7 @@
   @Parameter(0)
   public TestParameters parameters;
 
-  static final String EXPECTED = StringUtils.lines("Success!");
+  static final String EXPECTED = StringUtils.lines("Sub1", "Sub2");
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -62,11 +62,10 @@
         .addKeepClassRulesWithAllowObfuscation(Super.class)
         .addKeepMainRule(TestClass.class)
         .addHorizontallyMergedClassesInspector(
-            inspector -> {
-              inspector
-                  .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
-                  .assertNoOtherClassesMerged();
-            })
+            inspector ->
+                inspector
+                    .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
+                    .assertNoOtherClassesMerged())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
@@ -85,15 +84,26 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new Sub1();
-      new Sub2();
-      System.out.println("Success!");
+      System.out.println(new Sub1());
+      System.out.println(new Sub2());
     }
   }
 
   abstract static class Super /* permits Sub1, Sub2 */ {}
 
-  static class Sub1 extends Super {}
+  static class Sub1 extends Super {
 
-  static class Sub2 extends Super {}
+    @Override
+    public String toString() {
+      return "Sub1";
+    }
+  }
+
+  static class Sub2 extends Super {
+
+    @Override
+    public String toString() {
+      return "Sub2";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
index b420268..7d5eefa 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassMergedTest.java
@@ -34,7 +34,7 @@
 
   static final Matcher<String> EXPECTED = containsString("cannot inherit from sealed class");
   static final String EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE_OR_FIXED_ATTRIBUTE =
-      StringUtils.lines("Success!");
+      StringUtils.lines("Sub1", "Sub2");
 
   @Parameters(name = "{0}, keepPermittedSubclasses = {1}")
   public static TestParametersCollection data() {
@@ -101,15 +101,26 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new Sub1();
-      new Sub2();
-      System.out.println("Success!");
+      System.out.println(new Sub1());
+      System.out.println(new Sub2());
     }
   }
 
   abstract static class Super /* permits Sub1 */ {}
 
-  static class Sub1 extends Super {}
+  static class Sub1 extends Super {
 
-  static class Sub2 extends Super {}
+    @Override
+    public String toString() {
+      return "Sub1";
+    }
+  }
+
+  static class Sub2 extends Super {
+
+    @Override
+    public String toString() {
+      return "Sub2";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
index 240b7a9..cf6aef5 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
@@ -42,7 +42,7 @@
 
   static final Matcher<String> EXPECTED = containsString("cannot inherit from sealed class");
   static final String EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE =
-      StringUtils.lines("Success!");
+      StringUtils.lines("Sub1", "Sub2", "Sub3");
 
   @Parameters(name = "{0}, keepPermittedSubclasses = {1}")
   public static List<Object[]> data() {
@@ -135,18 +135,35 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new Sub1();
-      new Sub2();
-      new Sub3();
-      System.out.println("Success!");
+      System.out.println(new Sub1());
+      System.out.println(new Sub2());
+      System.out.println(new Sub3());
     }
   }
 
   abstract static class Super /* permits Sub1, Sub2 */ {}
 
-  static class Sub1 extends Super {}
+  static class Sub1 extends Super {
 
-  static class Sub2 extends Super {}
+    @Override
+    public String toString() {
+      return "Sub1";
+    }
+  }
 
-  static class Sub3 extends Super {}
+  static class Sub2 extends Super {
+
+    @Override
+    public String toString() {
+      return "Sub2";
+    }
+  }
+
+  static class Sub3 extends Super {
+
+    @Override
+    public String toString() {
+      return "Sub3";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsTest.java
index ac61cf8..a275617 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesImplementsTest.java
@@ -38,7 +38,7 @@
   @Parameter(1)
   public boolean keepPermittedSubclassesAttribute;
 
-  static final String EXPECTED = StringUtils.lines("Success!");
+  static final String EXPECTED = StringUtils.lines("Sub1", "Sub2");
 
   @Parameters(name = "{0}, keepPermittedSubclasses = {1}")
   public static List<Object[]> data() {
@@ -137,9 +137,8 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new Sub1();
-      new Sub2();
-      System.out.println("Success!");
+      System.out.println(new Sub1());
+      System.out.println(new Sub2());
     }
   }
 
@@ -147,7 +146,19 @@
 
   interface Iface2 /* permits Sub1, Sub2 */ {}
 
-  static class Sub1 implements Iface1, Iface2 {}
+  static class Sub1 implements Iface1, Iface2 {
 
-  static class Sub2 implements Iface1 {}
+    @Override
+    public String toString() {
+      return "Sub1";
+    }
+  }
+
+  static class Sub2 implements Iface1 {
+
+    @Override
+    public String toString() {
+      return "Sub2";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java
index bcd774a..74ee559 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java
@@ -29,7 +29,7 @@
   @Parameter(0)
   public TestParameters parameters;
 
-  static final String EXPECTED = StringUtils.lines("Success!");
+  static final String EXPECTED = StringUtils.lines("Sub1", "Sub2");
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -62,11 +62,10 @@
         .addKeepClassRulesWithAllowObfuscation(Super.class)
         .addKeepMainRule(TestClass.class)
         .addHorizontallyMergedClassesInspector(
-            inspector -> {
-              inspector
-                  .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
-                  .assertNoOtherClassesMerged();
-            })
+            inspector ->
+                inspector
+                    .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
+                    .assertNoOtherClassesMerged())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
@@ -85,15 +84,26 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new Sub1();
-      new Sub2();
-      System.out.println("Success!");
+      System.out.println(new Sub1());
+      System.out.println(new Sub2());
     }
   }
 
   abstract static class Super /* permits Sub1, Sub2 */ {}
 
-  static class Sub1 extends Super {}
+  static class Sub1 extends Super {
 
-  static class Sub2 extends Super {}
+    @Override
+    public String toString() {
+      return "Sub1";
+    }
+  }
+
+  static class Sub2 extends Super {
+
+    @Override
+    public String toString() {
+      return "Sub2";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java
index 1893e3b..a5a68a0 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java
@@ -79,14 +79,19 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new UsedSub();
-      System.out.println("Success!");
+      System.out.println(new UsedSub());
     }
   }
 
   abstract static class Super /* permits UsedSub, UnusedSub */ {}
 
-  static class UsedSub extends Super {}
+  static class UsedSub extends Super {
+
+    @Override
+    public String toString() {
+      return "Success!";
+    }
+  }
 
   static class UnusedSub extends Super {}
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTestAllowPermittedSubclassesRemovalTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTestAllowPermittedSubclassesRemovalTest.java
index c220e9d..4b96a42 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTestAllowPermittedSubclassesRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTestAllowPermittedSubclassesRemovalTest.java
@@ -30,7 +30,7 @@
   @Parameter(0)
   public TestParameters parameters;
 
-  static final String EXPECTED = StringUtils.lines("Success!");
+  static final String EXPECTED = StringUtils.lines("Sub1", "Sub2");
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -85,15 +85,26 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      new Sub1();
-      new Sub2();
-      System.out.println("Success!");
+      System.out.println(new Sub1());
+      System.out.println(new Sub2());
     }
   }
 
   abstract static class Super /* permits Sub1, Sub2 */ {}
 
-  static class Sub1 extends Super {}
+  static class Sub1 extends Super {
 
-  static class Sub2 extends Super {}
+    @Override
+    public String toString() {
+      return "Sub1";
+    }
+  }
+
+  static class Sub2 extends Super {
+
+    @Override
+    public String toString() {
+      return "Sub2";
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
index bf5614e..5c94d28 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -66,8 +66,8 @@
             testBuilder ->
                 testBuilder.addLibraryFiles(
                     parameters.getDefaultAndroidJarAbove(AndroidApiLevel.K)));
-    assertEquals(processResult.exitCode, 0);
-    assertEquals(processResult.stdout, EXPECTED);
+    assertEquals(processResult.stdout + processResult.stderr, 0, processResult.exitCode);
+    assertEquals(EXPECTED, processResult.stdout);
   }
 
   public abstract static class BaseSuperClass implements RunInterface {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java
index be84e82..0fd3754 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java
@@ -57,7 +57,7 @@
             .enableInliningAnnotations()
             .addProgramClasses(KeptClass.class, Main.class)
             .addKeepMainRule(Main.class)
-            .addKeepClassRules(KeptClass.class)
+            .addKeepClassAndDefaultConstructor(KeptClass.class)
             .setMinApi(parameters)
             .run(parameters.getRuntime(), Main.class)
             .assertSuccessWithOutputLines("used()")
diff --git a/src/test/java/com/android/tools/r8/keepanno/compatissues/EmptyClassWithMembersRuleTest.java b/src/test/java/com/android/tools/r8/keepanno/compatissues/EmptyClassWithMembersRuleTest.java
index 3b1ef5f..1f16eb9 100644
--- a/src/test/java/com/android/tools/r8/keepanno/compatissues/EmptyClassWithMembersRuleTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/compatissues/EmptyClassWithMembersRuleTest.java
@@ -9,10 +9,12 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.StringUtils;
@@ -30,11 +32,16 @@
 
   public enum Shrinker {
     PG,
-    R8;
+    R8,
+    R8_COMPAT;
 
     public boolean isPG() {
       return this == PG;
     }
+
+    public boolean isCompat() {
+      return this == R8_COMPAT;
+    }
   }
 
   @Parameter(0)
@@ -48,12 +55,14 @@
     return buildParameters(getTestParameters().withDefaultCfRuntime().build(), Shrinker.values());
   }
 
-  private TestRunResult<?> runTest(String... keepRules) throws Exception {
+  private TestRunResult<?> runTest(
+      ThrowableConsumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> configuration) throws Exception {
     TestShrinkerBuilder<?, ?, ?, ?, ?> builder;
     if (shrinker.isPG()) {
-      builder = testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass());
+      builder =
+          testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass()).apply(configuration);
     } else {
-      builder = testForR8(parameters.getBackend()).allowUnusedProguardConfigurationRules();
+      builder = testForR8Compat(parameters.getBackend(), shrinker.isCompat()).apply(configuration);
     }
     return builder
         .addProgramClassFileData(
@@ -63,48 +72,61 @@
                 .transform())
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepRules(keepRules)
         .run(parameters.getRuntime(), TestClass.class);
   }
 
   @Test
   public void testNoKeep() throws Exception {
-    runTest("-keep class missingClassToForceUnusedRuleDiagnostic")
+    runTest(
+            builder ->
+                builder
+                    .addKeepRules("-keep class missingClassToForceUnusedRuleDiagnostic")
+                    .applyIfR8(R8TestBuilder::allowUnusedProguardConfigurationRules))
         .assertSuccessWithOutput(EXPECTED_RENAMED)
         .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresentAndRenamed()));
   }
 
   @Test
   public void testNoMembers() throws Exception {
-    // TODO(b/323136645): This is incorrect behavior for R8.
-    //  R8 implicitly replaces the member rule with a member rule for <init>() which does not match.
-    runTest("-keepclasseswithmembers class " + typeName(A.class) + " { }")
-        .assertSuccessWithOutput(shrinker.isPG() ? EXPECTED_KEPT : EXPECTED_RENAMED)
-        .inspect(
-            inspector ->
-                assertThat(
-                    inspector.clazz(A.class),
-                    shrinker.isPG() ? isPresentAndNotRenamed() : isPresentAndRenamed()));
+    runTest(
+            builder ->
+                builder.addKeepRules("-keepclasseswithmembers class " + typeName(A.class) + " {}"))
+        .assertSuccessWithOutput(EXPECTED_KEPT)
+        .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresentAndNotRenamed()));
   }
 
   @Test
   public void testAllMembers() throws Exception {
     // Surprising, but consistent for PG and R8, "any member" pattern does not match an empty class.
-    runTest("-keepclasseswithmembers class " + typeName(A.class) + " { *; }")
+    runTest(
+            builder ->
+                builder
+                    .addKeepRules("-keepclasseswithmembers class " + typeName(A.class) + " { *; }")
+                    .applyIfR8(R8TestBuilder::allowUnusedProguardConfigurationRules))
         .assertSuccessWithOutput(EXPECTED_RENAMED)
         .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresentAndRenamed()));
   }
 
   @Test
   public void testAllMethods() throws Exception {
-    runTest("-keepclasseswithmembers class " + typeName(A.class) + " { *** *(...); }")
+    runTest(
+            builder ->
+                builder
+                    .addKeepRules(
+                        "-keepclasseswithmembers class " + typeName(A.class) + " { *** *(...); }")
+                    .applyIfR8(R8TestBuilder::allowUnusedProguardConfigurationRules))
         .assertSuccessWithOutput(EXPECTED_RENAMED)
         .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresentAndRenamed()));
   }
 
   @Test
   public void testAllMethods2() throws Exception {
-    runTest("-keepclasseswithmembers class " + typeName(A.class) + " { <methods>; }")
+    runTest(
+            builder ->
+                builder
+                    .addKeepRules(
+                        "-keepclasseswithmembers class " + typeName(A.class) + " { <methods>; }")
+                    .applyIfR8(R8TestBuilder::allowUnusedProguardConfigurationRules))
         .assertSuccessWithOutput(EXPECTED_RENAMED)
         .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresentAndRenamed()));
   }
diff --git a/src/test/java/com/android/tools/r8/keepanno/compatissues/NonDefaultInitClassWithMembersRuleTest.java b/src/test/java/com/android/tools/r8/keepanno/compatissues/NonDefaultInitClassWithMembersRuleTest.java
index 944b39b..9b46168 100644
--- a/src/test/java/com/android/tools/r8/keepanno/compatissues/NonDefaultInitClassWithMembersRuleTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/compatissues/NonDefaultInitClassWithMembersRuleTest.java
@@ -87,15 +87,9 @@
 
   @Test
   public void testNoMembers() throws Exception {
-    // TODO(b/323136645): This is incorrect behavior for R8.
-    //  R8 implicitly replaces the member rule with a member rule for <init>() which does not match.
-    runTestAllowingUnusedRules("-keepclasseswithmembers class " + typeName(A.class) + " { }")
-        .assertSuccessWithOutput(shrinker.isPG() ? EXPECTED_KEPT : EXPECTED_RENAMED)
-        .inspect(
-            inspector ->
-                assertThat(
-                    inspector.clazz(A.class),
-                    shrinker.isPG() ? isPresentAndNotRenamed() : isPresentAndRenamed()));
+    runTest("-keepclasseswithmembers class " + typeName(A.class) + " { }")
+        .assertSuccessWithOutput(EXPECTED_KEPT)
+        .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresentAndNotRenamed()));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index 8731d5e..3ad68c9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -92,7 +92,7 @@
                 baseLibJar, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(extLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the Extra class and its interface (which has the method).
-            .addKeepRules("-keep class **.Extra")
+            .addKeepClassAndDefaultConstructor("**.Extra")
             // Keep Super, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Impl")
             // Keep the ImplKt extension method which requires metadata
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index 2c7400a..fe8c747 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -120,7 +120,7 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(companionLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .addKeepClassAndDefaultConstructor("**.B")
             // Property in companion with @JvmField is defined in the host class, without accessors.
             .addKeepRules("-keepclassmembers class **.B { *** elt2; }")
             .addKeepRules("-keep class **.I { <methods>; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index 73e4534..969f5a7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -103,7 +103,10 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(extLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .applyIf(
+                full,
+                b -> b.addKeepClassAndDefaultConstructor("**.B"),
+                b -> b.addKeepClassRules("**.B"))
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep the BKt extension function which requires metadata
             // to be called with Kotlin syntax from other kotlin code.
@@ -158,7 +161,7 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(extLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .addKeepClassAndDefaultConstructor("**.B")
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep Super, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Super")
@@ -198,7 +201,7 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(extLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .addKeepClassAndDefaultConstructor("**.B")
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep Super, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Super")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index a177f44..2780a6b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -93,7 +93,10 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(extLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .applyIf(
+                full,
+                b -> b.addKeepClassAndDefaultConstructor("**.B"),
+                b -> b.addKeepClassRules("**.B"))
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep the BKt extension property which requires metadata
             // to be called with Kotlin syntax from other kotlin code.
@@ -157,7 +160,7 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(extLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .addKeepClassAndDefaultConstructor("**.B")
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep Super, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Super")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index da87d5f..c6fa158 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -92,7 +92,10 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(funLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .applyIf(
+                full,
+                b -> b.addKeepClassAndDefaultConstructor("**.B"),
+                b -> b.addKeepClassRules("**.B"))
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep the BKt method, which will be called from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
@@ -154,7 +157,7 @@
             .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
             .addProgramFiles(funLibJarMap.getForConfiguration(kotlinParameters))
             // Keep the B class and its interface (which has the doStuff method).
-            .addKeepRules("-keep class **.B")
+            .addKeepClassAndDefaultConstructor("**.B")
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep Super, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Super { <methods>; }")
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 28b621a..cc150aa 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -68,11 +68,12 @@
         "iput-object v0, p0, LExample;->aClassName:Ljava/lang/String;",
         "return-void");
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
-        "-keep class " + CLASS_NAME,
-        "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
-        "-dontoptimize");
+    List<String> pgConfigs =
+        ImmutableList.of(
+            "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
+            "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
+            "-dontoptimize");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -101,11 +102,12 @@
         "iput-object v1, p0, LExample;->aClassName:Ljava/lang/String;",
         "return-void");
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
-        "-keep class " + CLASS_NAME,
-        "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
-        "-dontoptimize");
+    List<String> pgConfigs =
+        ImmutableList.of(
+            "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
+            "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
+            "-dontoptimize");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -140,12 +142,13 @@
         "return-void");
     builder.addClass(BOO);
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
-        "-keep class " + CLASS_NAME,
-        "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
-        "-keep,allowobfuscation class " + BOO,
-        "-dontoptimize");
+    List<String> pgConfigs =
+        ImmutableList.of(
+            "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
+            "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
+            "-keep,allowobfuscation class " + BOO,
+            "-dontoptimize");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -392,8 +395,8 @@
                         .addKeepRules(
                             "-identifiernamestring class "
                                 + CLASS_NAME
-                                + " { static void foo(...); }",
-                            "-keep class " + CLASS_NAME)
+                                + " { static void foo(...); }")
+                        .addKeepClassAndDefaultConstructor(CLASS_NAME)
                         .allowDiagnosticWarningMessages())
             .assertAllWarningMessagesMatch(
                 containsString("Cannot determine what 'Mixed/form.Boo' refers to"))
@@ -439,7 +442,7 @@
     List<String> pgConfigs =
         ImmutableList.of(
             "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
-            "-keep class " + CLASS_NAME,
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
             "-dontoptimize");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
@@ -483,7 +486,7 @@
     List<String> pgConfigs =
         ImmutableList.of(
             "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
-            "-keep class " + CLASS_NAME,
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
             "-keep,allowobfuscation class " + BOO,
             "-dontoptimize");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
@@ -534,12 +537,13 @@
         "move-result-object v0",
         "return-object v0");
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class * {\n"
-            + "  static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
-            + "}",
-        "-keep class " + CLASS_NAME,
-        "-keep class R { *; }");
+    List<String> pgConfigs =
+        ImmutableList.of(
+            "-identifiernamestring class * {\n"
+                + "  static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
+                + "}",
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
+            "-keep class R { *; }");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -584,12 +588,13 @@
         "move-result-object v0",
         "return-object v0");
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class * {\n"
-            + "  static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
-            + "}",
-        "-keep class " + CLASS_NAME,
-        "-keep,allowobfuscation class R { *; }");
+    List<String> pgConfigs =
+        ImmutableList.of(
+            "-identifiernamestring class * {\n"
+                + "  static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
+                + "}",
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
+            "-keep,allowobfuscation class R { *; }");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -640,13 +645,14 @@
         "move-result-object v0",
         "return-object v0");
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class * {\n"
-            + "  static java.lang.reflect.Method"
-            + "    *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
-            + "}",
-        "-keep class " + CLASS_NAME,
-        "-keep class R { *; }");
+    List<String> pgConfigs =
+        ImmutableList.of(
+            "-identifiernamestring class * {\n"
+                + "  static java.lang.reflect.Method"
+                + "    *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
+                + "}",
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
+            "-keep class R { *; }");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -715,13 +721,14 @@
         "move-result-object v0",
         "return-object v0");
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class * {\n"
-            + "  static java.lang.reflect.Method"
-            + "    *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
-            + "}",
-        "-keep class " + CLASS_NAME,
-        "-keep,allowobfuscation class R { *; }");
+    List<String> pgConfigs =
+        ImmutableList.of(
+            "-identifiernamestring class * {\n"
+                + "  static java.lang.reflect.Method"
+                + "    *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
+                + "}",
+            "-keep class " + CLASS_NAME + " { void <init>(); }",
+            "-keep,allowobfuscation class R { *; }");
     CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java
index a4bc84c..b3df30f 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithStringIdentifier.java
@@ -44,7 +44,7 @@
             "-identifiernamestring class "
                 + Main.class.getTypeName()
                 + " { java.lang.String name; }")
-        .addKeepRules("-keepclassmembers,allowshrinking class **")
+        .addKeepRules("-keepclassmembers,allowshrinking class * { void <init>(); }")
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 8df062f..b7c0d9a 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -144,7 +144,7 @@
   public void testSatisfiedClassMembersPresentAnnotation() throws Exception {
     testForR8(Backend.CF)
         .addProgramFiles(R8_JAR)
-        .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+        .addKeepClassAndDefaultConstructor(CLASS_WITH_ANNOTATED_METHOD)
         .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
         .compile()
         .inspect(
@@ -182,7 +182,7 @@
   public void testSatisfiedConditionalPresentAnnotation() throws Exception {
     testForR8(Backend.CF)
         .addProgramFiles(R8_JAR)
-        .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+        .addKeepClassAndDefaultConstructor(CLASS_WITH_ANNOTATED_METHOD)
         .addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
         .compile()
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java
index 902d86a..96aa8fd 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalKeepIfKeptTest.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -63,9 +62,7 @@
               ClassSubject classSubject = inspector.clazz(StaticallyReferenced.class);
               assertThat(classSubject, isPresent());
               assertEquals(0, classSubject.allFields().size());
-              // TODO(b/132318799): Should not keep <init>() when not specified.
-              assertEquals(1, classSubject.allMethods().size());
-              assertTrue(classSubject.init().isPresent());
+              assertEquals(0, classSubject.allMethods().size());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnStaticFinalFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnStaticFinalFieldTest.java
index 63d5746..824863c 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnStaticFinalFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnStaticFinalFieldTest.java
@@ -50,7 +50,7 @@
             "-keep class " + A.class.getTypeName())
         .addKeepRules(
             "-if class " + StaticNonFinalField.class.getTypeName() + " { int f; }",
-            "-keep class " + B.class.getTypeName())
+            "-keep class " + B.class.getTypeName() + " { void <init>(); }")
         .setMinApi(parameters)
         .allowDiagnosticMessages()
         .compileWithExpectedDiagnostics(
@@ -70,7 +70,9 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
-        .addKeepRules("-if class * { int f; } -keep class " + B.class.getTypeName())
+        .addKeepRules(
+            "-if class * { int f; }",
+            "-keep class " + B.class.getTypeName() + " { void <init>(); }")
         .setMinApi(parameters)
         .allowDiagnosticMessages()
         .compileWithExpectedDiagnostics(
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java
index 79ae86f..9fa36b4 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java
@@ -35,11 +35,8 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class, R.class, Layout.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepRules(
-            "-if class " + R.class.getTypeName() + " {",
-            "  static int ID;",
-            "}",
-            "-keep class " + Layout.class.getTypeName())
+        .addKeepRules("-if class " + R.class.getTypeName() + " {", "  static int ID;", "}")
+        .addKeepClassAndDefaultConstructor(Layout.class)
         .addLibraryClasses(Library.class)
         .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
index 270a837..e2b98e8 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
@@ -35,11 +35,8 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class, R.class, Layout.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepRules(
-            "-if class " + R.class.getTypeName() + " {",
-            "  static int id();",
-            "}",
-            "-keep class " + Layout.class.getTypeName())
+        .addKeepRules("-if class " + R.class.getTypeName() + " {", "  static int id();", "}")
+        .addKeepClassAndDefaultConstructor(Layout.class)
         .addLibraryClasses(Library.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
index 0258428..3187639 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
@@ -78,7 +78,7 @@
         .addClasspathClasses(I.class, J.class)
         .setMinApi(parameters)
         .addKeepMainRule(Main.class)
-        .addKeepClassRules(ImplJ.class)
+        .addKeepClassAndDefaultConstructor(ImplJ.class)
         .addRunClasspathFiles(compileResult.writeToZip())
         .run(parameters.getRuntime(), Main.class, ImplJ.class.getTypeName(), "foo")
         .assertFailureWithErrorThatMatches(
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java
index 9357d07..7d43997 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java
@@ -38,7 +38,7 @@
             .addProgramClasses(I.class, A.class, B.class)
             .setMinApi(parameters)
             .addKeepMethodRules(A.class, "void foo()")
-            .addKeepClassRules(B.class)
+            .addKeepClassAndDefaultConstructor(B.class)
             .compile();
     testForRuntime(parameters)
         .addProgramClasses(Main.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java
index a2fe6b9..f850ee5 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java
@@ -74,7 +74,7 @@
             .setMinApi(parameters)
             .addKeepMethodRules(libraryClassWithMethod, "void foo()");
     if (!libraryClassWithMethod.isInterface()) {
-      libraryBuilder.addKeepClassRules(libraryClassWithMethod);
+      libraryBuilder.addKeepClassAndDefaultConstructor(libraryClassWithMethod);
     }
     testForRuntime(parameters)
         .addProgramClasses(programClasses)
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java
index b8095e1..3cdcb56 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java
@@ -45,7 +45,7 @@
         .setMinApi(parameters)
         .addKeepMainRule(Main.class)
         .addKeepMethodRules(A.class, "void <init>()", "void foo()")
-        .addKeepClassRules(B.class)
+        .addKeepClassAndDefaultConstructor(B.class)
         .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
         .assertSuccessWithOutputLines("Hello World!")
         .inspect(
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index 32a40c0..201d674 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -42,7 +42,7 @@
     return fn.applyWithRuntimeException(self());
   }
 
-  public T apply(ThrowableConsumer<T> fn) {
+  public T apply(ThrowableConsumer<? super T> fn) {
     if (fn != null) {
       fn.acceptWithRuntimeException(self());
     }
@@ -85,6 +85,14 @@
     return self;
   }
 
+  public T applyIfR8(ThrowableConsumer<? super R8TestBuilder<?>> consumer) {
+    T self = self();
+    if (this instanceof R8TestBuilder<?>) {
+      consumer.acceptWithRuntimeException((R8TestBuilder<?>) self);
+    }
+    return self;
+  }
+
   @Deprecated
   public RR run(String mainClass)
       throws CompilationFailedException, ExecutionException, IOException {
diff --git a/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 1d8821e..8ce018e 100644
--- a/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -140,7 +140,7 @@
             .addInliningAnnotations()
             .setMinApi(parameters)
             .addKeepMainRule(SplitRunner.class)
-            .addKeepClassRules(toRun)
+            .addKeepClassAndDefaultConstructor(toRun)
             .apply(r8TestConfigurator)
             .compile()
             .apply(compileResultConsumer);