|  | // 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 com.android.tools.r8.naming.ClassNameMinifier.getParentPackagePrefix; | 
|  | import static org.hamcrest.CoreMatchers.startsWith; | 
|  | import static org.hamcrest.MatcherAssert.assertThat; | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertFalse; | 
|  | import static org.junit.Assert.assertNotEquals; | 
|  | import static org.junit.Assert.assertTrue; | 
|  |  | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.utils.ThrowingConsumer; | 
|  | import com.android.tools.r8.utils.codeinspector.ClassSubject; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import com.android.tools.r8.utils.codeinspector.MethodSubject; | 
|  | import com.google.common.base.CharMatcher; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.io.File; | 
|  | import java.nio.file.Path; | 
|  | import java.nio.file.Paths; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Arrays; | 
|  | import java.util.Collection; | 
|  | import java.util.HashMap; | 
|  | import java.util.HashSet; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  | import java.util.stream.Collectors; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.Parameterized; | 
|  | import org.junit.runners.Parameterized.Parameters; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class PackageNamingTest extends TestBase { | 
|  |  | 
|  | private final Path input; | 
|  | private final Path keepRulesFile; | 
|  | private final ThrowingConsumer<CodeInspector, RuntimeException> inspection; | 
|  |  | 
|  | @Parameters(name = "test: {0} keep: {1}") | 
|  | public static Collection<Object[]> data() { | 
|  | List<String> tests = Arrays.asList("naming044", "naming101"); | 
|  | Map<String, ThrowingConsumer<CodeInspector, RuntimeException>> inspections = new HashMap<>(); | 
|  | inspections.put("naming044:keep-rules-001.txt", PackageNamingTest::test044_rule001); | 
|  | inspections.put("naming044:keep-rules-002.txt", PackageNamingTest::test044_rule002); | 
|  | inspections.put("naming044:keep-rules-003.txt", PackageNamingTest::test044_rule003); | 
|  | inspections.put("naming044:keep-rules-004.txt", PackageNamingTest::test044_rule004); | 
|  | inspections.put("naming044:keep-rules-005.txt", PackageNamingTest::test044_rule005); | 
|  | inspections.put("naming101:keep-rules-001.txt", PackageNamingTest::test101_rule001); | 
|  | inspections.put("naming101:keep-rules-002.txt", PackageNamingTest::test101_rule002); | 
|  | 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); | 
|  | inspections.put("naming101:keep-rules-106.txt", PackageNamingTest::test101_rule106); | 
|  | return createTests(tests, inspections); | 
|  | } | 
|  |  | 
|  | public PackageNamingTest( | 
|  | String test, | 
|  | Path keepRulesFile, | 
|  | ThrowingConsumer<CodeInspector, RuntimeException> inspection) { | 
|  | this.input = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar"); | 
|  | this.keepRulesFile = keepRulesFile; | 
|  | this.inspection = inspection; | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void packageNamingTest() throws Exception { | 
|  | testForR8(Backend.DEX) | 
|  | .addProgramFiles(input) | 
|  | .addKeepRuleFiles(keepRulesFile) | 
|  | .setMinApi(21) | 
|  | .compile() | 
|  | .inspect(inspection); | 
|  | } | 
|  |  | 
|  | private static final List<String> CLASSES_IN_NAMING044 = | 
|  | ImmutableList.of("naming044.A", "naming044.B", "naming044.sub.SubA", "naming044.sub.SubB"); | 
|  |  | 
|  | private static final List<String> CLASSES_IN_NAMING101 = | 
|  | ImmutableList.of( | 
|  | "naming101.c", | 
|  | "naming101.d", | 
|  | "naming101.a.a", | 
|  | "naming101.a.c", | 
|  | "naming101.a.b.c", | 
|  | "naming101.b.a'", | 
|  | "naming101.b.b"); | 
|  |  | 
|  | private static List<Object[]> createTests( | 
|  | List<String> tests, | 
|  | Map<String, ThrowingConsumer<CodeInspector, RuntimeException>> inspections) { | 
|  | List<Object[]> testCases = new ArrayList<>(); | 
|  | Set<String> usedInspections = new HashSet<>(); | 
|  | for (String test : tests) { | 
|  | File[] keepFiles = | 
|  | new File(ToolHelper.EXAMPLES_DIR + test) | 
|  | .listFiles(file -> file.isFile() && file.getName().endsWith(".txt")); | 
|  | for (File keepFile : keepFiles) { | 
|  | String keepName = keepFile.getName(); | 
|  | ThrowingConsumer<CodeInspector, RuntimeException> inspection = | 
|  | getTestOptionalParameter(inspections, usedInspections, test, keepName); | 
|  | if (inspection != null) { | 
|  | testCases.add(new Object[] {test, keepFile.toPath(), inspection}); | 
|  | } | 
|  | } | 
|  | } | 
|  | assert usedInspections.size() == inspections.size(); | 
|  | return testCases; | 
|  | } | 
|  |  | 
|  | private static <T> T getTestOptionalParameter( | 
|  | Map<String, T> specifications, Set<String> usedSpecifications, String test, String keepName) { | 
|  | T parameter = specifications.get(test); | 
|  | if (parameter == null) { | 
|  | parameter = specifications.get(test + ":" + keepName); | 
|  | if (parameter != null) { | 
|  | usedSpecifications.add(test + ":" + keepName); | 
|  | } | 
|  | } else { | 
|  | usedSpecifications.add(test); | 
|  | } | 
|  | return parameter; | 
|  | } | 
|  |  | 
|  | private static int countPackageDepth(ClassSubject subject) { | 
|  | return CharMatcher.is('.').countIn(subject.getFinalName()); | 
|  | } | 
|  |  | 
|  | private static void verifyUniqueNaming(CodeInspector inspector, List<String> klasses) { | 
|  | Set<String> renamedNames = Sets.newHashSet(); | 
|  | for (String klass : klasses) { | 
|  | String finalName = inspector.clazz(klass).getFinalName(); | 
|  | assertFalse(renamedNames.contains(finalName)); | 
|  | renamedNames.add(finalName); | 
|  | } | 
|  | } | 
|  |  | 
|  | // repackageclasses '' | 
|  | private static void test044_rule001(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING044); | 
|  |  | 
|  | // All classes are moved to the top-level package, hence no package separator. | 
|  | ClassSubject bClassSubject = inspector.clazz("naming044.B"); | 
|  | assertFalse(bClassSubject.getFinalName().contains(".")); | 
|  |  | 
|  | // Even classes in a sub-package are moved to the same top-level package. | 
|  | assertFalse(inspector.clazz("naming044.sub.SubB").getFinalName().contains(".")); | 
|  |  | 
|  | // Method naming044.B.m would be renamed. | 
|  | MethodSubject mMethodSubject = bClassSubject.uniqueMethodWithName("m"); | 
|  | assertNotEquals("m", mMethodSubject.getMethod().getName().toSourceString()); | 
|  | } | 
|  |  | 
|  | // repackageclasses 'p44.x' | 
|  | private static void test044_rule002(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING044); | 
|  |  | 
|  | // All classes are moved to a single package, so they all have the same package prefix. | 
|  | assertThat(inspector.clazz("naming044.A").getFinalName(), startsWith("p44.x.")); | 
|  |  | 
|  | ClassSubject bClassSubject = inspector.clazz("naming044.B"); | 
|  | assertThat(bClassSubject.getFinalName(), startsWith("p44.x.")); | 
|  |  | 
|  | ClassSubject subBClassSubject = inspector.clazz("naming044.sub.SubB"); | 
|  | assertThat(subBClassSubject.getFinalName(), startsWith("p44.x.")); | 
|  |  | 
|  | // Even classes in a sub-package are moved to the same package. | 
|  | assertEquals( | 
|  | subBClassSubject.getDexProgramClass().getType().getPackageName(), | 
|  | bClassSubject.getDexProgramClass().getType().getPackageName()); | 
|  | } | 
|  |  | 
|  | // flattenpackagehierarchy '' | 
|  | private static void test044_rule003(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING044); | 
|  |  | 
|  | // All packages are moved to the top-level package, hence only one package separator. | 
|  | ClassSubject b = inspector.clazz("naming044.B"); | 
|  | ClassSubject bClassSubject = inspector.clazz("naming044.B"); | 
|  | assertEquals(1, countPackageDepth(bClassSubject)); | 
|  |  | 
|  | // Classes in a sub-package are moved to the top-level as well, but in a different one. | 
|  | ClassSubject sub = inspector.clazz("naming044.sub.SubB"); | 
|  | assertEquals(1, countPackageDepth(sub)); | 
|  | assertNotEquals( | 
|  | sub.getDexProgramClass().getType().getPackageName(), | 
|  | b.getDexProgramClass().getType().getPackageName()); | 
|  |  | 
|  | // method naming044.B.m would be renamed. | 
|  | MethodSubject mMethodSubject = b.uniqueMethodWithName("m"); | 
|  | assertNotEquals("m", mMethodSubject.getMethod().getName().toSourceString()); | 
|  | } | 
|  |  | 
|  | // flattenpackagehierarchy 'p44.y' | 
|  | private static void test044_rule004(CodeInspector inspector) { | 
|  | // All packages are moved to a single package. | 
|  | ClassSubject a = inspector.clazz("naming044.A"); | 
|  | assertTrue(a.getFinalName().startsWith("p44.y.")); | 
|  | // naming004.A -> Lp44/y/a/a; | 
|  | assertEquals(3, countPackageDepth(a)); | 
|  |  | 
|  | ClassSubject b = inspector.clazz("naming044.B"); | 
|  | assertTrue(b.getFinalName().startsWith("p44.y.")); | 
|  | // naming004.B -> Lp44/y/a/b; | 
|  | assertEquals(3, countPackageDepth(b)); | 
|  |  | 
|  | ClassSubject sub = inspector.clazz("naming044.sub.SubB"); | 
|  | assertTrue(sub.getFinalName().startsWith("p44.y.")); | 
|  | // naming004.sub.SubB -> Lp44/y/b/b; | 
|  | assertEquals(3, countPackageDepth(sub)); | 
|  |  | 
|  | // Classes in a sub-package should be in a different package. | 
|  | assertNotEquals( | 
|  | sub.getDexProgramClass().getType().getPackageName(), | 
|  | b.getDexProgramClass().getType().getPackageName()); | 
|  | } | 
|  |  | 
|  | private static void test044_rule005(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING044); | 
|  |  | 
|  | // All packages are renamed somehow. Need to check package hierarchy is consistent. | 
|  | ClassSubject a = inspector.clazz("naming044.A"); | 
|  | assertEquals(1, countPackageDepth(a)); | 
|  | ClassSubject b = inspector.clazz("naming044.B"); | 
|  | assertEquals(1, countPackageDepth(b)); | 
|  | assertEquals( | 
|  | a.getDexProgramClass().getType().getPackageName(), | 
|  | b.getDexProgramClass().getType().getPackageName()); | 
|  |  | 
|  | ClassSubject sub_a = inspector.clazz("naming044.sub.SubA"); | 
|  | assertEquals(1, countPackageDepth(sub_a)); | 
|  | ClassSubject sub_b = inspector.clazz("naming044.sub.SubB"); | 
|  | assertEquals(1, countPackageDepth(sub_b)); | 
|  | assertEquals( | 
|  | sub_a.getDexProgramClass().getType().getPackageName(), | 
|  | sub_b.getDexProgramClass().getType().getPackageName()); | 
|  |  | 
|  | // Lnaming044.B -> La/c --prefix--> La | 
|  | // Lnaming044.sub.SubB -> La/b/b --prefix--> La/b --prefix--> La | 
|  | assertEquals( | 
|  | getParentPackagePrefix(b.getFinalName()), | 
|  | getParentPackagePrefix(getParentPackagePrefix(sub_b.getFinalName()))); | 
|  | } | 
|  |  | 
|  | private static void test101_rule001(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING101); | 
|  |  | 
|  | // All classes are moved to the top-level package, hence no package separator. | 
|  | ClassSubject c = inspector.clazz("naming101.c"); | 
|  | assertFalse(c.getFinalName().contains(".")); | 
|  |  | 
|  | ClassSubject abc = inspector.clazz("naming101.a.b.c"); | 
|  | assertFalse(abc.getFinalName().contains(".")); | 
|  | assertNotEquals(abc.getFinalName(), c.getFinalName()); | 
|  | } | 
|  |  | 
|  | private static void test101_rule002(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING101); | 
|  |  | 
|  | // Check naming101.*.a is kept due to **.a | 
|  | ClassSubject aa = inspector.clazz("naming101.a.a"); | 
|  | assertEquals("naming101.a.a", aa.getFinalName()); | 
|  | ClassSubject ba = inspector.clazz("naming101.b.a"); | 
|  | assertEquals("naming101.b.a", ba.getFinalName()); | 
|  |  | 
|  | // 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 | 
|  | List<String> klasses = | 
|  | ImmutableList.of( | 
|  | "naming101.c", "naming101.d", "naming101.a.c", "naming101.a.b.c", "naming101.b.b"); | 
|  | for (String klass : klasses) { | 
|  | ClassSubject k = inspector.clazz(klass); | 
|  | assertEquals("naming101.a", k.getDexProgramClass().getType().getPackageName()); | 
|  | assertNotEquals("naming101.a.a", k.getFinalName()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static void test101_rule003(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING101); | 
|  |  | 
|  | // All packages are moved to the top-level package, hence only one package separator. | 
|  | ClassSubject aa = inspector.clazz("naming101.a.a"); | 
|  | assertEquals(1, countPackageDepth(aa)); | 
|  |  | 
|  | ClassSubject ba = inspector.clazz("naming101.b.a"); | 
|  | assertEquals(1, countPackageDepth(ba)); | 
|  |  | 
|  | assertNotEquals( | 
|  | aa.getDexProgramClass().getType().getPackageName(), | 
|  | ba.getDexProgramClass().getType().getPackageName()); | 
|  | } | 
|  |  | 
|  | private static void test101_rule004(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING101); | 
|  |  | 
|  | // Check naming101.*.a is kept due to **.a | 
|  | ClassSubject aa = inspector.clazz("naming101.a.a"); | 
|  | assertEquals("naming101.a.a", aa.getFinalName()); | 
|  | ClassSubject ba = inspector.clazz("naming101.b.a"); | 
|  | assertEquals("naming101.b.a", ba.getFinalName()); | 
|  |  | 
|  | // 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. | 
|  | List<String> klasses = | 
|  | ImmutableList.of( | 
|  | "naming101.c", "naming101.d", "naming101.a.b.c", "naming101.b.a", "naming101.b.b"); | 
|  | for (String klass : klasses) { | 
|  | ClassSubject k = inspector.clazz(klass); | 
|  | assertNotEquals("naming101.a", k.getDexProgramClass().getType().getPackageName()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static void test101_rule005(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING101); | 
|  |  | 
|  | // All packages are renamed somehow. Need to check package hierarchy is consistent. | 
|  | ClassSubject aa = inspector.clazz("naming101.a.a"); | 
|  | assertEquals(1, countPackageDepth(aa)); | 
|  | ClassSubject abc = inspector.clazz("naming101.a.b.c"); | 
|  | assertEquals(1, countPackageDepth(abc)); | 
|  |  | 
|  | // naming101.a/a; -> La/a/a; --prefix--> La/a | 
|  | // naming101.a/b/c; -> La/a/a/a; --prefix--> La/a/a --prefix--> La/a | 
|  | assertEquals( | 
|  | getParentPackagePrefix(aa.getFinalName()), | 
|  | getParentPackagePrefix(getParentPackagePrefix(abc.getFinalName()))); | 
|  | } | 
|  |  | 
|  | private static void test101_rule102(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING101); | 
|  |  | 
|  | // Check naming101.*.a is kept due to **.a | 
|  | ClassSubject aa = inspector.clazz("naming101.a.a"); | 
|  | assertEquals("naming101.a.a", aa.getFinalName()); | 
|  | ClassSubject ba = inspector.clazz("naming101.b.a"); | 
|  | assertEquals("naming101.b.a", ba.getFinalName()); | 
|  | // Due to package-private access, classes in the same package should remain in the same package. | 
|  | ClassSubject ac = inspector.clazz("naming101.a.c"); | 
|  | assertEquals( | 
|  | aa.getDexProgramClass().getType().getPackageName(), | 
|  | ac.getDexProgramClass().getType().getPackageName()); | 
|  | ClassSubject bb = inspector.clazz("naming101.b.b"); | 
|  | assertEquals( | 
|  | ba.getDexProgramClass().getType().getPackageName(), | 
|  | bb.getDexProgramClass().getType().getPackageName()); | 
|  |  | 
|  | // We cannot repackage c or d since these have package-private members and a | 
|  | // keep,allowobfuscation. For us to be able to repackage them, we have to use | 
|  | // -allowaccesmodification. | 
|  | List<String> klasses = ImmutableList.of("naming101.c", "naming101.d"); | 
|  | for (String klass : klasses) { | 
|  | ClassSubject k = inspector.clazz(klass); | 
|  | assertNotEquals("naming101.a", k.getDexProgramClass().getType().getPackageName()); | 
|  | } | 
|  |  | 
|  | // 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'. | 
|  | ClassSubject namingAbc = inspector.clazz("naming101.a.b.c"); | 
|  | assertEquals("naming101.a", namingAbc.getDexProgramClass().getType().getPackageName()); | 
|  | assertNotEquals("naming101.a.a", namingAbc.getFinalName()); | 
|  | } | 
|  |  | 
|  | private static void test101_rule104(CodeInspector inspector) { | 
|  | verifyUniqueNaming(inspector, CLASSES_IN_NAMING101); | 
|  |  | 
|  | // Check naming101.*.a is kept due to **.a | 
|  | ClassSubject aa = inspector.clazz("naming101.a.a"); | 
|  | assertEquals("naming101.a.a", aa.getFinalName()); | 
|  | ClassSubject ba = inspector.clazz("naming101.b.a"); | 
|  | assertEquals("naming101.b.a", ba.getFinalName()); | 
|  | // Due to package-private access, classes in the same package should remain in the same package. | 
|  | ClassSubject ac = inspector.clazz("naming101.a.c"); | 
|  | assertEquals( | 
|  | aa.getDexProgramClass().getType().getPackageName(), | 
|  | ac.getDexProgramClass().getType().getPackageName()); | 
|  | ClassSubject bb = inspector.clazz("naming101.b.b"); | 
|  | assertEquals( | 
|  | ba.getDexProgramClass().getType().getPackageName(), | 
|  | bb.getDexProgramClass().getType().getPackageName()); | 
|  |  | 
|  | // 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("naming101.c", "naming101.d", "naming101.a.b.c"); | 
|  | for (String klass : klasses) { | 
|  | ClassSubject k = inspector.clazz(klass); | 
|  | assertNotEquals("naming101.a", k.getDexProgramClass().getType().getPackageName()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static void test101_rule106(CodeInspector inspector) { | 
|  | // Classes that end with either c or d will be kept and renamed. | 
|  | List<String> klasses = | 
|  | CLASSES_IN_NAMING101.stream() | 
|  | .filter(className -> className.endsWith("c") || className.endsWith("d")) | 
|  | .collect(Collectors.toList()); | 
|  | verifyUniqueNaming(inspector, klasses); | 
|  |  | 
|  | // Check naming101.c is kept as-is. | 
|  | ClassSubject topC = inspector.clazz("naming101.c"); | 
|  | assertEquals("naming101.c", topC.getFinalName()); | 
|  |  | 
|  | // naming101.d accesses to a package-private, static field in naming101.c. | 
|  | // Therefore, it should remain in the same package. | 
|  | ClassSubject topD = inspector.clazz("naming101.d"); | 
|  | assertEquals("naming101", topD.getDexProgramClass().getType().getPackageName()); | 
|  |  | 
|  | // The remaining classes are in subpackages of naming101 and will therefore not have | 
|  | // package-private access to namin101.c | 
|  | ClassSubject subAC = inspector.clazz("naming101.a.c"); | 
|  | assertEquals(1, countPackageDepth(subAC)); | 
|  |  | 
|  | ClassSubject subABC = inspector.clazz("naming101.a.b.c"); | 
|  | assertEquals(1, countPackageDepth(subABC)); | 
|  | } | 
|  | } |