Add support -adaptclassstrings [class_filter] (w/ different parsing).

First, user-given rules w/ that option in pg config will be converted
to ProguardClassNameList, which is in turn used to match classes.

After the normal minifier process is done, IdentifierMinifier, a newly
introduced minifier that uses aforementioned matched classes and naming
lense that is generated from the other minifiers, will walk through
candidate classes to find string literal usages:
1) in <clinit> (for static field initialization), and
2) in method's code_item.
Those string literals will be replaced with the renamed ones only if
1) they correspond to class identifiers; and
2) corresponding types are indeed renamed by the previous minifier.

Bug: 36799092
Change-Id: I3f09a22fa0d0eda6671f4c60d29b9363fb991df3
diff --git a/src/test/examples/adaptclassstrings/A.java b/src/test/examples/adaptclassstrings/A.java
new file mode 100644
index 0000000..a1d95ef
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/A.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class A {
+  protected static final String ITSELF = "Ladaptclassstrings/A;";
+  protected static final String OTHER = "adaptclassstrings.C";
+
+  protected int f;
+
+  A(int f) {
+    this.f = f;
+  }
+
+  protected int foo() {
+    return f * 9;
+  }
+
+  void bar() {
+    System.out.println("adaptclassstrings.C");
+    System.out.println("adaptclassstrings.D");
+  }
+}
diff --git a/src/test/examples/adaptclassstrings/AA.java b/src/test/examples/adaptclassstrings/AA.java
new file mode 100644
index 0000000..0a03efa
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/AA.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class AA extends A {
+
+  AA(int f) {
+    super(f);
+  }
+
+  @Override
+  protected int foo() {
+    return super.foo() - f;
+  }
+}
diff --git a/src/test/examples/adaptclassstrings/C.java b/src/test/examples/adaptclassstrings/C.java
new file mode 100644
index 0000000..c5c8fe6
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/C.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class C {
+  static final String OTHER = "adaptclassstrings.A";
+  static final String ITSELF = "adaptclassstrings.C";
+}
diff --git a/src/test/examples/adaptclassstrings/Main.java b/src/test/examples/adaptclassstrings/Main.java
new file mode 100644
index 0000000..4cee1d5
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/Main.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, 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 adaptclassstrings;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    int f = 3;
+    A a = new A(f);
+    AA aa = new AA(f);
+    assert a.foo() != aa.foo();
+
+    a.bar();
+
+    Object a_foo =
+        Class.forName("adaptclassstrings.A").getMethod("foo").invoke(a);
+    Object aa_foo =
+        Class.forName("adaptclassstrings.AA").getMethod("foo").invoke(aa);
+    assert !a_foo.equals(aa_foo);
+
+    Object c_to_a_foo =
+        Class.forName((String)
+            Class.forName("adaptclassstrings.C").getField("OTHER").get(null))
+            .getMethod("foo").invoke(a);
+    assert a_foo.equals(c_to_a_foo);
+
+    String cName = (String) Class.forName(C.ITSELF).getField("ITSELF").get(null);
+    assert cName.equals(A.OTHER);
+  }
+}
diff --git a/src/test/examples/adaptclassstrings/keep-rules-1.txt b/src/test/examples/adaptclassstrings/keep-rules-1.txt
new file mode 100644
index 0000000..72562ed
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/keep-rules-1.txt
@@ -0,0 +1,12 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class adaptclassstrings.Main {
+  public static void main(...);
+}
+
+-dontshrink
+-dontoptimize
diff --git a/src/test/examples/adaptclassstrings/keep-rules-2.txt b/src/test/examples/adaptclassstrings/keep-rules-2.txt
new file mode 100644
index 0000000..fc8be9d
--- /dev/null
+++ b/src/test/examples/adaptclassstrings/keep-rules-2.txt
@@ -0,0 +1,14 @@
+# Copyright (c) 2017, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class adaptclassstrings.Main {
+  public static void main(...);
+}
+
+-dontshrink
+-dontoptimize
+
+-adaptclassstrings *.*A
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
new file mode 100644
index 0000000..191f6cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -0,0 +1,202 @@
+// Copyright (c) 2017, 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.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IdentifierMinifierTest {
+
+  private final String appFileName;
+  private final List<String> keepRulesFiles;
+  private final Consumer<DexInspector> inspection;
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  public IdentifierMinifierTest(
+      String test,
+      List<String> keepRulesFiles,
+      Consumer<DexInspector> inspection) {
+    this.appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
+    this.keepRulesFiles = keepRulesFiles;
+    this.inspection = inspection;
+  }
+
+  @Before
+  public void generateR8ProcessedApp()
+      throws IOException, ExecutionException, ProguardRuleParserException, CompilationException {
+    Path out = temp.getRoot().toPath();
+    R8Command command =
+        R8Command.builder()
+            .setOutputPath(out)
+            .addProgramFiles(Paths.get(appFileName))
+            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
+            .addProguardConfigurationConsumer(builder -> {
+              builder.setPrintMapping(true);
+              builder.setPrintMappingFile(out.resolve(AndroidApp.DEFAULT_PROGUARD_MAP_FILE));
+            })
+            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+            .build();
+    ToolHelper.runR8(command);
+  }
+
+  @Test
+  public void identiferMinifierTest() throws Exception {
+    Path out = temp.getRoot().toPath();
+    DexInspector dexInspector =
+        new DexInspector(
+            out.resolve("classes.dex"),
+            out.resolve(AndroidApp.DEFAULT_PROGUARD_MAP_FILE).toString());
+    inspection.accept(dexInspector);
+  }
+
+  @Parameters(name = "test: {0} keep: {1}")
+  public static Collection<Object[]> data() {
+    List<String> tests = Arrays.asList("adaptclassstrings");
+
+    Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
+    inspections.put("adaptclassstrings:keep-rules-1.txt", IdentifierMinifierTest::test1_rule1);
+    inspections.put("adaptclassstrings:keep-rules-2.txt", IdentifierMinifierTest::test1_rule2);
+
+    return NamingTestBase.createTests(tests, inspections);
+  }
+
+  // Without -adaptclassstrings
+  private static void test1_rule1(DexInspector inspector) {
+    ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
+    MethodSubject main = mainClass.method(DexInspector.MAIN);
+    Code mainCode = main.getMethod().getCode();
+    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    int renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
+    MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
+    Code barCode = bar.getMethod().getCode();
+    verifyPresenceOfConstString(barCode.asDexCode().instructions);
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+    assertEquals(0, renamedYetFoundIdentifierCount);
+  }
+
+  // With -adaptclassstrings *.*A
+  private static void test1_rule2(DexInspector inspector) {
+    ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
+    MethodSubject main = mainClass.method(DexInspector.MAIN);
+    Code mainCode = main.getMethod().getCode();
+    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    int renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+    assertEquals(0, renamedYetFoundIdentifierCount);
+
+    ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
+    MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
+    Code barCode = bar.getMethod().getCode();
+    verifyPresenceOfConstString(barCode.asDexCode().instructions);
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+    assertEquals(1, renamedYetFoundIdentifierCount);
+
+    renamedYetFoundIdentifierCount =
+        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+    assertEquals(1, renamedYetFoundIdentifierCount);
+  }
+
+  private static void verifyPresenceOfConstString(Instruction[] instructions) {
+    boolean presence =
+        Arrays.stream(instructions)
+            .anyMatch(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
+    assertTrue(presence);
+  }
+
+  private static String retrieveString(Instruction instr) {
+    if (instr instanceof ConstString) {
+      ConstString cnst = (ConstString) instr;
+      return cnst.getString().toString();
+    } else if (instr instanceof ConstStringJumbo) {
+      ConstStringJumbo cnst = (ConstStringJumbo) instr;
+      return cnst.getString().toString();
+    }
+    return null;
+  }
+
+  private static int countRenamedClassIdentifier(
+      DexInspector inspector, Instruction[] instructions) {
+    return Arrays.stream(instructions)
+        .filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo)
+        .reduce(0, (cnt, instr) -> {
+          String cnstString = retrieveString(instr);
+          if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+            ClassSubject classSubject = inspector.clazz(cnstString);
+            if (classSubject.isRenamed()
+                && DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
+                .equals(cnstString)) {
+              return cnt + 1;
+            }
+          }
+          return cnt;
+        }, Integer::sum);
+  }
+
+  private static int countRenamedClassIdentifier(
+      DexInspector inspector, DexEncodedField[] fields) {
+    return Arrays.stream(fields)
+        .filter(encodedField -> encodedField.staticValue instanceof DexValueString)
+        .reduce(0, (cnt, encodedField) -> {
+          String cnstString = ((DexValueString) encodedField.staticValue).getValue().toString();
+          if (!DescriptorUtils.isClassDescriptor(cnstString)) {
+            ClassSubject classSubject = inspector.clazz(cnstString);
+            if (classSubject.isRenamed()
+                && DescriptorUtils.descriptorToJavaType(classSubject.getFinalDescriptor())
+                .equals(cnstString)) {
+              return cnt + 1;
+            }
+          }
+          return cnt;
+        }, Integer::sum);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index cb76b5d..fb3c88a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -347,30 +347,15 @@
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, diagnosticsHandler);
-    String config1 = "-adaptclassstrings !foobar,*bar";
-    String config2 = "-adaptclassstrings !a.b.c.nope,a.b.**";
-    parser.parse( new ProguardConfigurationSourceStrings(ImmutableList.of(config1, config2)));
+    String adaptClassStrings = "-adaptclassstrings !foobar,*bar";
+    parser.parse( new ProguardConfigurationSourceStrings(ImmutableList.of(adaptClassStrings)));
     ProguardConfiguration config = parser.getConfig();
-    List<ProguardConfigurationRule> rules = config.getRules();
-    assertEquals(2, rules.size());
-    assertEquals(0, rules.get(0).getMemberRules().size());
-    assertEquals(2, rules.get(0).getClassNames().size());
     assertFalse(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobar;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
     assertFalse(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lfoobar;")));
-    assertEquals(0, rules.get(1).getMemberRules().size());
-    assertEquals(2, rules.get(1).getClassNames().size());
-    assertFalse(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("Lx/y/z;")));
-    assertTrue(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("La/b/c/d;")));
-    assertTrue(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("La/b/p/q;")));
-    assertFalse(
-        rules.get(1).getClassNames().matches(dexItemFactory.createType("La/b/c/nope;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
@@ -381,14 +366,12 @@
     String adaptAll = "-adaptclassstrings *";
     parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(adaptAll)));
     ProguardConfiguration config = parser.getConfig();
-    List<ProguardConfigurationRule> rules = config.getRules();
-    assertEquals(1, rules.size());
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lfoobar;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
@@ -399,14 +382,12 @@
     String adaptAll = "-adaptclassstrings";
     parser.parse(new ProguardConfigurationSourceStrings(ImmutableList.of(adaptAll)));
     ProguardConfiguration config = parser.getConfig();
-    List<ProguardConfigurationRule> rules = config.getRules();
-    assertEquals(1, rules.size());
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        rules.get(0).getClassNames().matches(dexItemFactory.createType("Lfoobar;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 26de96c..612043e 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -697,6 +697,8 @@
     public abstract DexEncodedField getField();
 
     public abstract DexValue getStaticValue();
+
+    public abstract boolean isRenamed();
   }
 
   public class AbsentFieldSubject extends FieldSubject {
@@ -727,6 +729,11 @@
     }
 
     @Override
+    public boolean isRenamed() {
+      return false;
+    }
+
+    @Override
     public Signature getOriginalSignature() {
       return null;
     }
@@ -787,6 +794,12 @@
       return true;
     }
 
+    @Override
+    public boolean isRenamed() {
+      return clazz.naming == null || !getFinalSignature().name.equals(getOriginalSignature().name);
+    }
+
+
     public TypeSubject type() {
       return new TypeSubject(dexField.field.type);
     }