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);
}