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