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/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
new file mode 100644
index 0000000..374f496
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -0,0 +1,99 @@
+// 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 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.AppInfo;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Map;
+
+class IdentifierMinifier {
+
+ private final AppInfo appInfo;
+ private final ProguardClassNameList adaptClassStrings;
+ private final NamingLens lens;
+
+ IdentifierMinifier(
+ AppInfo appInfo,
+ ProguardClassNameList adaptClassStrings,
+ NamingLens lens) {
+ this.appInfo = appInfo;
+ this.adaptClassStrings = adaptClassStrings;
+ this.lens = lens;
+ }
+
+ void run() {
+ if (adaptClassStrings.size() != 0) {
+ handleAdaptClassStrings();
+ }
+ // TODO(b/36799092): Handle influx of string literals from call sites to annotated members.
+ }
+
+ private void handleAdaptClassStrings() {
+ appInfo.classes().forEach(clazz -> {
+ if (!adaptClassStrings.matches(clazz.type)) {
+ return;
+ }
+ clazz.forEachField(encodedField -> {
+ if (encodedField.staticValue instanceof DexValueString) {
+ DexString original = ((DexValueString) encodedField.staticValue).getValue();
+ DexString renamed = getRenamedStringLiteral(original);
+ if (renamed != original) {
+ encodedField.staticValue = new DexValueString(renamed);
+ }
+ }
+ });
+ clazz.forEachMethod(encodedMethod -> {
+ // Abstract methods do not have code_item.
+ if (encodedMethod.accessFlags.isAbstract()) {
+ return;
+ }
+ Code code = encodedMethod.getCode();
+ if (code == null || !code.isDexCode()) {
+ return;
+ }
+ DexCode dexCode = code.asDexCode();
+ for (Instruction instr : dexCode.instructions) {
+ if (instr instanceof ConstString) {
+ ConstString cnst = (ConstString) instr;
+ DexString dexString = cnst.getString();
+ cnst.BBBB = getRenamedStringLiteral(dexString);
+ } else if (instr instanceof ConstStringJumbo) {
+ ConstStringJumbo cnst = (ConstStringJumbo) instr;
+ DexString dexString = cnst.getString();
+ cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
+ }
+ }
+ });
+ });
+ }
+
+ private DexString getRenamedStringLiteral(DexString originalLiteral) {
+ String originalString = originalLiteral.toString();
+ Map<String, DexType> renamedYetMatchedTypes =
+ lens.getRenamedItems(
+ DexType.class,
+ type -> type.toSourceString().equals(originalString),
+ DexType::toSourceString);
+ DexType type = renamedYetMatchedTypes.get(originalString);
+ if (type != null) {
+ DexString renamed = lens.lookupDescriptor(type);
+ // Create a new DexString only when the corresponding string literal will be replaced.
+ if (renamed != originalLiteral) {
+ return appInfo.dexItemFactory.createString(
+ DescriptorUtils.descriptorToJavaType(renamed.toString()));
+ }
+ }
+ return originalLiteral;
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index fa85364..4181f7e 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -16,10 +16,13 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
public class Minifier {
@@ -47,7 +50,12 @@
Map<DexField, DexString> fieldRenaming =
new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
timing.end();
- return new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+ NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+ timing.begin("MinifyIdentifiers");
+ new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
+ .run();
+ timing.end();
+ return lens;
}
private static class MinifiedRenaming extends NamingLens {
@@ -84,6 +92,17 @@
Iterables.filter(renaming.keySet(), DexType.class).forEach(consumer);
}
+ @Override
+ <T extends DexItem> Map<String, T> getRenamedItems(
+ Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
+ return renaming
+ .keySet()
+ .stream()
+ .filter(item -> (item.getClass() == clazz) && predicate.test(clazz.cast(item)))
+ .map(clazz::cast)
+ .collect(ImmutableMap.toImmutableMap(namer::apply, i -> i));
+ }
+
/**
* Checks whether the target is precise enough to be translated,
* <p>
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index 69c165d..ca6c1c8 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -4,11 +4,16 @@
package com.android.tools.r8.naming;
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.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
/**
* Implements a translation of the Dex graph from original names to new names produced by
@@ -39,6 +44,9 @@
abstract void forAllRenamedTypes(Consumer<DexType> consumer);
+ abstract <T extends DexItem> Map<String, T> getRenamedItems(
+ Class<T> clazz, Predicate<T> predicate, Function<T, String> namer);
+
/**
* Checks whether the target will be translated properly by this lense.
* <p>
@@ -75,6 +83,12 @@
}
@Override
+ <T extends DexItem> Map<String, T> getRenamedItems(
+ Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
+ return ImmutableMap.of();
+ }
+
+ @Override
public boolean checkTargetCanBeTranslated(DexMethod item) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 9ad96e2..dc800a3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -44,6 +44,7 @@
private Path packageObfuscationDictionary;
private boolean useUniqueClassMemberNames;
private boolean keepParameterNames;
+ private ProguardClassNameList adaptClassStrings = ProguardClassNameList.emptyList();
private Builder(DexItemFactory dexItemFactory) {
this.dexItemFactory = dexItemFactory;
@@ -201,6 +202,10 @@
return keepParameterNames;
}
+ public void setAdaptClassStrings(ProguardClassNameList adaptClassStrings) {
+ this.adaptClassStrings = adaptClassStrings;
+ }
+
public ProguardConfiguration build() throws CompilationException {
return new ProguardConfiguration(
dexItemFactory,
@@ -229,7 +234,8 @@
DictionaryReader.readAllNames(classObfuscationDictionary),
DictionaryReader.readAllNames(packageObfuscationDictionary),
useUniqueClassMemberNames,
- keepParameterNames);
+ keepParameterNames,
+ adaptClassStrings);
}
}
@@ -260,6 +266,7 @@
private final ImmutableList<String> packageObfuscationDictionary;
private boolean useUniqueClassMemberNames;
private boolean keepParameterNames;
+ private final ProguardClassNameList adaptClassStrings;
private ProguardConfiguration(
DexItemFactory factory,
@@ -288,7 +295,8 @@
ImmutableList<String> classObfuscationDictionary,
ImmutableList<String> packageObfuscationDictionary,
boolean useUniqueClassMemberNames,
- boolean keepParameterNames) {
+ boolean keepParameterNames,
+ ProguardClassNameList adaptClassStrings) {
this.dexItemFactory = factory;
this.injars = ImmutableList.copyOf(injars);
this.libraryjars = ImmutableList.copyOf(libraryjars);
@@ -316,6 +324,7 @@
this.packageObfuscationDictionary = packageObfuscationDictionary;
this.useUniqueClassMemberNames = useUniqueClassMemberNames;
this.keepParameterNames = keepParameterNames;
+ this.adaptClassStrings = adaptClassStrings;
}
/**
@@ -438,11 +447,17 @@
return keepParameterNames;
}
+ public ProguardClassNameList getAdaptClassStrings() {
+ return adaptClassStrings;
+ }
+
public static ProguardConfiguration defaultConfiguration(DexItemFactory dexItemFactory) {
try {
return builderInitializedWithDefaults(dexItemFactory).build();
} catch(CompilationException e) {
- throw new RuntimeException(); // Building a builder initialized with defaults will not throw CompilationException because DictionaryReader is called with empty lists.
+ // Building a builder initialized with defaults will not throw CompilationException because
+ // DictionaryReader is called with empty lists.
+ throw new RuntimeException();
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 50dae72..36f7efd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -279,13 +279,13 @@
} else if (acceptString("useuniqueclassmembernames")) {
configurationBuilder.setUseUniqueClassMemberNames(true);
} else if (acceptString("adaptclassstrings")) {
+ skipWhitespace();
if (isOptionalArgumentGiven()) {
- configurationBuilder.addRule(parseAdaptClassStrings());
+ configurationBuilder.setAdaptClassStrings(parseClassNames());
} else {
- configurationBuilder.addRule(ProguardIdentifierNameStringRule.defaultAllRule());
+ configurationBuilder.setAdaptClassStrings(
+ ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
}
- // TODO(b/36799092): warn until it is fully implemented.
- warnIgnoringOptions("adaptclassstrings");
} else if (acceptString("identifiernamestring")) {
configurationBuilder.addRule(parseIdentifierNameStringRule());
// TODO(b/36799092): warn until it is fully implemented.
@@ -464,14 +464,6 @@
return keepRuleBuilder.build();
}
- private ProguardIdentifierNameStringRule parseAdaptClassStrings()
- throws ProguardRuleParserException {
- ProguardIdentifierNameStringRule.Builder keepRuleBuilder =
- ProguardIdentifierNameStringRule.builder();
- keepRuleBuilder.setClassNames(parseClassNames());
- return keepRuleBuilder.build();
- }
-
private ProguardIdentifierNameStringRule parseIdentifierNameStringRule()
throws ProguardRuleParserException {
ProguardIdentifierNameStringRule.Builder keepRuleBuilder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index 44e7078..6cfd154 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -43,7 +43,7 @@
return "identifiernamestring";
}
- public static ProguardIdentifierNameStringRule defaultAllRule() {
+ public static ProguardIdentifierNameStringRule defaultAdaptAllRule() {
ProguardIdentifierNameStringRule.Builder builder = ProguardIdentifierNameStringRule.builder();
builder.setClassNames(
ProguardClassNameList.singletonList(ProguardTypeMatcher.defaultAllMatcher()));
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index aa9c360..b55a7a0 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -243,8 +243,17 @@
} finally {
application.timing.end();
}
- return new RootSet(noShrinking, noOptimization, noObfuscation, reasonAsked, keepPackageName,
- checkDiscarded, alwaysInline, noSideEffects, assumedValues, dependentNoShrinking);
+ return new RootSet(
+ noShrinking,
+ noOptimization,
+ noObfuscation,
+ reasonAsked,
+ keepPackageName,
+ checkDiscarded,
+ alwaysInline,
+ noSideEffects,
+ assumedValues,
+ dependentNoShrinking);
}
private void markMatchingVisibleMethods(DexClass clazz,
@@ -325,17 +334,14 @@
}
/**
- * Checks whether the given rule is satisfied bu this clazz, not taking superclasses into
+ * Checks whether the given rule is satisfied by this clazz, not taking superclasses into
* account.
*/
private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
- if (ruleSatisfiedByMethods(rule, clazz.directMethods()) ||
- ruleSatisfiedByMethods(rule, clazz.virtualMethods()) ||
- ruleSatisfiedByFields(rule, clazz.staticFields()) ||
- ruleSatisfiedByFields(rule, clazz.instanceFields())) {
- return true;
- }
- return false;
+ return ruleSatisfiedByMethods(rule, clazz.directMethods())
+ || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
+ || ruleSatisfiedByFields(rule, clazz.staticFields())
+ || ruleSatisfiedByFields(rule, clazz.instanceFields());
}
private boolean ruleSatisfiedByMethods(ProguardMemberRule rule, DexEncodedMethod[] methods) {
@@ -536,10 +542,15 @@
return true;
}
- private RootSet(Map<DexItem, ProguardKeepRule> noShrinking,
- Set<DexItem> noOptimization, Set<DexItem> noObfuscation, Set<DexItem> reasonAsked,
- Set<DexItem> keepPackageName, Set<DexItem> checkDiscarded,
- Set<DexItem> alwaysInline, Map<DexItem, ProguardMemberRule> noSideEffects,
+ private RootSet(
+ Map<DexItem, ProguardKeepRule> noShrinking,
+ Set<DexItem> noOptimization,
+ Set<DexItem> noObfuscation,
+ Set<DexItem> reasonAsked,
+ Set<DexItem> keepPackageName,
+ Set<DexItem> checkDiscarded,
+ Set<DexItem> alwaysInline,
+ Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
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);
}