Rewrite Class#forName to const-class if possible.

Bug: 117545367
Change-Id: Idf143e6f7c3341bce6d5a3c243970e5d6b0a2bd6
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 2aefbc7..50c343e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -476,7 +476,11 @@
   // Library methods listed here are based on their original implementations. That is, we assume
   // these cannot be overridden.
   public final Set<DexMethod> libraryMethodsReturningNonNull =
-      ImmutableSet.of(classMethods.getName, classMethods.getSimpleName, stringMethods.valueOf);
+      ImmutableSet.of(
+          classMethods.getName,
+          classMethods.getSimpleName,
+          classMethods.forName,
+          stringMethods.valueOf);
 
   // We assume library methods listed here are `public`, i.e., free from visibility side effects.
   // If not, that library method should not be added here because it literally has side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index f7d9077..a696a17 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1077,8 +1077,8 @@
     previous = printMethod(code, "IR after inlining (SSA)", previous);
 
     if (appView.appInfo().hasLiveness()) {
-      // Reflection optimization 1. getClass() -> const-class
-      ReflectionOptimizer.rewriteGetClass(appView.withLiveness(), code);
+      // Reflection optimization 1. getClass() / forName() -> const-class
+      ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(appView.withLiveness(), code);
     }
 
     if (!isDebugMode) {
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 9dc651c..8a4d614 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
@@ -10,79 +10,182 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
 
 public class ReflectionOptimizer {
 
-  // Rewrite getClass() call to const-class if the type of the given instance is effectively final.
-  public static void rewriteGetClass(AppView<AppInfoWithLiveness> appView, IRCode code) {
-    InstructionListIterator it = code.instructionListIterator();
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    while (it.hasNext()) {
-      Instruction current = it.next();
+  // Rewrite getClass() to const-class if the type of the given instance is effectively final.
+  // Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
+  public static void rewriteGetClassOrForNameToConstClass(
+      AppView<AppInfoWithLiveness> appView, IRCode code) {
+    DexType context = code.method.method.holder;
+    ClassInitializationAnalysis classInitializationAnalysis =
+        new ClassInitializationAnalysis(appView, code);
+    for (BasicBlock block : code.blocks) {
       // Conservatively bail out if the containing block has catch handlers.
       // TODO(b/118509730): unless join of all catch types is ClassNotFoundException ?
-      if (current.getBlock().hasCatchHandlers()) {
+      if (block.hasCatchHandlers()) {
         continue;
       }
-      if (!current.isInvokeVirtual()) {
-        continue;
-      }
-      InvokeVirtual invoke = current.asInvokeVirtual();
-      DexMethod invokedMethod = invoke.getInvokedMethod();
-      // Class<?> Object#getClass() is final and cannot be overridden.
-      if (invokedMethod != dexItemFactory.objectMethods.getClass) {
-        continue;
-      }
-      Value in = invoke.getReceiver();
-      if (in.hasLocalInfo()) {
-        continue;
-      }
-      TypeLatticeElement inType = in.getTypeLattice();
-      // Check the receiver is either class type or array type. Also make sure it is not nullable.
-      if (!(inType.isClassType() || inType.isArrayType())
-          || inType.isNullable()) {
-        continue;
-      }
-      DexType type =
-          inType.isClassType()
-              ? inType.asClassTypeLatticeElement().getClassType()
-              : inType.asArrayTypeLatticeElement().getArrayType(dexItemFactory);
-      DexType baseType = type.toBaseType(dexItemFactory);
-      // Make sure base type is a class type.
-      if (!baseType.isClassType()) {
-        continue;
-      }
-      // Only consider program class, e.g., platform can introduce sub types in different versions.
-      DexClass clazz = appView.definitionFor(baseType);
-      if (clazz == null || !clazz.isProgramClass()) {
-        continue;
-      }
-      // Only consider effectively final class. Exception: new Base().getClass().
-      if (!appView.appInfo().hasSubtypes(baseType)
-          || !appView.appInfo().isInstantiatedIndirectly(baseType)
-          || (!in.isPhi() && in.definition.isCreatingInstanceOrArray())) {
-        // Make sure the target (base) type is visible.
-        ConstraintWithTarget constraints =
-            ConstraintWithTarget.classIsVisible(code.method.method.holder, baseType, appView);
-        if (constraints == ConstraintWithTarget.NEVER) {
-          continue;
+      InstructionListIterator it = block.listIterator(code);
+      while (it.hasNext()) {
+        Instruction current = it.next();
+        DexType type = null;
+        if (current.isInvokeVirtual()) {
+          type = getTypeForGetClass( appView, context, current.asInvokeVirtual());
+        } else if (current.isInvokeStatic()) {
+          type = getTypeForClassForName(
+              appView, classInitializationAnalysis, context, current.asInvokeStatic());
         }
-        TypeLatticeElement typeLattice =
-            TypeLatticeElement.classClassType(appView, definitelyNotNull());
-        Value value = code.createValue(typeLattice, invoke.getLocalInfo());
-        ConstClass constClass = new ConstClass(value, type);
-        it.replaceCurrentInstruction(constClass);
+        if (type != null) {
+          TypeLatticeElement typeLattice =
+              TypeLatticeElement.classClassType(appView, definitelyNotNull());
+          Value value = code.createValue(typeLattice, current.getLocalInfo());
+          ConstClass constClass = new ConstClass(value, type);
+          it.replaceCurrentInstruction(constClass);
+        }
       }
     }
+    classInitializationAnalysis.finish();
     assert code.isConsistentSSA();
   }
+
+  private static DexType getTypeForGetClass(
+      AppView<AppInfoWithLiveness> appView,
+      DexType context,
+      InvokeVirtual invoke) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    // Class<?> Object#getClass() is final and cannot be overridden.
+    if (invokedMethod != dexItemFactory.objectMethods.getClass) {
+      return null;
+    }
+    Value in = invoke.getReceiver();
+    if (in.hasLocalInfo()) {
+      return null;
+    }
+    TypeLatticeElement inType = in.getTypeLattice();
+    // Check the receiver is either class type or array type. Also make sure it is not
+    // nullable.
+    if (!(inType.isClassType() || inType.isArrayType())
+        || inType.isNullable()) {
+      return null;
+    }
+    DexType type =
+        inType.isClassType()
+            ? inType.asClassTypeLatticeElement().getClassType()
+            : inType.asArrayTypeLatticeElement().getArrayType(dexItemFactory);
+    DexType baseType = type.toBaseType(dexItemFactory);
+    // Make sure base type is a class type.
+    if (!baseType.isClassType()) {
+      return null;
+    }
+    // Only consider program class, e.g., platform can introduce subtypes in different
+    // versions.
+    DexClass clazz = appView.definitionFor(baseType);
+    if (clazz == null || !clazz.isProgramClass()) {
+      return null;
+    }
+    // Only consider effectively final class. Exception: new Base().getClass().
+    if (appView.appInfo().hasSubtypes(baseType)
+        && appView.appInfo().isInstantiatedIndirectly(baseType)
+        && (in.isPhi() || !in.definition.isCreatingInstanceOrArray())) {
+      return null;
+    }
+    // Make sure the target (base) type is visible.
+    ConstraintWithTarget constraints =
+        ConstraintWithTarget.classIsVisible(context, baseType, appView);
+    if (constraints == ConstraintWithTarget.NEVER) {
+      return null;
+    }
+    return type;
+  }
+
+  private static DexType getTypeForClassForName(
+      AppView<AppInfoWithLiveness> appView,
+      ClassInitializationAnalysis classInitializationAnalysis,
+      DexType context,
+      InvokeStatic invoke) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    // Class<?> Class#forName(String) is final and cannot be overridden.
+    if (invokedMethod != dexItemFactory.classMethods.forName) {
+      return null;
+    }
+    assert invoke.inValues().size() == 1;
+    Value in = invoke.inValues().get(0).getAliasedValue();
+    // Only consider const-string input without locals.
+    if (in.hasLocalInfo() || in.isPhi()) {
+      return null;
+    }
+    // Also, check if the result of forName() is updatable via locals.
+    Value out = invoke.outValue();
+    if (out != null && out.hasLocalInfo()) {
+      return null;
+    }
+    DexType type = null;
+    if (in.definition.isDexItemBasedConstString()) {
+      if (in.definition.asDexItemBasedConstString().getItem().isDexType()) {
+        type = in.definition.asDexItemBasedConstString().getItem().asDexType();
+      }
+    } else if (in.definition.isConstString()) {
+      String name = in.definition.asConstString().getValue().toString();
+      // Convert the name into descriptor if the given name is a valid java type.
+      String descriptor = DescriptorUtils.javaTypeToDescriptorIfValidJavaType(name);
+      // Otherwise, it may be an array's fully qualified name from Class<?>#getName().
+      if (descriptor == null && name.startsWith("[") && name.endsWith(";")) {
+        // E.g., [Lx.y.Z; -> [Lx/y/Z;
+        descriptor = name.replace(
+            DescriptorUtils.JAVA_PACKAGE_SEPARATOR,
+            DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR);
+      }
+      if (descriptor == null
+          || descriptor.indexOf(DescriptorUtils.JAVA_PACKAGE_SEPARATOR) > 0) {
+        return null;
+      }
+      type = dexItemFactory.createType(descriptor);
+      // Check if the given name refers to a reference type.
+      if (!type.isReferenceType()) {
+        return null;
+      }
+    } else {
+      // Bail out for non-deterministic input to Class<?>#forName(name).
+      return null;
+    }
+    if (type == null) {
+      return null;
+    }
+    // Make sure the (base) type is resolvable.
+    DexType baseType = type.toBaseType(dexItemFactory);
+    DexClass baseClazz = appView.definitionFor(baseType);
+    if (baseClazz == null || !baseClazz.isResolvable(appView)) {
+      return null;
+    }
+    // Make sure the (base) type is visible.
+    ConstraintWithTarget constraints =
+        ConstraintWithTarget.classIsVisible(context, baseType, appView);
+    if (constraints == ConstraintWithTarget.NEVER) {
+      return null;
+    }
+    // Make sure the type is already initialized.
+    // Note that, if the given name refers to an array type, the corresponding Class<?> won't
+    // be initialized. So, it's okay to rewrite the instruction.
+    if (type.isClassType()
+        && !classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(type, invoke)) {
+      return null;
+    }
+    return type;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
new file mode 100644
index 0000000..149c374
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
@@ -0,0 +1,179 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+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.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class ForNameTestMain {
+  static class A {
+    static {
+      System.out.println("A#<clinit>");
+    }
+  }
+
+  static class B {
+    static {
+      System.out.println("B#<clinit>");
+    }
+  }
+
+  static class ArrayBase {
+    static {
+      System.out.println("ArrayBase#<clinit>");
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    {
+      try {
+        // Not found, hence kept.
+        Class<?> c = Class.forName("UnknownClass");
+        fail("Should preserve ClassNotFoundException.");
+      } catch (ClassNotFoundException e) {
+        // Expected
+        System.out.println("Unknown");
+      }
+    }
+    {
+      A a = new A();
+      // initialized, should be rewritten to const-class.
+      Class<?> c = Class.forName("com.android.tools.r8.ir.optimize.reflection.ForNameTestMain$A");
+      System.out.println(c.getSimpleName());
+      // Not initialized, hence kept.
+      c = Class.forName("com.android.tools.r8.ir.optimize.reflection.ForNameTestMain$B");
+      System.out.println(c.getSimpleName());
+      // But, array is okay even though the base type is not initialized yet.
+      c = Class.forName("[Lcom.android.tools.r8.ir.optimize.reflection.ForNameTestMain$ArrayBase;");
+      System.out.println(c.getSimpleName());
+    }
+  }
+}
+
+@RunWith(Parameterized.class)
+public class ForNameTest extends ReflectionOptimizerTestBase {
+  private static final String JAVA_OUTPUT = StringUtils.lines(
+      "Unknown",
+      "A#<clinit>",
+      "A",
+      "B#<clinit>",
+      "B",
+      "ArrayBase[]"
+  );
+  private static final Class<?> MAIN = ForNameTestMain.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public ForNameTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJVMOutput() throws Exception {
+    assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+  }
+
+  private void test(
+      TestRunResult result, int expectedForNameCount, int expectedConstClassCount)
+      throws Exception {
+    CodeInspector codeInspector = result.inspector();
+    ClassSubject mainClass = codeInspector.clazz(MAIN);
+    MethodSubject mainMethod = mainClass.mainMethod();
+    assertThat(mainMethod, isPresent());
+    assertEquals(expectedForNameCount, countForName(mainMethod));
+    assertEquals(expectedForNameCount + 2, countConstString(mainMethod));
+    assertEquals(expectedConstClassCount, countConstClass(mainMethod));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+
+    // D8 debug.
+    D8TestRunResult result =
+        testForD8()
+            .debug()
+            .addProgramClassesAndInnerClasses(MAIN)
+            .setMinApi(parameters.getRuntime())
+            .run(parameters.getRuntime(), MAIN)
+            .assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 4, 0);
+
+    // D8 release.
+    result =
+        testForD8()
+            .release()
+            .addProgramClassesAndInnerClasses(MAIN)
+            .setMinApi(parameters.getRuntime())
+            .run(parameters.getRuntime(), MAIN)
+            .assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 4, 0);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // R8 debug, no minification.
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .debug()
+            .addProgramClassesAndInnerClasses(MAIN)
+            .addKeepMainRule(MAIN)
+            .addKeepAllClassesRule()
+            .addKeepAttributes("EnclosingMethod", "InnerClasses")
+            .noMinification()
+            .setMinApi(parameters.getRuntime())
+            .run(parameters.getRuntime(), MAIN);
+    test(result, 4, 0);
+
+    // R8 release, no minification.
+    result =
+        testForR8(parameters.getBackend())
+            .addProgramClassesAndInnerClasses(MAIN)
+            .addKeepMainRule(MAIN)
+            .addKeepAllClassesRule()
+            .addKeepAttributes("EnclosingMethod", "InnerClasses")
+            .noMinification()
+            .setMinApi(parameters.getRuntime())
+            .run(parameters.getRuntime(), MAIN)
+            .assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 2, 2);
+
+    // R8 release, minification.
+    result =
+        testForR8(parameters.getBackend())
+            .addProgramClassesAndInnerClasses(MAIN)
+            .addKeepMainRule(MAIN)
+            .addKeepAllClassesRuleWithAllowObfuscation()
+            .addKeepAttributes("EnclosingMethod", "InnerClasses")
+            .setMinApi(parameters.getRuntime())
+            // We are not checking output because it can't be matched due to minification. Just run.
+            .run(parameters.getRuntime(), MAIN);
+    test(result, 2, 2);
+  }
+}
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 2fab599..1b9c22f 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
@@ -13,18 +13,14 @@
 import com.android.tools.r8.ForceInline;
 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.TestRunResult;
-import com.android.tools.r8.graph.DexMethod;
 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.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Streams;
 import java.util.concurrent.Callable;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -110,7 +106,7 @@
 }
 
 @RunWith(Parameterized.class)
-public class GetClassTest extends TestBase {
+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",
@@ -144,25 +140,6 @@
         .assertSuccessWithOutput(JAVA_OUTPUT);
   }
 
-  private static boolean isGetClass(DexMethod method) {
-    return method.getArity() == 0
-        && method.proto.returnType.toDescriptorString().equals("Ljava/lang/Class;")
-        && method.name.toString().equals("getClass");
-  }
-
-  private long countGetClass(MethodSubject method) {
-    return Streams.stream(method.iterateInstructions(instructionSubject -> {
-      if (instructionSubject.isInvoke()) {
-        return isGetClass(instructionSubject.getMethod());
-      }
-      return false;
-    })).count();
-  }
-
-  private long countConstClass(MethodSubject method) {
-    return Streams.stream(method.iterateInstructions(InstructionSubject::isConstClass)).count();
-  }
-
   private void test(
       TestRunResult result,
       int expectedGetClassCount,
@@ -190,7 +167,7 @@
     assertEquals(expectedConstClassCountForCall, countConstClass(call));
   }
 
-    @Test
+  @Test
   public void testD8() throws Exception {
     assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
 
@@ -222,7 +199,6 @@
         testForR8(parameters.getBackend())
             .debug()
             .addProgramClassesAndInnerClasses(MAIN)
-            .enableProguardTestOptions()
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
             .noMinification()
@@ -238,7 +214,6 @@
     result =
         testForR8(parameters.getBackend())
             .addProgramClassesAndInnerClasses(MAIN)
-            .enableProguardTestOptions()
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
             .noMinification()
@@ -251,7 +226,6 @@
     result =
         testForR8(parameters.getBackend())
             .addProgramClassesAndInnerClasses(MAIN)
-            .enableProguardTestOptions()
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
             .setMinApi(parameters.getRuntime())
@@ -259,5 +233,4 @@
             .run(parameters.getRuntime(), MAIN);
     test(result, 0, expectedConstClassCount, 0, 1);
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ReflectionOptimizerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ReflectionOptimizerTestBase.java
new file mode 100644
index 0000000..84c0bfb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ReflectionOptimizerTestBase.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2019, 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.TestBase;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+
+abstract class ReflectionOptimizerTestBase extends TestBase {
+  private static boolean isGetClass(DexMethod method) {
+    return method.getArity() == 0
+        && method.proto.returnType.toDescriptorString().equals("Ljava/lang/Class;")
+        && method.name.toString().equals("getClass");
+  }
+
+  long countGetClass(MethodSubject method) {
+    return method.streamInstructions().filter(instructionSubject -> {
+      if (instructionSubject.isInvoke()) {
+        return isGetClass(instructionSubject.getMethod());
+      }
+      return false;
+    }).count();
+  }
+
+  private static boolean isForName(DexMethod method) {
+    return method.getArity() == 1
+        && method.proto.returnType.toDescriptorString().equals("Ljava/lang/Class;")
+        && method.holder.toDescriptorString().equals("Ljava/lang/Class;")
+        && method.name.toString().equals("forName");
+  }
+
+  long countForName(MethodSubject method) {
+    return method.streamInstructions().filter(instructionSubject -> {
+      if (instructionSubject.isInvoke()) {
+        return isForName(instructionSubject.getMethod());
+      }
+      return false;
+    }).count();
+  }
+
+  long countConstClass(MethodSubject method) {
+    return method.streamInstructions().filter(InstructionSubject::isConstClass).count();
+  }
+
+  long countConstString(MethodSubject method) {
+    return method.streamInstructions().filter(i -> i.isConstString(JumboStringMode.ALLOW)).count();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 28f4e92..8f4aca9 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -107,19 +107,19 @@
 
   // Without -adaptclassstrings
   private static void test1_rule1(TestParameters parameters, CodeInspector inspector) {
-    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 4 : 3;
+    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
     test1_rules(inspector, expectedRenamedIdentifierInMain, 0, 0);
   }
 
   // With -adaptclassstrings *.*A
   private static void test1_rule2(TestParameters parameters, CodeInspector inspector) {
-    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 4 : 3;
+    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
     test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
   }
 
   // With -adaptclassstrings (no filter)
   private static void test1_rule3(TestParameters parameters, CodeInspector inspector) {
-    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 5 : 4;
+    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 3 : 2;
     test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
   }