Enable -dontusemixedcaseclassnames and allow numbers in identifiers
Bug: 129493704
Bug: 132812927
Change-Id: Id32cf84007abb4f6067f3fa44d96e0c97edc87af
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 166ec88..a3e71e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -21,7 +21,8 @@
import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence.Wrapper;
@@ -148,8 +149,10 @@
appView
.dexItemFactory()
.createString(
- StringUtils.numberToIdentifier(
- count, method.method.name.toSourceString().toCharArray()));
+ SymbolGenerationUtils.numberToIdentifier(
+ count,
+ MixedCasing.USE_MIXED_CASE,
+ method.method.name.toSourceString().toCharArray()));
} else {
// Constructors must be named `<init>`.
return null;
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index db8a84b..adbc1c3 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.StringUtils;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
@@ -340,20 +339,11 @@
void printLastName(String indentation, PrintStream out) {
out.print(indentation);
- out.print("Last name: ");
- int index = virtualNameCount + directNameCount;
- if (index > 1) {
- out.print(StringUtils.numberToIdentifier(index - 1));
- out.print(" (public name count: ");
- out.print(virtualNameCount);
- out.print(")");
- out.print(" (direct name count: ");
- out.print(directNameCount);
- out.print(")");
- } else {
- out.print("<NONE>");
- }
- out.println();
+ out.print("public name count: ");
+ out.print(virtualNameCount);
+ out.print(", ");
+ out.print("direct name count: ");
+ out.println(directNameCount);
}
}
}
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 b86cfc4..6906a06 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -22,7 +22,8 @@
import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
import com.android.tools.r8.utils.Timing;
import java.util.HashSet;
import java.util.List;
@@ -90,10 +91,13 @@
// dictionary. We use a list for direct indexing based on a number and a set for looking up.
private final List<String> obfuscationDictionary;
private final Set<String> obfuscationDictionaryForLookup;
+ private final MixedCasing mixedCasing;
- BaseMinificationNamingStrategy(List<String> obfuscationDictionary) {
+ BaseMinificationNamingStrategy(List<String> obfuscationDictionary, boolean dontUseMixedCasing) {
this.obfuscationDictionary = obfuscationDictionary;
this.obfuscationDictionaryForLookup = new HashSet<>(this.obfuscationDictionary);
+ this.mixedCasing =
+ dontUseMixedCasing ? MixedCasing.DONT_USE_MIXED_CASE : MixedCasing.USE_MIXED_CASE;
assert obfuscationDictionary != null;
}
@@ -105,7 +109,9 @@
} else {
String nextString;
do {
- nextString = StringUtils.numberToIdentifier(state.incrementNameIndex(isDirectMethodCall));
+ nextString =
+ SymbolGenerationUtils.numberToIdentifier(
+ state.incrementNameIndex(isDirectMethodCall), mixedCasing);
} while (obfuscationDictionaryForLookup.contains(nextString));
nextName.append(nextString);
}
@@ -120,7 +126,9 @@
private final DexItemFactory factory;
MinificationClassNamingStrategy(AppView<?> appView) {
- super(appView.options().getProguardConfiguration().getClassObfuscationDictionary());
+ super(
+ appView.options().getProguardConfiguration().getClassObfuscationDictionary(),
+ appView.options().getProguardConfiguration().hasDontUseMixedCaseClassnames());
this.appView = appView;
factory = appView.dexItemFactory();
}
@@ -140,7 +148,9 @@
implements PackageNamingStrategy {
MinificationPackageNamingStrategy(AppView<?> appView) {
- super(appView.options().getProguardConfiguration().getPackageObfuscationDictionary());
+ super(
+ appView.options().getProguardConfiguration().getPackageObfuscationDictionary(),
+ appView.options().getProguardConfiguration().hasDontUseMixedCaseClassnames());
}
@Override
@@ -160,7 +170,7 @@
private final DexItemFactory factory;
public MinifierMemberNamingStrategy(AppView<?> appView) {
- super(appView.options().getProguardConfiguration().getObfuscationDictionary());
+ super(appView.options().getProguardConfiguration().getObfuscationDictionary(), false);
this.appView = appView;
this.factory = appView.dexItemFactory();
}
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 d46a976..9d0adec 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -69,6 +69,7 @@
private boolean overloadAggressively;
private boolean keepRuleSynthesisForRecompilation = false;
private boolean configurationDebugging = false;
+ private boolean dontUseMixedCaseClassnames = false;
private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
this.dexItemFactory = dexItemFactory;
@@ -275,6 +276,10 @@
this.configurationDebugging = configurationDebugging;
}
+ public void setDontUseMixedCaseClassnames(boolean dontUseMixedCaseClassnames) {
+ this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
+ }
+
/**
* This synthesizes a set of keep rules that are necessary in order to be able to successfully
* recompile the generated dex files with the same keep rules.
@@ -332,7 +337,8 @@
adaptResourceFilenames.build(),
adaptResourceFileContents.build(),
keepDirectories.build(),
- configurationDebugging);
+ configurationDebugging,
+ dontUseMixedCaseClassnames);
reporter.failIfPendingErrors();
@@ -400,6 +406,7 @@
private final ProguardPathFilter adaptResourceFileContents;
private final ProguardPathFilter keepDirectories;
private final boolean configurationDebugging;
+ private final boolean dontUseMixedCaseClassnames;
private ProguardConfiguration(
String parsedConfiguration,
@@ -438,7 +445,8 @@
ProguardPathFilter adaptResourceFilenames,
ProguardPathFilter adaptResourceFileContents,
ProguardPathFilter keepDirectories,
- boolean configurationDebugging) {
+ boolean configurationDebugging,
+ boolean dontUseMixedCaseClassnames) {
this.parsedConfiguration = parsedConfiguration;
this.dexItemFactory = factory;
this.injars = ImmutableList.copyOf(injars);
@@ -476,6 +484,7 @@
this.adaptResourceFileContents = adaptResourceFileContents;
this.keepDirectories = keepDirectories;
this.configurationDebugging = configurationDebugging;
+ this.dontUseMixedCaseClassnames = dontUseMixedCaseClassnames;
}
/**
@@ -638,6 +647,10 @@
return configurationDebugging;
}
+ public boolean hasDontUseMixedCaseClassnames() {
+ return dontUseMixedCaseClassnames;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
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 e4113b3..ae25876 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -62,7 +62,6 @@
private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
"forceprocessing",
- "dontusemixedcaseclassnames",
"dontpreverify",
"experimentalshrinkunusedprotofields",
"filterlibraryjarswithorginalprogramjars",
@@ -401,6 +400,8 @@
configurationBuilder.addRule(parseIfRule(optionStart));
} else if (acceptString("addconfigurationdebugging")) {
configurationBuilder.setConfigurationDebugging(true);
+ } else if (acceptString("dontusemixedcaseclassnames")) {
+ configurationBuilder.setDontUseMixedCaseClassnames(true);
} else {
String unknownOption = acceptString();
String devMessage = "";
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 9064579..05900cb 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -17,10 +17,6 @@
public static final String[] EMPTY_ARRAY = {};
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
- private static final char[] IDENTIFIER_LETTERS
- = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".toCharArray();
- private static final int NUMBER_OF_LETTERS = IDENTIFIER_LETTERS.length;
-
public enum BraceType {
PARENS,
SQUARE,
@@ -257,39 +253,6 @@
return Arrays.toString(digest);
}
- public static String numberToIdentifier(int nameCount) {
- return numberToIdentifier(nameCount, EMPTY_CHAR_ARRAY, false);
- }
-
- public static String numberToIdentifier(int nameCount, char[] prefix) {
- return numberToIdentifier(nameCount, prefix, false);
- }
-
- public static String numberToIdentifier(int nameCount, char[] prefix, boolean addSemicolon) {
- // TODO(b132812927): Add support for using numbers.
- int size = addSemicolon ? 1 : 0;
- int number = nameCount;
- while (number >= NUMBER_OF_LETTERS) {
- number /= NUMBER_OF_LETTERS;
- size++;
- }
- size++;
- char characters[] = Arrays.copyOfRange(prefix, 0, prefix.length + size);
- number = nameCount;
-
- int i = prefix.length;
- while (number >= NUMBER_OF_LETTERS) {
- characters[i++] = IDENTIFIER_LETTERS[number % NUMBER_OF_LETTERS];
- number /= NUMBER_OF_LETTERS;
- }
- characters[i++] = IDENTIFIER_LETTERS[number - 1];
- if (addSemicolon) {
- characters[i++] = ';';
- }
- assert i == characters.length;
-
- return new String(characters);
- }
public static String times(String string, int count) {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
new file mode 100644
index 0000000..b66ca5d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
@@ -0,0 +1,73 @@
+// 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.utils;
+
+import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
+
+import java.util.Arrays;
+
+public class SymbolGenerationUtils {
+
+ public enum MixedCasing {
+ USE_MIXED_CASE,
+ DONT_USE_MIXED_CASE
+ }
+
+ // These letters are used not creating fresh names to output and not for parsing dex/class files.
+ private static final char[] IDENTIFIER_CHARACTERS =
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+ private static final int NUMBER_OF_CHARACTERS = IDENTIFIER_CHARACTERS.length;
+ private static final int NUMBER_OF_CHARACTERS_MINUS_CAPITAL_LETTERS = NUMBER_OF_CHARACTERS - 26;
+ private static final int NON_ALLOWED_FIRST_CHARACTERS = 10;
+
+ public static String numberToIdentifier(int nameCount, MixedCasing mixedCasing) {
+ return numberToIdentifier(nameCount, mixedCasing, EMPTY_CHAR_ARRAY, false);
+ }
+
+ public static String numberToIdentifier(int nameCount, MixedCasing mixedCasing, char[] prefix) {
+ return numberToIdentifier(nameCount, mixedCasing, prefix, false);
+ }
+
+ public static String numberToIdentifier(
+ int nameCount, MixedCasing mixedCasing, char[] prefix, boolean addSemicolon) {
+ int size = 1;
+ int number = nameCount;
+ int maximumNumberOfCharacters =
+ mixedCasing == MixedCasing.USE_MIXED_CASE
+ ? NUMBER_OF_CHARACTERS
+ : NUMBER_OF_CHARACTERS_MINUS_CAPITAL_LETTERS;
+ int firstNumberOfCharacters = maximumNumberOfCharacters - NON_ALLOWED_FIRST_CHARACTERS;
+ int availableCharacters = firstNumberOfCharacters;
+ // We first do an initial computation to find the size of the resulting string to allocate an
+ // array that will fit in length.
+ while (number > availableCharacters) {
+ number = (number - 1) / availableCharacters;
+ availableCharacters = maximumNumberOfCharacters;
+ size++;
+ }
+ size += addSemicolon ? 1 : 0;
+ char characters[] = Arrays.copyOfRange(prefix, 0, prefix.length + size);
+ number = nameCount;
+
+ int i = prefix.length;
+ availableCharacters = firstNumberOfCharacters;
+ int firstLetterPadding = NON_ALLOWED_FIRST_CHARACTERS;
+ while (number > availableCharacters) {
+ characters[i++] =
+ IDENTIFIER_CHARACTERS[(number - 1) % availableCharacters + firstLetterPadding];
+ number = (number - 1) / availableCharacters;
+ availableCharacters = maximumNumberOfCharacters;
+ firstLetterPadding = 0;
+ }
+ characters[i++] = IDENTIFIER_CHARACTERS[number - 1 + firstLetterPadding];
+ if (addSemicolon) {
+ characters[i++] = ';';
+ }
+ assert i == characters.length;
+ assert !Character.isDigit(characters[prefix.length]);
+
+ return new String(characters);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
new file mode 100644
index 0000000..55b3a9a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MinificationMixedCaseAndNumbersTest.java
@@ -0,0 +1,204 @@
+// 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.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.SymbolGenerationUtils;
+import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MinificationMixedCaseAndNumbersTest extends TestBase {
+
+ private static final int NUMBER_OF_MINIFIED_CLASSES = 60;
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public MinificationMixedCaseAndNumbersTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNaming() throws ExecutionException, CompilationFailedException, IOException {
+ Set<String> allowedNames = new HashSet<>();
+ allowedNames.add("com.android.tools.r8.naming.MinificationMixedCaseAndNumbersTest$Main");
+ for (int i = 1; i < NUMBER_OF_MINIFIED_CLASSES; i++) {
+ String newString =
+ SymbolGenerationUtils.numberToIdentifier(i, MixedCasing.DONT_USE_MIXED_CASE);
+ assertFalse(Character.isDigit(newString.charAt(0)));
+ allowedNames.add("com.android.tools.r8.naming." + newString);
+ }
+ testForR8(parameters.getBackend())
+ .addInnerClasses(MinificationMixedCaseAndNumbersTest.class)
+ .addKeepMainRule(Main.class)
+ .noTreeShaking()
+ .addKeepRules("-dontusemixedcaseclassnames")
+ .setMinApi(parameters.getRuntime())
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(
+ inspector -> {
+ List<FoundClassSubject> foundClassSubjects = new ArrayList<>(inspector.allClasses());
+ foundClassSubjects.forEach(
+ foundClazz -> {
+ assertTrue(allowedNames.contains(foundClazz.getFinalName()));
+ allowedNames.remove(foundClazz.getFinalName());
+ });
+ });
+ // The first identifier to use a number is 27 for MixedCasing.DONT_USE_MIXED_CASE.
+ assertTrue(allowedNames.isEmpty());
+ assertEquals(
+ "a0", SymbolGenerationUtils.numberToIdentifier(27, MixedCasing.DONT_USE_MIXED_CASE));
+ assertEquals(
+ "zz", SymbolGenerationUtils.numberToIdentifier(962, MixedCasing.DONT_USE_MIXED_CASE));
+ assertEquals(
+ "a00", SymbolGenerationUtils.numberToIdentifier(963, MixedCasing.DONT_USE_MIXED_CASE));
+ }
+
+ public static class A {}
+
+ public static class B {}
+
+ public static class C {}
+
+ public static class D {}
+
+ public static class E {}
+
+ public static class F {}
+
+ public static class G {}
+
+ public static class H {}
+
+ public static class I {}
+
+ public static class J {}
+
+ public static class K {}
+
+ public static class L {}
+
+ public static class M {}
+
+ public static class N {}
+
+ public static class O {}
+
+ public static class P {}
+
+ public static class Q {}
+
+ public static class R {}
+
+ public static class S {}
+
+ public static class T {}
+
+ public static class U {}
+
+ public static class V {}
+
+ public static class W {}
+
+ public static class X {}
+
+ public static class Y {}
+
+ public static class Z {}
+
+ public static class a {}
+
+ public static class b {}
+
+ public static class c {}
+
+ public static class d {}
+
+ public static class e {}
+
+ public static class f {}
+
+ public static class g {}
+
+ public static class h {}
+
+ public static class i {}
+
+ public static class j {}
+
+ public static class k {}
+
+ public static class l {}
+
+ public static class m {}
+
+ public static class n {}
+
+ public static class o {}
+
+ public static class p {}
+
+ public static class q {}
+
+ public static class r {}
+
+ public static class s {}
+
+ public static class t {}
+
+ public static class u {}
+
+ public static class v {}
+
+ public static class w {}
+
+ public static class x {}
+
+ public static class y {}
+
+ public static class z {}
+
+ public static class AA {}
+
+ public static class AB {}
+
+ public static class AC {}
+
+ public static class AD {}
+
+ public static class AE {}
+
+ public static class AF {}
+
+ public static class AG {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println("HELLO WORLD");
+ }
+ }
+}