Add -identifiernamestring rule for Java reflection on demand.

Bug: 76181966
Change-Id: Ie178b941744735fddbc0dcd1c4c083c45eb2a545
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index b2560ff..f2f59a2 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -4,37 +4,9 @@
 
 package com.android.tools.r8;
 
-import com.android.tools.r8.origin.EmbeddedOrigin;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
-import java.util.List;
 
 public class CompatProguardCommandBuilder extends R8Command.Builder {
-  @VisibleForTesting
-  public static final List<String> REFLECTIONS = ImmutableList.of(
-      "-identifiernamestring public class java.lang.Class {",
-      "  public static java.lang.Class forName(java.lang.String);",
-      "  public java.lang.reflect.Field getField(java.lang.String);",
-      "  public java.lang.reflect.Field getDeclaredField(java.lang.String);",
-      "  public java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]);",
-      "  public java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);",
-      "}",
-      "-identifiernamestring public class java.util.concurrent.atomic.AtomicIntegerFieldUpdater {",
-      "  public static java.util.concurrent.atomic.AtomicIntegerFieldUpdater",
-      "      newUpdater(java.lang.Class, java.lang.String);",
-      "}",
-      "-identifiernamestring public class java.util.concurrent.atomic.AtomicLongFieldUpdater {",
-      "  public static java.util.concurrent.atomic.AtomicLongFieldUpdater",
-      "      newUpdater(java.lang.Class, java.lang.String);",
-      "}",
-      "-identifiernamestring public class",
-      "    java.util.concurrent.atomic.AtomicReferenceFieldUpdater {",
-      "  public static java.util.concurrent.atomic.AtomicReferenceFieldUpdater",
-      "      newUpdater(java.lang.Class, java.lang.Class, java.lang.String);",
-      "}"
-  );
-
   public CompatProguardCommandBuilder() {
     this(true);
   }
@@ -43,13 +15,11 @@
       boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
     super(forceProguardCompatibility, diagnosticsHandler);
     setIgnoreDexInArchive(true);
-    addProguardConfiguration(REFLECTIONS, EmbeddedOrigin.INSTANCE);
   }
 
   public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
     super(forceProguardCompatibility);
     setIgnoreDexInArchive(true);
-    addProguardConfiguration(REFLECTIONS, EmbeddedOrigin.INSTANCE);
   }
 
   public void setProguardCompatibilityRulesOutput(Path path) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 59c2639..21cab49 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -285,8 +285,8 @@
                 .run(executorService);
         ProtoLiteExtension protoLiteExtension =
             options.forceProguardCompatibility ? null : new ProtoLiteExtension(appInfo);
-        appInfo = new Enqueuer(appInfo, options, compatibility, protoLiteExtension)
-            .traceApplication(rootSet, timing);
+        Enqueuer enqueuer = new Enqueuer(appInfo, options, compatibility, protoLiteExtension);
+        appInfo = enqueuer.traceApplication(rootSet, timing);
         if (options.proguardConfiguration.isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index b11b999..b733faf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentiferNameString;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
+import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.Diagnostic;
@@ -103,6 +104,7 @@
 
   private final ProtoLiteExtension protoLiteExtension;
   private final Set<DexField> protoLiteFields = Sets.newIdentityHashSet();
+  private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
 
   /**
    * This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
@@ -272,9 +274,14 @@
 
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
-      if (options.forceProguardCompatibility
-          && appInfo.dexItemFactory.classMethods.isReflectiveMemberLookup(method)) {
-        pendingProguardReflectiveCompatibility.add(currentMethod);
+      if (appInfo.dexItemFactory.classMethods.isReflectiveMemberLookup(method)) {
+        if (options.forceProguardCompatibility) {
+          // TODO(b/76181966): whether or not add this rule in normal mode.
+          if (identifierNameStrings.add(method) && compatibility != null) {
+            compatibility.addRule(buildIdentifierNameStringRule(method));
+          }
+          pendingProguardReflectiveCompatibility.add(currentMethod);
+        }
       }
       if (!registerItemWithTarget(virtualInvokes, method)) {
         return false;
@@ -300,10 +307,15 @@
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      if (options.forceProguardCompatibility
-          && (method == appInfo.dexItemFactory.classMethods.forName
-              || appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method))) {
-        pendingProguardReflectiveCompatibility.add(currentMethod);
+      if (method == appInfo.dexItemFactory.classMethods.forName
+          || appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
+        if (options.forceProguardCompatibility) {
+          // TODO(b/76181966): whether or not add this rule in normal mode.
+          if (identifierNameStrings.add(method) && compatibility != null) {
+            compatibility.addRule(buildIdentifierNameStringRule(method));
+          }
+          pendingProguardReflectiveCompatibility.add(currentMethod);
+        }
       }
       if (!registerItemWithTarget(staticInvokes, method)) {
         return false;
@@ -1510,7 +1522,8 @@
       this.noSideEffects = enqueuer.rootSet.noSideEffects;
       this.assumedValues = enqueuer.rootSet.assumedValues;
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
-      this.identifierNameStrings = enqueuer.rootSet.identifierNameStrings;
+      this.identifierNameStrings =
+          Sets.union(enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
       this.protoLiteFields = enqueuer.protoLiteFields;
       this.prunedTypes = Collections.emptySet();
       this.switchMaps = Collections.emptyMap();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index b8b7780..5852cd9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -7,6 +7,10 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.List;
@@ -82,4 +86,37 @@
     builder.getMemberRules().add(memberRuleBuilder.build());
     return builder.build();
   }
+
+  public static ProguardIdentifierNameStringRule buildIdentifierNameStringRule(DexItem item) {
+    assert item instanceof DexField || item instanceof DexMethod;
+    ProguardIdentifierNameStringRule.Builder builder = ProguardIdentifierNameStringRule.builder();
+    ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
+    DexType holderType;
+    if (item instanceof DexField) {
+      DexField field = (DexField) item;
+      holderType = field.getHolder();
+      memberRuleBuilder.setRuleType(ProguardMemberType.FIELD);
+      memberRuleBuilder.setName(field.name.toString());
+      memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(field.type));
+    } else {
+      DexMethod method = (DexMethod) item;
+      holderType = method.getHolder();
+      memberRuleBuilder.setRuleType(ProguardMemberType.METHOD);
+      memberRuleBuilder.setName(method.name.toString());
+      memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(method.proto.returnType));
+      List<ProguardTypeMatcher> arguments = Arrays.stream(method.proto.parameters.values)
+          .map(ProguardTypeMatcher::create)
+          .collect(Collectors.toList());
+      memberRuleBuilder.setArguments(arguments);
+    }
+    if (holderType.isInterface()) {
+      builder.setClassType(ProguardClassType.INTERFACE);
+    } else {
+      builder.setClassType(ProguardClassType.CLASS);
+    }
+    builder.setClassNames(
+        ProguardClassNameList.singletonList(ProguardTypeMatcher.create(holderType)));
+    builder.getMemberRules().add(memberRuleBuilder.build());
+    return builder.build();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index 2f53fd9..eb1f245 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -9,7 +9,6 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
@@ -22,7 +21,6 @@
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import java.nio.file.Path;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,14 +38,16 @@
               pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
             })
         .addProguardConfiguration(
-            ImmutableList.copyOf(Iterables.concat(ImmutableList.of(
+            ImmutableList.of(
                 keepMainProguardConfiguration(main),
                 overloadaggressively ? "-overloadaggressively" : ""),
-                CompatProguardCommandBuilder.REFLECTIONS)),
             Origin.unknown())
         .setOutput(out, OutputMode.DexIndexed)
         .build();
-    return ToolHelper.runR8(command, o -> o.enableInlining = false);
+    return ToolHelper.runR8(command, o -> {
+      o.enableInlining = false;
+      o.forceProguardCompatibility = true;
+    });
   }
 
   private void fieldUpdater(boolean overloadaggressively) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 4681d7d..abbe3d3 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardIdentifierNameStringRule;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.ProguardKeepRuleType;
@@ -42,6 +43,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.io.File;
@@ -271,13 +273,12 @@
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
-      assertEquals(2, configuration.getRules().size());
-      configuration.getRules().forEach(rule -> {
-        assertTrue(rule instanceof ProguardKeepRule);
-        ProguardKeepRule keepRule = (ProguardKeepRule) rule;
-        assertEquals(ProguardKeepRuleType.KEEP, keepRule.getType());
-        assertTrue(keepRule.getModifiers().allowsObfuscation);
-        assertTrue(keepRule.getModifiers().allowsOptimization);
+      List<ProguardConfigurationRule> rules = configuration.getRules();
+      assertEquals(3, rules.size());
+      Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+        assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
+        assertTrue(rule.getModifiers().allowsObfuscation);
+        assertTrue(rule.getModifiers().allowsOptimization);
         Set<ProguardMemberRule> memberRules = rule.getMemberRules();
         ProguardClassNameList classNames = rule.getClassNames();
         assertEquals(1, classNames.size());
@@ -293,6 +294,17 @@
           assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
         }
       });
+      Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
+        Set<ProguardMemberRule> memberRules = rule.getMemberRules();
+        ProguardClassNameList classNames = rule.getClassNames();
+        assertEquals(1, classNames.size());
+        DexType type = classNames.asSpecificDexTypes().get(0);
+        assertEquals("java.lang.Class", type.toSourceString());
+        assertEquals(1, memberRules.size());
+        ProguardMemberRule memberRule = memberRules.iterator().next();
+        assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
+        assertEquals("forName", memberRule.getName().toString());
+      });
     } else {
       assertEquals(0, configuration.getRules().size());
     }
@@ -370,13 +382,12 @@
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
-      assertEquals(2, configuration.getRules().size());
-      configuration.getRules().forEach(rule -> {
-        assertTrue(rule instanceof ProguardKeepRule);
-        ProguardKeepRule keepRule = (ProguardKeepRule) rule;
-        assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, keepRule.getType());
-        assertTrue(keepRule.getModifiers().allowsObfuscation);
-        assertTrue(keepRule.getModifiers().allowsOptimization);
+      List<ProguardConfigurationRule> rules = configuration.getRules();
+      assertEquals(4, rules.size());
+      Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+        assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
+        assertTrue(rule.getModifiers().allowsObfuscation);
+        assertTrue(rule.getModifiers().allowsOptimization);
         Set<ProguardMemberRule> memberRules = rule.getMemberRules();
         ProguardClassNameList classNames = rule.getClassNames();
         assertEquals(1, classNames.size());
@@ -391,6 +402,17 @@
           assertTrue(memberRule.getName().matches("bar"));
         }
       });
+      Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
+        Set<ProguardMemberRule> memberRules = rule.getMemberRules();
+        ProguardClassNameList classNames = rule.getClassNames();
+        assertEquals(1, classNames.size());
+        DexType type = classNames.asSpecificDexTypes().get(0);
+        assertEquals("java.lang.Class", type.toSourceString());
+        assertEquals(1, memberRules.size());
+        ProguardMemberRule memberRule = memberRules.iterator().next();
+        assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
+        assertTrue(memberRule.getName().toString().startsWith("getDeclared"));
+      });
     } else {
       assertEquals(0, configuration.getRules().size());
     }
@@ -475,14 +497,13 @@
     parser.parse(proguardCompatibilityRules);
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
-      assertEquals(3, configuration.getRules().size());
+      List<ProguardConfigurationRule> rules = configuration.getRules();
+      assertEquals(6, rules.size());
       Object2BooleanMap<String> keptFields = new Object2BooleanArrayMap<>();
-      configuration.getRules().forEach(rule -> {
-        assertTrue(rule instanceof ProguardKeepRule);
-        ProguardKeepRule keepRule = (ProguardKeepRule) rule;
-        assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, keepRule.getType());
-        assertTrue(keepRule.getModifiers().allowsObfuscation);
-        assertTrue(keepRule.getModifiers().allowsOptimization);
+      Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+        assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
+        assertTrue(rule.getModifiers().allowsObfuscation);
+        assertTrue(rule.getModifiers().allowsOptimization);
         Set<ProguardMemberRule> memberRules = rule.getMemberRules();
         ProguardClassNameList classNames = rule.getClassNames();
         assertEquals(1, classNames.size());
@@ -497,6 +518,18 @@
       assertTrue(keptFields.containsKey("intField"));
       assertTrue(keptFields.containsKey("longField"));
       assertTrue(keptFields.containsKey("objField"));
+      Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
+        Set<ProguardMemberRule> memberRules = rule.getMemberRules();
+        ProguardClassNameList classNames = rule.getClassNames();
+        assertEquals(1, classNames.size());
+        DexType type = classNames.asSpecificDexTypes().get(0);
+        assertTrue(type.toSourceString().startsWith("java.util.concurrent.atomic.Atomic"));
+        assertTrue(type.toSourceString().endsWith("FieldUpdater"));
+        assertEquals(1, memberRules.size());
+        ProguardMemberRule memberRule = memberRules.iterator().next();
+        assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
+        assertEquals("newUpdater", memberRule.getName().toString());
+      });
     } else {
       assertEquals(0, configuration.getRules().size());
     }