Don't optimize getClass on kept classes.

This also adds a test for a know type issue:
Bug: 154792347

Change-Id: If227e53ae97a8a87fdb31632e1f5073755a35a78
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 64a8c86..3b427cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -119,8 +119,7 @@
       return null;
     }
     // Only consider effectively final class. Exception: new Base().getClass().
-    if (appView.appInfo().hasSubtypes(baseType)
-        && appView.appInfo().isInstantiatedIndirectly(clazz)
+    if (!clazz.isEffectivelyFinal(appView)
         && (in.isPhi() || !in.definition.isCreatingInstanceOrArray())) {
       return null;
     }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 743a898..f172bb12f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1449,6 +1449,27 @@
     return buildOnDexRuntime(parameters, Arrays.asList(paths));
   }
 
+  public Path buildOnDexRuntime(TestParameters parameters, Class<?>... classes)
+      throws IOException, CompilationFailedException {
+    if (parameters.isDexRuntime()) {
+      return testForD8()
+          .addProgramClasses(classes)
+          .setMinApi(parameters.getApiLevel())
+          .compile()
+          .writeToZip();
+    }
+    Path path = temp.newFolder().toPath().resolve("classes.jar");
+    ArchiveConsumer consumer = new ArchiveConsumer(path);
+    for (Class clazz : classes) {
+      consumer.accept(
+          ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
+          DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()),
+          null);
+    }
+    consumer.finished(null);
+    return path;
+  }
+
   public static String binaryName(Class<?> clazz) {
     return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
   }
diff --git a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
new file mode 100644
index 0000000..81f1d09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, 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.cf;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MissingClassJoinsToObjectTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public MissingClassJoinsToObjectTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private List<Path> getRuntimeClasspath() throws Exception {
+    return buildOnDexRuntime(parameters, ToolHelper.getClassFileForTestClass(B.class));
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(TestClass.class, A.class, B.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .enableInliningAnnotations()
+            .addProgramClasses(TestClass.class, A.class)
+            .addKeepMainRule(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .addRunClasspathFiles(getRuntimeClasspath())
+            .run(parameters.getRuntime(), TestClass.class);
+    if (parameters.isCfRuntime()) {
+      // TODO(b/154792347): The analysis of types in the presence of undefined is incomplete.
+      result.assertFailureWithErrorThatThrows(VerifyError.class);
+    } else {
+      result.assertSuccessWithOutput(EXPECTED);
+    }
+  }
+
+  static class A {
+    @NeverInline
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  // Missing at compile time.
+  static class B extends A {
+    // Intentionally empty.
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Due to the missing class B, the join is assigned Object.
+      A join = args.length == 0 ? new A() : new B();
+      // The call to Object::foo fails.
+      join.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassBaseAndSubTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassBaseAndSubTest.java
new file mode 100644
index 0000000..b5daa52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassBaseAndSubTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, 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.ir.optimize.reflection;
+
+import static org.junit.Assert.assertFalse;
+
+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.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class GetClassBaseAndSubTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines("class " + Base.class.getTypeName(), "class " + Sub.class.getTypeName());
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public GetClassBaseAndSubTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(GetClassBaseAndSubTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .noMinification()
+        .addInnerClasses(GetClassBaseAndSubTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector ->
+                assertFalse(
+                    inspector
+                        .clazz(TestClass.class)
+                        .mainMethod()
+                        .streamInstructions()
+                        .anyMatch(InstructionSubject::isConstClass)));
+  }
+
+  static class Base {}
+
+  static class Sub extends Base {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Base baseWithBase = System.currentTimeMillis() > 0 ? new Base() : new Sub();
+      // Cannot be rewritten to const-class.
+      System.out.println(baseWithBase.getClass());
+      Base baseWithSub = System.currentTimeMillis() > 0 ? new Sub() : new Base();
+      // Cannot be rewritten to const-class.
+      System.out.println(baseWithSub.getClass());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassOnKeptClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassOnKeptClassTest.java
new file mode 100644
index 0000000..6e0e9e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassOnKeptClassTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, 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.ir.optimize.reflection;
+
+import com.android.tools.r8.NeverInline;
+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 java.util.concurrent.Callable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class GetClassOnKeptClassTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "class " + KeptClass.class.getTypeName(),
+          "class " + KeptClass.class.getTypeName(),
+          "class " + UnknownClass.class.getTypeName(),
+          "class " + UnknownClass.class.getTypeName());
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public GetClassOnKeptClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(KeptClass.class, UnknownClass.class, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .addProgramClasses(KeptClass.class, TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(KeptClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, UnknownClass.class))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  static class KeptClass implements Callable<Class<?>> {
+    @NeverInline
+    static Class<?> getClassMethod(KeptClass instance) {
+      // Nullable argument. Should not be rewritten to const-class to preserve NPE.
+      return instance.getClass();
+    }
+
+    @NeverInline
+    @Override
+    public Class<?> call() {
+      // Non-null `this` pointer.
+      return getClass();
+    }
+  }
+
+  static class UnknownClass extends KeptClass {
+    // Empty subtype of KeptClass.
+  }
+
+  static class TestClass {
+
+    static KeptClass getInstance(int i) throws Exception {
+      return i == 0
+          ? new KeptClass()
+          : (KeptClass)
+              Class.forName(TestClass.class.getName().replace("TestClass", "UnknownClass"))
+                  .getDeclaredConstructor()
+                  .newInstance();
+    }
+
+    public static void main(String[] args) throws Exception {
+      for (int i = 0; i < 2; i++) {
+        KeptClass instance = getInstance(args.length + i);
+        System.out.println(instance.call());
+        System.out.println(KeptClass.getClassMethod(instance));
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index c2fa7fb..7807a9f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -9,30 +9,34 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 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 com.google.common.collect.ImmutableList;
+import java.util.List;
 import java.util.concurrent.Callable;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-class GetClassTestMain implements Callable<Class<?>> {
+@RunWith(Parameterized.class)
+public class GetClassTest extends ReflectionOptimizerTestBase {
+
   static class Base {}
+
   static class Sub extends Base {}
+
   static class EffectivelyFinal {}
 
   static class Reflection implements Callable<Class<?>> {
+
     @ForceInline
     @Override
     public Class<?> call() {
@@ -40,96 +44,106 @@
     }
   }
 
-  @NeverInline
-  static Class<?> getMainClass(GetClassTestMain instance) {
-    // Nullable argument. Should not be rewritten to const-class to preserve NPE.
-    return instance.getClass();
+  static class GetClassTestMain implements Callable<Class<?>> {
+
+    @NeverInline
+    static Class<?> getMainClass(GetClassTestMain instance) {
+      // Nullable argument. Should not be rewritten to const-class to preserve NPE.
+      return instance.getClass();
+    }
+
+    @NeverInline
+    @Override
+    public Class<?> call() {
+      // Non-null `this` pointer.
+      return getClass();
+    }
   }
 
-  @NeverInline
-  @Override
-  public Class<?> call() {
-    // Non-null `this` pointer.
-    return getClass();
-  }
+  static class Main {
 
-  public static void main(String[] args) {
-    {
-      Base base = new Base();
-      // Not applicable in debug mode.
-      System.out.println(base.getClass());
-      // Can be rewritten to const-class always.
-      System.out.println(new Base().getClass());
-    }
+    public static void main(String[] args) {
+      {
+        Base base = new Base();
+        // Not applicable in debug mode.
+        System.out.println(base.getClass());
+        // Can be rewritten to const-class always.
+        System.out.println(new Base().getClass());
+      }
 
-    {
-      Base sub = new Sub();
-      // Not applicable in debug mode.
-      System.out.println(sub.getClass());
-    }
+      {
+        Base sub = new Sub();
+        // Not applicable in debug mode.
+        System.out.println(sub.getClass());
+      }
 
-    {
-      Base[] subs = new Sub[1];
-      // Not applicable in debug mode.
-      System.out.println(subs.getClass());
-    }
+      {
+        Base[] subs = new Sub[1];
+        // Not applicable in debug mode.
+        System.out.println(subs.getClass());
+      }
 
-    {
-      EffectivelyFinal ef = new EffectivelyFinal();
-      // Not applicable in debug mode.
-      System.out.println(ef.getClass());
-    }
+      {
+        EffectivelyFinal ef = new EffectivelyFinal();
+        // Not applicable in debug mode.
+        System.out.println(ef.getClass());
+      }
 
-    try {
-      // To not be recognized as un-instantiated class.
-      GetClassTestMain instance = new GetClassTestMain();
-      System.out.println(instance.call());
-      System.out.println(getMainClass(instance));
-
-      System.out.println(getMainClass(null));
-      throw new AssertionError("Should preserve NPE.");
-    } catch (NullPointerException e) {
-      // Expected
-    }
-
-    {
-      Reflection r = new Reflection();
-      // Not applicable in debug mode.
-      System.out.println(r.getClass());
       try {
-        // Can be rewritten to const-class after inlining.
-        System.out.println(r.call());
-      } catch (Throwable e) {
-        throw new AssertionError("Not expected any exceptions.");
+        // To not be recognized as un-instantiated class.
+        GetClassTestMain instance = new GetClassTestMain();
+        System.out.println(instance.call());
+        System.out.println(GetClassTestMain.getMainClass(instance));
+
+        System.out.println(GetClassTestMain.getMainClass(null));
+        throw new AssertionError("Should preserve NPE.");
+      } catch (NullPointerException e) {
+        // Expected
+      }
+
+      {
+        Reflection r = new Reflection();
+        // Not applicable in debug mode.
+        System.out.println(r.getClass());
+        try {
+          // Can be rewritten to const-class after inlining.
+          System.out.println(r.call());
+        } catch (Throwable e) {
+          throw new AssertionError("Not expected any exceptions.");
+        }
       }
     }
   }
-}
 
-@RunWith(Parameterized.class)
-public class GetClassTest extends ReflectionOptimizerTestBase {
-  private static final String JAVA_OUTPUT = StringUtils.lines(
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Base",
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Base",
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Sub",
-      "class [Lcom.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Sub;",
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$EffectivelyFinal",
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain",
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain",
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Reflection",
-      "class com.android.tools.r8.ir.optimize.reflection.GetClassTestMain$Reflection"
-  );
-  private static final Class<?> MAIN = GetClassTestMain.class;
+  private static final String JAVA_OUTPUT =
+      StringUtils.lines(
+          ListUtils.map(
+              ImmutableList.of(
+                  Base.class.getTypeName(),
+                  Base.class.getTypeName(),
+                  Sub.class.getTypeName(),
+                  "[L" + Sub.class.getTypeName() + ";",
+                  EffectivelyFinal.class.getTypeName(),
+                  GetClassTestMain.class.getTypeName(),
+                  GetClassTestMain.class.getTypeName(),
+                  Reflection.class.getTypeName(),
+                  Reflection.class.getTypeName()),
+              l -> "class " + l));
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}, mode:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
   }
 
   private final TestParameters parameters;
+  private final CompilationMode mode;
 
-  public GetClassTest(TestParameters parameters) {
+  public GetClassTest(TestParameters parameters, CompilationMode mode) {
     this.parameters = parameters;
+    this.mode = mode;
   }
 
   private void configure(InternalOptions options) {
@@ -140,109 +154,75 @@
   }
 
   @Test
-  public void testJVMOutput() throws Exception {
-    assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+  public void testJVM() throws Exception {
+    assumeTrue(
+        "Only run JVM reference on CF runtimes",
+        parameters.isCfRuntime() && mode == CompilationMode.DEBUG);
     testForJvm()
-        .addTestClasspath()
+        .addInnerClasses(GetClassTest.class)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
   }
 
   private void test(
-      TestRunResult<?> result, boolean isR8, boolean isRelease) throws Exception {
-    CodeInspector codeInspector = result.inspector();
+      CodeInspector codeInspector,
+      boolean expectCallPresent,
+      int expectedGetClassCount,
+      int expectedConstClassCount) {
     ClassSubject mainClass = codeInspector.clazz(MAIN);
     MethodSubject mainMethod = mainClass.mainMethod();
     assertThat(mainMethod, isPresent());
-    int expectedCount = isR8 ? (isRelease ? 0 : 5) : 6;
-    assertEquals(expectedCount, countGetClass(mainMethod));
-    expectedCount = isR8 ? (isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1) : 0;
-    assertEquals(expectedCount, countConstClass(mainMethod));
+    assertEquals(expectedGetClassCount, countGetClass(mainMethod));
+    assertEquals(expectedConstClassCount, countConstClass(mainMethod));
 
-    boolean expectToBeOptimized = isR8 && isRelease;
-
-    MethodSubject getMainClass = mainClass.uniqueMethodWithName("getMainClass");
+    ClassSubject getterClass = codeInspector.clazz(GetClassTestMain.class);
+    MethodSubject getMainClass = getterClass.uniqueMethodWithName("getMainClass");
     assertThat(getMainClass, isPresent());
     // Because of nullable argument, getClass() should remain.
     assertEquals(1, countGetClass(getMainClass));
     assertEquals(0, countConstClass(getMainClass));
 
-    MethodSubject call = mainClass.method("java.lang.Class", "call", ImmutableList.of());
-    if (isR8 && isRelease) {
+    MethodSubject call = getterClass.method("java.lang.Class", "call", ImmutableList.of());
+    if (!expectCallPresent) {
       assertThat(call, not(isPresent()));
     } else {
       assertThat(call, isPresent());
       // Because of local, only R8 release mode can rewrite getClass() to const-class.
-      assertEquals(expectToBeOptimized ? 0 : 1, countGetClass(call));
-      assertEquals(expectToBeOptimized ? 1 : 0, countConstClass(call));
+      assertEquals(1, countGetClass(call));
+      assertEquals(0, countConstClass(call));
     }
   }
 
   @Test
   public void testD8() throws Exception {
     assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
-
-    // D8 debug.
-    D8TestRunResult result =
-        testForD8()
-            .debug()
-            .addProgramClassesAndInnerClasses(MAIN)
-            .addOptionsModification(this::configure)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, false, false);
-
-    // D8 release.
-    result =
-        testForD8()
-            .release()
-            .addProgramClassesAndInnerClasses(MAIN)
-            .addOptionsModification(this::configure)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, false, true);
+    testForD8()
+        .setMode(mode)
+        .addInnerClasses(GetClassTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT)
+        .inspect(inspector -> test(inspector, true, 6, 0));
   }
 
   @Test
   public void testR8() throws Exception {
-    // R8 debug, no minification.
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .debug()
-            .addProgramClassesAndInnerClasses(MAIN)
-            .enableInliningAnnotations()
-            .addKeepMainRule(MAIN)
-            .noMinification()
-            .addOptionsModification(this::configure)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), MAIN);
-    test(result, true, false);
-
-    // R8 release, no minification.
-    result =
-        testForR8(parameters.getBackend())
-            .addProgramClassesAndInnerClasses(MAIN)
-            .enableInliningAnnotations()
-            .addKeepMainRule(MAIN)
-            .noMinification()
-            .addOptionsModification(this::configure)
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), MAIN)
-            .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, true, true);
-
-    // R8 release, minification.
-    result =
-        testForR8(parameters.getBackend())
-            .addProgramClassesAndInnerClasses(MAIN)
-            .enableInliningAnnotations()
-            .addKeepMainRule(MAIN)
-            .addOptionsModification(this::configure)
-            .setMinApi(parameters.getApiLevel())
-            // We are not checking output because it can't be matched due to minification. Just run.
-            .run(parameters.getRuntime(), MAIN);
-    test(result, true, true);
+    boolean isRelease = mode == CompilationMode.RELEASE;
+    boolean expectCallPresent = !isRelease;
+    int expectedGetClassCount = isRelease ? 0 : 5;
+    int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1;
+    testForR8(parameters.getBackend())
+        .setMode(mode)
+        .addInnerClasses(GetClassTest.class)
+        .enableInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .noMinification()
+        .addOptionsModification(this::configure)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT)
+        .inspect(
+            inspector ->
+                test(inspector, expectCallPresent, expectedGetClassCount, expectedConstClassCount));
   }
 }