Package obfuscation w/o access modification.

Bug: 62048823
Change-Id: I6b8e81b88bca923a72a641c96ea20d24747861e8
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 c1e8515..3ef67e6 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -40,6 +40,8 @@
   private final AppInfoWithLiveness appInfo;
   private final RootSet rootSet;
   private final PackageObfuscationMode packageObfuscationMode;
+  private final boolean isAccessModificationAllowed;
+  private final Set<String> noObfuscationPrefixes = Sets.newHashSet();
   private final Set<String> usedPackagePrefixes = Sets.newHashSet();
   private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
 
@@ -63,6 +65,7 @@
     this.appInfo = appInfo;
     this.rootSet = rootSet;
     this.packageObfuscationMode = options.proguardConfiguration.getPackageObfuscationMode();
+    this.isAccessModificationAllowed = options.proguardConfiguration.isAccessModificationAllowed();
     this.packageDictionary = options.proguardConfiguration.getPackageObfuscationDictionary();
     this.classDictionary = options.proguardConfiguration.getClassObfuscationDictionary();
     this.keepInnerClassStructure = options.attributeRemoval.signature;
@@ -164,6 +167,11 @@
    * Registers the given package prefix and all of parent packages as used.
    */
   private void registerPackagePrefixesAsUsed(String packagePrefix) {
+    // If -allowaccessmodification is not set, we may keep classes in their original packages,
+    // accounting for package-private accesses.
+    if (!isAccessModificationAllowed) {
+      noObfuscationPrefixes.add(packagePrefix);
+    }
     String usedPrefix = packagePrefix;
     while (usedPrefix.length() > 0) {
       usedPackagePrefixes.add(usedPrefix);
@@ -209,7 +217,9 @@
   private Namespace getStateForClass(DexClass clazz) {
     String packageName = getPackageBinaryNameFromJavaType(clazz.type.getPackageDescriptor());
     // Check whether the given class should be kept.
-    if (rootSet.keepPackageName.contains(clazz)) {
+    // or check whether the given class belongs to a package that is kept for another class.
+    if (rootSet.keepPackageName.contains(clazz)
+        || noObfuscationPrefixes.contains(packageName)) {
       return states.computeIfAbsent(packageName, Namespace::new);
     }
     Namespace state = topLevelState;
@@ -320,6 +330,7 @@
       do {
         candidate = appInfo.dexItemFactory.createString(nextSuggestedNameForClass());
       } while (usedTypeNames.contains(candidate));
+      usedTypeNames.add(candidate);
       return candidate;
     }
 
@@ -342,6 +353,7 @@
       do {
         candidate = nextSuggestedNameForSubpackage();
       } while (usedPackagePrefixes.contains(candidate));
+      usedPackagePrefixes.add(candidate);
       return candidate;
     }
 
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 951790c..a26e135 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -36,10 +36,6 @@
 
   public NamingLens run(Timing timing) {
     assert !options.skipMinification;
-    // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
-    if (!options.proguardConfiguration.isAccessModificationAllowed()) {
-      throw new CompilationError("Minification requires allowaccessmodification.");
-    }
     timing.begin("MinifyClasses");
     Map<DexType, DexString> classRenaming =
         new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
diff --git a/src/test/examples/naming101/keep-rules-102.txt b/src/test/examples/naming101/keep-rules-102.txt
new file mode 100644
index 0000000..b1c0cda
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-102.txt
@@ -0,0 +1,13 @@
+# 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.
+
+-repackageclasses 'naming101.a'
+
+-keep public class **.a {
+  *;
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/examples/naming101/keep-rules-104.txt b/src/test/examples/naming101/keep-rules-104.txt
new file mode 100644
index 0000000..6866dee
--- /dev/null
+++ b/src/test/examples/naming101/keep-rules-104.txt
@@ -0,0 +1,13 @@
+# 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.
+
+-flattenpackagehierarchy 'naming101'
+
+-keep public class **.a {
+  *;
+}
+
+-keep,allowobfuscation class * {
+  *;
+}
diff --git a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
index b8e22d2..5975483 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageNamingTest.java
@@ -17,12 +17,14 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 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.Set;
 import java.util.function.BiConsumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,6 +47,16 @@
     inspection.accept(dexItemFactory, naming);
   }
 
+  private static final List<String> CLASSES_IN_NAMING044 = ImmutableList.of(
+      "Lnaming044/A;", "Lnaming044/B;", "Lnaming044/sub/SubA;", "Lnaming044/sub/SubB;");
+
+  private static final List<String> CLASSES_IN_NAMING101 = ImmutableList.of(
+      "Lnaming101/c;", "Lnaming101/d;",
+      "Lnaming101/a/a;", "Lnaming101/a/c;",
+      "Lnaming101/a/b/c",
+      "Lnaming101/b/a'", "Lnaming101/b/b;"
+  );
+
   @Parameters(name = "test: {0} keep: {1}")
   public static Collection<Object[]> data() {
     List<String> tests = Arrays.asList("naming044", "naming101");
@@ -60,6 +72,8 @@
     inspections.put("naming101:keep-rules-003.txt", PackageNamingTest::test101_rule003);
     inspections.put("naming101:keep-rules-004.txt", PackageNamingTest::test101_rule004);
     inspections.put("naming101:keep-rules-005.txt", PackageNamingTest::test101_rule005);
+    inspections.put("naming101:keep-rules-102.txt", PackageNamingTest::test101_rule102);
+    inspections.put("naming101:keep-rules-104.txt", PackageNamingTest::test101_rule104);
 
     return createTests(tests, inspections);
   }
@@ -68,8 +82,23 @@
     return CharMatcher.is('/').countIn(descriptor);
   }
 
+  private static void verifyUniqueNaming(
+      DexItemFactory dexItemFactory,
+      NamingLens naming,
+      List<String> klasses) {
+    Set<String> renamedNames = Sets.newHashSet();
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String rename = naming.lookupDescriptor(k).toSourceString();
+      assertFalse(renamedNames.contains(rename));
+      renamedNames.add(rename);
+    }
+  }
+
   // repackageclasses ''
   private static void test044_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All classes are moved to the top-level package, hence no package separator.
     DexType b = dexItemFactory.createType("Lnaming044/B;");
     assertFalse(naming.lookupDescriptor(b).toSourceString().contains("/"));
@@ -86,6 +115,8 @@
 
   // repackageclasses 'p44.x'
   private static void test044_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All classes are moved to a single package, so they all have the same package prefix.
     DexType a = dexItemFactory.createType("Lnaming044/A;");
     assertTrue(naming.lookupDescriptor(a).toSourceString().startsWith("Lp44/x/"));
@@ -103,6 +134,8 @@
 
   // flattenpackagehierarchy ''
   private static void test044_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All packages are moved to the top-level package, hence only one package separator.
     DexType b = dexItemFactory.createType("Lnaming044/B;");
     assertEquals(1, countPackageDepth(naming.lookupDescriptor(b).toSourceString()));
@@ -145,6 +178,8 @@
   }
 
   private static void test044_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING044);
+
     // All packages are renamed somehow. Need to check package hierarchy is consistent.
     DexType a = dexItemFactory.createType("Lnaming044/A;");
     assertEquals(1, countPackageDepth(naming.lookupDescriptor(a).toSourceString()));
@@ -171,6 +206,8 @@
   }
 
   private static void test101_rule001(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
     // All classes are moved to the top-level package, hence no package separator.
     DexType c = dexItemFactory.createType("Lnaming101/c;");
     assertFalse(naming.lookupDescriptor(c).toSourceString().contains("/"));
@@ -183,9 +220,14 @@
   }
 
   private static void test101_rule002(DexItemFactory dexItemFactory, NamingLens naming) {
-    // Check naming101.a.a is kept due to **.a
-    DexType a = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+
     // Repackaged to naming101.a, but naming101.a.a exists to make a name conflict.
     // Thus, everything else should not be renamed to 'a',
     // except for naming101.b.a, which is also kept due to **.a
@@ -204,6 +246,8 @@
   }
 
   private static void test101_rule003(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
     // All packages are moved to the top-level package, hence only one package separator.
     DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
     assertEquals(1, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
@@ -217,9 +261,14 @@
   }
 
   private static void test101_rule004(DexItemFactory dexItemFactory, NamingLens naming) {
-    // Check naming101.a.a is kept due to **.a
-    DexType a = dexItemFactory.createType("Lnaming101/a/a;");
-    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(a).toSourceString());
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+
     // Flattened to naming101, hence all other classes will be in naming101.* package.
     // Due to naming101.a.a, prefix naming101.a is already used. So, any other classes,
     // except for naming101.a.c, should not have naming101.a as package.
@@ -237,6 +286,8 @@
   }
 
   private static void test101_rule005(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
     // All packages are renamed somehow. Need to check package hierarchy is consistent.
     DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
     assertEquals(2, countPackageDepth(naming.lookupDescriptor(aa).toSourceString()));
@@ -250,4 +301,60 @@
         getParentPackagePrefix(
             getParentPackagePrefix(naming.lookupDescriptor(abc).toSourceString())));
   }
+
+  private static void test101_rule102(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    // Due to package-private access, classes in the same package should remain as-is.
+    DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
+    assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+    DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+    assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+
+    // All other classes can be repackaged to naming101.a, but naming101.a.a exists to make a name
+    // conflict. Thus, those should not be renamed to 'a'.
+    List<String> klasses = ImmutableList.of(
+        "Lnaming101/c;",
+        "Lnaming101/d;",
+        "Lnaming101/a/b/c;");
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String renamedName = naming.lookupDescriptor(k).toSourceString();
+      assertEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+      assertNotEquals("Lnaming101/a/a;", renamedName);
+    }
+  }
+
+  private static void test101_rule104(DexItemFactory dexItemFactory, NamingLens naming) {
+    verifyUniqueNaming(dexItemFactory, naming, CLASSES_IN_NAMING101);
+
+    // Check naming101.*.a is kept due to **.a
+    DexType aa = dexItemFactory.createType("Lnaming101/a/a;");
+    assertEquals("Lnaming101/a/a;", naming.lookupDescriptor(aa).toSourceString());
+    DexType ba = dexItemFactory.createType("Lnaming101/b/a;");
+    assertEquals("Lnaming101/b/a;", naming.lookupDescriptor(ba).toSourceString());
+    // Due to package-private access, classes in the same package should remain as-is.
+    DexType ab = dexItemFactory.createType("Lnaming101/a/b;");
+    assertEquals("Lnaming101/a/b;", naming.lookupDescriptor(ab).toSourceString());
+    DexType bb = dexItemFactory.createType("Lnaming101/b/b;");
+    assertEquals("Lnaming101/b/b;", naming.lookupDescriptor(bb).toSourceString());
+
+    // All other packages are flattened to naming101, hence all other classes will be in
+    // naming101.* package. Due to naming101.a.a, prefix naming101.a is already used. So,
+    // remaining classes should not have naming101.a as package.
+    List<String> klasses = ImmutableList.of(
+        "Lnaming101/c;",
+        "Lnaming101/d;",
+        "Lnaming101/a/b/c;");
+    for (String klass : klasses) {
+      DexType k = dexItemFactory.createType(klass);
+      String renamedName = naming.lookupDescriptor(k).toSourceString();
+      assertNotEquals("naming101.a", getPackageNameFromDescriptor(renamedName));
+    }
+  }
 }