Add tests for class and package obfuscation dictionary
This CL also rewrites the class and package strategy to centralize the
logic regarding naming.
Bug: 132666215
Change-Id: I92b97e7149114f92b17a00261329b905efeddaf0
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 1b50601..10430cc 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -30,8 +30,6 @@
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -50,8 +48,6 @@
private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
private final Map<String, Namespace> states = new HashMap<>();
- private final List<String> packageDictionary;
- private final List<String> classDictionary;
private final boolean keepInnerClassStructure;
private final Namespace topLevelState;
@@ -69,8 +65,6 @@
this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
this.isAccessModificationAllowed =
options.getProguardConfiguration().isAccessModificationAllowed();
- this.packageDictionary = options.getProguardConfiguration().getPackageObfuscationDictionary();
- this.classDictionary = options.getProguardConfiguration().getClassObfuscationDictionary();
this.keepInnerClassStructure =
options.getProguardConfiguration().getKeepAttributes().signature
|| options.getProguardConfiguration().getKeepAttributes().innerClasses;
@@ -356,12 +350,12 @@
}
}
- protected class Namespace {
+ protected class Namespace implements InternalNamingState {
private final String packageName;
private final char[] packagePrefix;
- private final Iterator<String> packageDictionaryIterator;
- private final Iterator<String> classDictionaryIterator;
+ private int dictionaryIndex = 0;
+ private int nameIndex = 1;
Namespace(String packageName) {
this(packageName, String.valueOf(DESCRIPTOR_PACKAGE_SEPARATOR));
@@ -373,8 +367,6 @@
// L or La/b/ (or La/b/C$)
+ (packageName.isEmpty() ? "" : separator))
.toCharArray();
- this.packageDictionaryIterator = packageDictionary.iterator();
- this.classDictionaryIterator = classDictionary.iterator();
// R.class in Android, which contains constant IDs to assets, can be bundled at any time.
// Insert `R` immediately so that the class name minifier can skip that name by default.
@@ -386,63 +378,49 @@
return packageName;
}
- private DexString nextSuggestedNameForClass(DexType type) {
- StringBuilder nextName = new StringBuilder();
- if (!classNamingStrategy.bypassDictionary() && classDictionaryIterator.hasNext()) {
- nextName.append(packagePrefix).append(classDictionaryIterator.next()).append(';');
- return appView.dexItemFactory().createString(nextName.toString());
- } else {
- return classNamingStrategy.next(this, type, packagePrefix);
- }
- }
-
DexString nextTypeName(DexType type) {
DexString candidate;
do {
- candidate = nextSuggestedNameForClass(type);
+ candidate = classNamingStrategy.next(type, packagePrefix, this);
} while (usedTypeNames.contains(candidate));
usedTypeNames.add(candidate);
return candidate;
}
- private String nextSuggestedNameForSubpackage() {
- // Note that the differences between this method and the other variant for class renaming are
- // 1) this one uses the different dictionary and counter,
- // 2) this one does not append ';' at the end, and
- // 3) this one removes 'L' at the beginning to make the return value a binary form.
- if (!packageNamingStrategy.bypassDictionary() && packageDictionaryIterator.hasNext()) {
- StringBuilder nextName = new StringBuilder();
- nextName.append(packagePrefix).append(packageDictionaryIterator.next());
- return nextName.toString().substring(1);
- } else {
- return packageNamingStrategy.next(this, packagePrefix);
- }
- }
-
String nextPackagePrefix() {
String candidate;
do {
- candidate = nextSuggestedNameForSubpackage();
+ candidate = packageNamingStrategy.next(packagePrefix, this);
} while (usedPackagePrefixes.contains(candidate));
usedPackagePrefixes.add(candidate);
return candidate;
}
+
+ @Override
+ public int getDictionaryIndex() {
+ return dictionaryIndex;
+ }
+
+ @Override
+ public int incrementDictionaryIndex() {
+ return dictionaryIndex++;
+ }
+
+ @Override
+ public int incrementNameIndex(boolean isDirectMethodCall) {
+ assert !isDirectMethodCall;
+ return nameIndex++;
+ }
}
protected interface ClassNamingStrategy {
-
- DexString next(Namespace namespace, DexType type, char[] packagePrefix);
-
- boolean bypassDictionary();
+ DexString next(DexType type, char[] packagePrefix, InternalNamingState state);
boolean noObfuscation(DexType type);
}
protected interface PackageNamingStrategy {
-
- String next(Namespace namespace, char[] packagePrefix);
-
- boolean bypassDictionary();
+ String next(char[] packagePrefix, InternalNamingState state);
}
/**
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index b713582..d654558 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.FieldNamingState.InternalState;
-import com.android.tools.r8.naming.MemberNamingStrategy.MemberNamingInternalState;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -80,7 +79,7 @@
return new FieldNamingState(appView, strategy, reservedNames, internalStatesClone);
}
- class InternalState implements MemberNamingInternalState, Cloneable {
+ class InternalState implements InternalNamingState, Cloneable {
private int dictionaryIndex;
private int nextNameIndex;
diff --git a/src/main/java/com/android/tools/r8/naming/InternalNamingState.java b/src/main/java/com/android/tools/r8/naming/InternalNamingState.java
new file mode 100644
index 0000000..e5749c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/InternalNamingState.java
@@ -0,0 +1,14 @@
+// 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;
+
+interface InternalNamingState {
+
+ int getDictionaryIndex();
+
+ int incrementDictionaryIndex();
+
+ int incrementNameIndex(boolean isDirectMethodCall);
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index 260b176..f9e8ed5 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -11,20 +11,11 @@
public interface MemberNamingStrategy {
- DexString next(DexMethod method, MemberNamingInternalState internalState);
+ DexString next(DexMethod method, InternalNamingState internalState);
- DexString next(DexField field, MemberNamingInternalState internalState);
+ DexString next(DexField field, InternalNamingState internalState);
boolean breakOnNotAvailable(DexReference source, DexString name);
boolean noObfuscation(DexReference reference);
-
- interface MemberNamingInternalState {
-
- int getDictionaryIndex();
-
- int incrementDictionaryIndex();
-
- int incrementNameIndex(boolean isDirectMethodCall);
- }
}
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 7b0c5fb..8b9d042 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.naming.MemberNamingStrategy.MemberNamingInternalState;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Sets;
import java.io.PrintStream;
@@ -131,7 +130,7 @@
}
}
- class InternalState implements MemberNamingInternalState {
+ class InternalState implements InternalNamingState {
private static final int INITIAL_NAME_COUNT = 1;
private static final int INITIAL_DICTIONARY_INDEX = 0;
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 4bdf87c..d2424fe 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
@@ -15,15 +17,12 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
-import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
import com.android.tools.r8.naming.ClassNameMinifier.PackageNamingStrategy;
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.Timing;
-import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -50,7 +49,7 @@
new ClassNameMinifier(
appView,
new MinificationClassNamingStrategy(appView),
- new MinificationPackageNamingStrategy(),
+ new MinificationPackageNamingStrategy(appView),
// Use deterministic class order to make sure renaming is deterministic.
appView.appInfo().classesWithDeterministicOrder());
ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
@@ -84,29 +83,50 @@
return lens;
}
- static class MinificationClassNamingStrategy implements ClassNamingStrategy {
+ abstract static class BaseMinificationNamingStrategy {
+
+ // We have to ensure that the names proposed by the minifier is not used in the obfuscation
+ // 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;
+
+ BaseMinificationNamingStrategy(List<String> obfuscationDictionary) {
+ this.obfuscationDictionary = obfuscationDictionary;
+ this.obfuscationDictionaryForLookup = new HashSet<>(this.obfuscationDictionary);
+ assert obfuscationDictionary != null;
+ }
+
+ String nextName(char[] packagePrefix, InternalNamingState state, boolean isDirectMethodCall) {
+ StringBuilder nextName = new StringBuilder();
+ nextName.append(packagePrefix);
+ if (state.getDictionaryIndex() < obfuscationDictionary.size()) {
+ nextName.append(obfuscationDictionary.get(state.incrementDictionaryIndex()));
+ } else {
+ String nextString;
+ do {
+ nextString = StringUtils.numberToIdentifier(state.incrementNameIndex(isDirectMethodCall));
+ } while (obfuscationDictionaryForLookup.contains(nextString));
+ nextName.append(nextString);
+ }
+ return nextName.toString();
+ }
+ }
+
+ static class MinificationClassNamingStrategy extends BaseMinificationNamingStrategy
+ implements ClassNamingStrategy {
private final AppView<?> appView;
- private final Object2IntMap<Namespace> namespaceCounters = new Object2IntLinkedOpenHashMap<>();
+ private final DexItemFactory factory;
MinificationClassNamingStrategy(AppView<?> appView) {
+ super(appView.options().getProguardConfiguration().getClassObfuscationDictionary());
this.appView = appView;
- namespaceCounters.defaultReturnValue(1);
+ factory = appView.dexItemFactory();
}
@Override
- public DexString next(Namespace namespace, DexType type, char[] packagePrefix) {
- int counter = namespaceCounters.put(namespace, namespaceCounters.getInt(namespace) + 1);
- DexString string =
- appView
- .dexItemFactory()
- .createString(StringUtils.numberToIdentifier(counter, packagePrefix, true));
- return string;
- }
-
- @Override
- public boolean bypassDictionary() {
- return false;
+ public DexString next(DexType type, char[] packagePrefix, InternalNamingState state) {
+ return factory.createString(nextName(packagePrefix, state, false) + ";");
}
@Override
@@ -115,74 +135,50 @@
}
}
- static class MinificationPackageNamingStrategy implements PackageNamingStrategy {
+ static class MinificationPackageNamingStrategy extends BaseMinificationNamingStrategy
+ implements PackageNamingStrategy {
- private final Object2IntMap<Namespace> namespaceCounters = new Object2IntLinkedOpenHashMap<>();
-
- public MinificationPackageNamingStrategy() {
- namespaceCounters.defaultReturnValue(1);
+ MinificationPackageNamingStrategy(AppView<?> appView) {
+ super(appView.options().getProguardConfiguration().getPackageObfuscationDictionary());
}
@Override
- public String next(Namespace namespace, char[] packagePrefix) {
+ public String next(char[] packagePrefix, InternalNamingState state) {
// Note that the differences between this method and the other variant for class renaming are
// 1) this one uses the different dictionary and counter,
// 2) this one does not append ';' at the end, and
// 3) this one removes 'L' at the beginning to make the return value a binary form.
- int counter = namespaceCounters.put(namespace, namespaceCounters.getInt(namespace) + 1);
- return StringUtils.numberToIdentifier(counter, packagePrefix, false).substring(1);
- }
-
- @Override
- public boolean bypassDictionary() {
- return false;
+ return nextName(packagePrefix, state, false).substring(1);
}
}
- static class MinifierMemberNamingStrategy implements MemberNamingStrategy {
+ static class MinifierMemberNamingStrategy extends BaseMinificationNamingStrategy
+ implements MemberNamingStrategy {
private final DexItemFactory factory;
- // We have to ensure that the names proposed by the minifier is not used in the obfuscation
- // 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 AppView<?> appView;
public MinifierMemberNamingStrategy(AppView<?> appView) {
+ super(appView.options().getProguardConfiguration().getObfuscationDictionary());
this.appView = appView;
this.factory = appView.dexItemFactory();
- this.obfuscationDictionary =
- appView.options().getProguardConfiguration().getObfuscationDictionary();
- this.obfuscationDictionaryForLookup = new HashSet<>(this.obfuscationDictionary);
- assert this.obfuscationDictionary != null;
}
@Override
- public DexString next(DexMethod method, MemberNamingInternalState internalState) {
+ public DexString next(DexMethod method, InternalNamingState internalState) {
DexEncodedMethod encodedMethod = appView.definitionFor(method);
boolean isDirectOrStatic = encodedMethod.isDirectMethod() || encodedMethod.isStatic();
return getNextName(internalState, isDirectOrStatic);
}
@Override
- public DexString next(DexField field, MemberNamingInternalState internalState) {
+ public DexString next(DexField field, InternalNamingState internalState) {
return getNextName(internalState, false);
}
- private DexString getNextName(
- MemberNamingInternalState internalState, boolean isDirectOrStatic) {
- if (internalState.getDictionaryIndex() < obfuscationDictionary.size()) {
- return factory.createString(
- obfuscationDictionary.get(internalState.incrementDictionaryIndex()));
- } else {
- String nextString;
- do {
- int counter = internalState.incrementNameIndex(isDirectOrStatic);
- nextString = StringUtils.numberToIdentifier(counter);
- } while (obfuscationDictionaryForLookup.contains(nextString));
- return factory.createString(nextString);
- }
+ private DexString getNextName(InternalNamingState internalState, boolean isDirectOrStatic) {
+ return factory.createString(nextName(EMPTY_CHAR_ARRAY, internalState, isDirectOrStatic));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 4c2c8cf..90a6760 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
-import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -122,7 +121,7 @@
// will be output with identity name if not found in mapping. However, there is a check
// in the ClassNameMinifier that the strategy should produce a "fresh" name so we just
// use the existing strategy.
- new MinificationPackageNamingStrategy(),
+ new MinificationPackageNamingStrategy(appView),
mappedClasses);
ClassRenaming classRenaming =
classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
@@ -205,16 +204,11 @@
}
@Override
- public DexString next(Namespace namespace, DexType type, char[] packagePrefix) {
+ public DexString next(DexType type, char[] packagePrefix, InternalNamingState state) {
return mappings.getOrDefault(type, type.descriptor);
}
@Override
- public boolean bypassDictionary() {
- return true;
- }
-
- @Override
public boolean noObfuscation(DexType type) {
// We have an explicit mapping from the proguard map thus everything might have to be renamed.
return false;
@@ -235,12 +229,12 @@
}
@Override
- public DexString next(DexMethod method, MemberNamingInternalState internalState) {
+ public DexString next(DexMethod method, InternalNamingState internalState) {
return next(method);
}
@Override
- public DexString next(DexField field, MemberNamingInternalState internalState) {
+ public DexString next(DexField field, InternalNamingState internalState) {
return next(field);
}
diff --git a/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
new file mode 100644
index 0000000..4f3a868
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
@@ -0,0 +1,79 @@
+// 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.HashSet;
+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 ClassObfuscationDictionaryDuplicateTest extends TestBase {
+
+ public static class A {}
+
+ public static class B {}
+
+ public static class C {
+ public static void main(String[] args) {
+ System.out.print("HELLO WORLD!");
+ }
+ }
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public ClassObfuscationDictionaryDuplicateTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws IOException, CompilationFailedException, ExecutionException {
+ Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+ FileUtils.writeTextFile(dictionary, "a");
+
+ Set<String> finalNames = new HashSet<>();
+ finalNames.add(A.class.getPackage().getName() + ".a");
+ finalNames.add(B.class.getPackage().getName() + ".b");
+
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, C.class)
+ .noTreeShaking()
+ .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+ .addKeepMainRule(C.class)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .run(parameters.getRuntime(), C.class)
+ .assertSuccessWithOutput("HELLO WORLD!")
+ .inspect(
+ inspector -> {
+ ClassSubject clazzA = inspector.clazz(A.class);
+ assertThat(clazzA, isPresent());
+ assertTrue(finalNames.contains(clazzA.getFinalName()));
+ finalNames.remove(clazzA.getFinalName());
+ ClassSubject clazzB = inspector.clazz(B.class);
+ assertThat(clazzB, isPresent());
+ assertTrue(finalNames.contains(clazzB.getFinalName()));
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
new file mode 100644
index 0000000..f10947e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
@@ -0,0 +1,65 @@
+// 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 junit.framework.TestCase.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.naming.keeppackagenames.Top;
+import com.android.tools.r8.naming.packageobfucationdict.A;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PackageObfuscationDictionaryDuplicateTest extends TestBase {
+
+ public static class C {
+ public static void main(String[] args) {
+ System.out.print("HELLO WORLD!");
+ }
+ }
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public PackageObfuscationDictionaryDuplicateTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws IOException, CompilationFailedException, ExecutionException {
+ Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+ FileUtils.writeTextFile(dictionary, "a");
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(Top.class, A.class, C.class)
+ .noTreeShaking()
+ .addKeepRules("-packageobfuscationdictionary " + dictionary.toString())
+ .addKeepMainRule(C.class)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .run(parameters.getRuntime(), C.class)
+ .assertSuccessWithOutput("HELLO WORLD!")
+ .inspect(
+ inspector -> {
+ ClassSubject clazzTop = inspector.clazz(Top.class);
+ assertTrue(clazzTop.getFinalName().endsWith(".a.a"));
+ ClassSubject clazzA = inspector.clazz(A.class);
+ assertTrue(clazzA.getFinalName().endsWith(".b.a"));
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java b/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
new file mode 100644
index 0000000..012140e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
@@ -0,0 +1,7 @@
+// 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.packageobfucationdict;
+
+public class A {}