Extend repackaging to allow alpha renaming of classes for testing
Bug: 169036452
Change-Id: I09e162f15c4f6f92b5e7bee0c5bcae35b1160823
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 5868aa8..95ee828 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.repackaging;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
import static com.android.tools.r8.utils.DescriptorUtils.INNER_CLASS_SEPARATOR;
import com.android.tools.r8.graph.AppView;
@@ -18,7 +19,7 @@
import com.android.tools.r8.graph.SortedProgramPackageCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -43,13 +44,14 @@
public class Repackaging {
private final AppView<AppInfoWithLiveness> appView;
- private final DexItemFactory dexItemFactory;
private final ProguardConfiguration proguardConfiguration;
+ private final RepackagingConfiguration repackagingConfiguration;
public Repackaging(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
- this.dexItemFactory = appView.dexItemFactory();
this.proguardConfiguration = appView.options().getProguardConfiguration();
+ this.repackagingConfiguration =
+ appView.options().testing.repackagingConfigurationFactory.apply(appView);
}
public RepackagingLens run(
@@ -90,10 +92,16 @@
Iterator<ProgramPackage> iterator = packages.iterator();
while (iterator.hasNext()) {
ProgramPackage pkg = iterator.next();
- String newPackageDescriptor = getNewPackageDescriptor(pkg, seenPackageDescriptors);
+ String newPackageDescriptor =
+ repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors);
if (pkg.getPackageDescriptor().equals(newPackageDescriptor)) {
for (DexProgramClass alreadyRepackagedClass : pkg) {
- mappings.put(alreadyRepackagedClass.getType(), alreadyRepackagedClass.getType());
+ if (!appView.appInfo().isRepackagingAllowed(alreadyRepackagedClass.getType())) {
+ mappings.put(alreadyRepackagedClass.getType(), alreadyRepackagedClass.getType());
+ }
+ }
+ for (DexProgramClass alreadyRepackagedClass : pkg) {
+ processClass(alreadyRepackagedClass, pkg, newPackageDescriptor, mappings);
}
seenPackageDescriptors.add(newPackageDescriptor);
iterator.remove();
@@ -111,7 +119,8 @@
// desired package.
for (ProgramPackage pkg : packages) {
// Already processed packages should have been removed.
- String newPackageDescriptor = getNewPackageDescriptor(pkg, seenPackageDescriptors);
+ String newPackageDescriptor =
+ repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors);
assert !pkg.getPackageDescriptor().equals(newPackageDescriptor);
Iterable<DexProgramClass> classesToRepackage =
@@ -151,7 +160,8 @@
}
mappings.put(
classToRepackage.getType(),
- getRepackagedType(classToRepackage, outerClass, newPackageDescriptor, mappings));
+ repackagingConfiguration.getRepackagedType(
+ classToRepackage, outerClass, newPackageDescriptor, mappings));
}
private Iterable<DexProgramClass> computeClassesToRepackage(
@@ -165,46 +175,129 @@
return constraintGraph.computeClassesToRepackage();
}
- private String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) {
- String newPackageDescriptor =
- StringUtils.replaceAll(proguardConfiguration.getPackagePrefix(), ".", "/");
- if (proguardConfiguration.getPackageObfuscationMode().isRepackageClasses()) {
- return newPackageDescriptor;
- }
- if (!newPackageDescriptor.isEmpty()) {
- newPackageDescriptor += "/";
- }
- newPackageDescriptor += pkg.getLastPackageName();
- String finalPackageDescriptor = newPackageDescriptor;
- for (int i = 1; seenPackageDescriptors.contains(finalPackageDescriptor); i++) {
- finalPackageDescriptor = newPackageDescriptor + INNER_CLASS_SEPARATOR + i;
- }
- return finalPackageDescriptor;
+ public interface RepackagingConfiguration {
+
+ String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors);
+
+ DexType getRepackagedType(
+ DexProgramClass clazz,
+ DexProgramClass outerClass,
+ String newPackageDescriptor,
+ BiMap<DexType, DexType> mappings);
}
- private DexType getRepackagedType(
- DexProgramClass clazz,
- DexProgramClass outerClass,
- String newPackageDescriptor,
- BiMap<DexType, DexType> mappings) {
- DexType repackagedDexType =
- clazz.getType().replacePackage(newPackageDescriptor, dexItemFactory);
- if (outerClass != null) {
- String simpleName = clazz.getType().getSimpleName();
- String outerClassSimpleName = outerClass.getType().getSimpleName();
- if (simpleName.startsWith(outerClassSimpleName + INNER_CLASS_SEPARATOR)) {
- String newSimpleName =
- mappings.get(outerClass.getType()).getSimpleName()
- + simpleName.substring(outerClassSimpleName.length());
- repackagedDexType = repackagedDexType.withSimpleName(newSimpleName, dexItemFactory);
+ public static class DefaultRepackagingConfiguration implements RepackagingConfiguration {
+
+ private final DexItemFactory dexItemFactory;
+ private final ProguardConfiguration proguardConfiguration;
+
+ public DefaultRepackagingConfiguration(
+ DexItemFactory dexItemFactory, ProguardConfiguration proguardConfiguration) {
+ this.dexItemFactory = dexItemFactory;
+ this.proguardConfiguration = proguardConfiguration;
+ }
+
+ @Override
+ public String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) {
+ String newPackageDescriptor =
+ DescriptorUtils.getBinaryNameFromJavaType(proguardConfiguration.getPackagePrefix());
+ if (proguardConfiguration.getPackageObfuscationMode().isRepackageClasses()) {
+ return newPackageDescriptor;
}
+ if (!newPackageDescriptor.isEmpty()) {
+ newPackageDescriptor += DESCRIPTOR_PACKAGE_SEPARATOR;
+ }
+ newPackageDescriptor += pkg.getLastPackageName();
+ String finalPackageDescriptor = newPackageDescriptor;
+ for (int i = 1; seenPackageDescriptors.contains(finalPackageDescriptor); i++) {
+ finalPackageDescriptor = newPackageDescriptor + INNER_CLASS_SEPARATOR + i;
+ }
+ return finalPackageDescriptor;
}
- DexType finalRepackagedDexType = repackagedDexType;
- for (int i = 1; mappings.inverse().containsKey(finalRepackagedDexType); i++) {
- finalRepackagedDexType =
- repackagedDexType.addSuffix(
- Character.toString(INNER_CLASS_SEPARATOR) + i, dexItemFactory);
+
+ @Override
+ public DexType getRepackagedType(
+ DexProgramClass clazz,
+ DexProgramClass outerClass,
+ String newPackageDescriptor,
+ BiMap<DexType, DexType> mappings) {
+ DexType repackagedDexType =
+ clazz.getType().replacePackage(newPackageDescriptor, dexItemFactory);
+ // Rename the class consistently with its outer class.
+ if (outerClass != null) {
+ String simpleName = clazz.getType().getSimpleName();
+ String outerClassSimpleName = outerClass.getType().getSimpleName();
+ if (simpleName.startsWith(outerClassSimpleName + INNER_CLASS_SEPARATOR)) {
+ String newSimpleName =
+ mappings.get(outerClass.getType()).getSimpleName()
+ + simpleName.substring(outerClassSimpleName.length());
+ repackagedDexType = repackagedDexType.withSimpleName(newSimpleName, dexItemFactory);
+ } else {
+ assert false
+ : "Unexpected name for inner class: "
+ + clazz.getType().toSourceString()
+ + " (outer class: "
+ + outerClass.getType().toSourceString()
+ + ")";
+ }
+ }
+ // Ensure that the generated name is unique.
+ DexType finalRepackagedDexType = repackagedDexType;
+ for (int i = 1; mappings.inverse().containsKey(finalRepackagedDexType); i++) {
+ finalRepackagedDexType =
+ repackagedDexType.addSuffix(
+ Character.toString(INNER_CLASS_SEPARATOR) + i, dexItemFactory);
+ }
+ return finalRepackagedDexType;
}
- return finalRepackagedDexType;
+ }
+
+ /** Testing only. */
+ public static class SuffixRenamingRepackagingConfiguration implements RepackagingConfiguration {
+
+ private final String classNameSuffix;
+ private final DexItemFactory dexItemFactory;
+
+ public SuffixRenamingRepackagingConfiguration(
+ String classNameSuffix, DexItemFactory dexItemFactory) {
+ this.classNameSuffix = classNameSuffix;
+ this.dexItemFactory = dexItemFactory;
+ }
+
+ @Override
+ public String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) {
+ // Don't change the package of classes.
+ return pkg.getPackageDescriptor();
+ }
+
+ @Override
+ public DexType getRepackagedType(
+ DexProgramClass clazz,
+ DexProgramClass outerClass,
+ String newPackageDescriptor,
+ BiMap<DexType, DexType> mappings) {
+ DexType repackagedDexType = clazz.getType();
+ // Rename the class consistently with its outer class.
+ if (outerClass != null) {
+ String simpleName = clazz.getType().getSimpleName();
+ String outerClassSimpleName = outerClass.getType().getSimpleName();
+ if (simpleName.startsWith(outerClassSimpleName + INNER_CLASS_SEPARATOR)) {
+ String newSimpleName =
+ mappings.get(outerClass.getType()).getSimpleName()
+ + simpleName.substring(outerClassSimpleName.length());
+ repackagedDexType = repackagedDexType.withSimpleName(newSimpleName, dexItemFactory);
+ }
+ }
+ // Append the class name suffix to all classes.
+ repackagedDexType = repackagedDexType.addSuffix(classNameSuffix, dexItemFactory);
+ // Ensure that the generated name is unique.
+ DexType finalRepackagedDexType = repackagedDexType;
+ for (int i = 1; mappings.inverse().containsKey(finalRepackagedDexType); i++) {
+ finalRepackagedDexType =
+ repackagedDexType.addSuffix(
+ Character.toString(INNER_CLASS_SEPARATOR) + i, dexItemFactory);
+ }
+ return finalRepackagedDexType;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 15183a1..af11559 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -43,6 +43,8 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration;
+import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.GlobalKeepInfoConfiguration;
@@ -75,6 +77,7 @@
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
import org.objectweb.asm.Opcodes;
@@ -1213,6 +1216,12 @@
public BiConsumer<ProgramMethod, MethodProcessingId> methodProcessingIdConsumer = null;
+ public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration>
+ repackagingConfigurationFactory =
+ appView ->
+ new DefaultRepackagingConfiguration(
+ appView.dexItemFactory(), appView.options().getProguardConfiguration());
+
public Consumer<Deque<SortedProgramMethodSet>> waveModifier = waves -> {};
/**
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithSuffixRenamingConfigurationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithSuffixRenamingConfigurationTest.java
new file mode 100644
index 0000000..b22042f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithSuffixRenamingConfigurationTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, 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.repackage;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.repackaging.Repackaging.SuffixRenamingRepackagingConfiguration;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithSuffixRenamingConfigurationTest extends RepackageTestBase {
+
+ public RepackageWithSuffixRenamingConfigurationTest(
+ String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+ super(flattenPackageHierarchyOrRepackageClasses, parameters);
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassRules(GreeterFoo.class)
+ .addOptionsModification(
+ options ->
+ options.testing.repackagingConfigurationFactory =
+ appView ->
+ new SuffixRenamingRepackagingConfiguration("Foo", appView.dexItemFactory()))
+ .apply(this::configureRepackaging)
+ .enableInliningAnnotations()
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject greeterSubject = inspector.clazz(Greeter.class);
+ assertEquals(GreeterFoo.class.getTypeName() + "$1", greeterSubject.getFinalName());
+
+ ClassSubject greeterFooSubject = inspector.clazz(GreeterFoo.class);
+ assertEquals(GreeterFoo.class.getTypeName(), greeterFooSubject.getFinalName());
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ Greeter.greet();
+ GreeterFoo.greet();
+ }
+ }
+
+ public static class Greeter extends Exception {
+
+ @NeverInline
+ public static void greet() {
+ System.out.print("Hello");
+ }
+ }
+
+ public static class GreeterFoo extends Exception {
+
+ @NeverInline
+ public static void greet() {
+ System.out.println(" world!");
+ }
+ }
+}