High-level structure for new repackaging support

Bug: 165783399
Change-Id: Ica95f07624b646fa1c84ae7245c9272661e1db11
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4d1a076..19b353c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -76,6 +76,7 @@
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.optimize.VisibilityBridgeRemover;
 import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.repackaging.Repackaging;
 import com.android.tools.r8.shaking.AbstractMethodRemover;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -796,6 +797,12 @@
         }
       }
 
+      // Perform repackaging.
+      // TODO(b/165783399): Consider making repacking available without minification.
+      if (appView.options().isMinifying()) {
+        new Repackaging(appView.withLiveness()).run(executorService, timing);
+      }
+
       // Perform minification.
       NamingLens namingLens;
       if (options.getProguardConfiguration().hasApplyMappingFile()) {
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index a0a1b99..8792d0c 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -128,6 +128,10 @@
     return !isPublic() && !isPrivate() && !isProtected();
   }
 
+  public boolean isPackagePrivateOrProtected() {
+    return !isPublic() && !isPrivate();
+  }
+
   public boolean isPublic() {
     return isSet(Constants.ACC_PUBLIC);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index ae39752..fe2eabd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -108,6 +108,11 @@
     }
   }
 
+  @Override
+  public ClassAccessFlags getAccessFlags() {
+    return accessFlags;
+  }
+
   public Iterable<DexEncodedField> fields() {
     return fields(Predicates.alwaysTrue());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index 09daf79..6168bd8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -25,6 +25,8 @@
     return annotations;
   }
 
+  public abstract AccessFlags<?> getAccessFlags();
+
   public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
     return annotations.keepIf(
         annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index e7d5f02..638a9be 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -14,8 +14,6 @@
     return toReference().holder;
   }
 
-  public abstract AccessFlags<?> getAccessFlags();
-
   @Override
   public abstract R toReference();
 
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
new file mode 100644
index 0000000..28fc4e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
@@ -0,0 +1,42 @@
+// 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.graph;
+
+import com.google.common.collect.Sets;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class ProgramPackage implements Iterable<DexProgramClass> {
+
+  private final String packageDescriptor;
+  private final Set<DexProgramClass> classes = Sets.newIdentityHashSet();
+
+  public ProgramPackage(String packageDescriptor) {
+    this.packageDescriptor = packageDescriptor;
+  }
+
+  public void add(DexProgramClass clazz) {
+    assert clazz.getType().getPackageDescriptor().equals(packageDescriptor);
+    classes.add(clazz);
+  }
+
+  public void forEachClass(Consumer<DexProgramClass> consumer) {
+    forEach(consumer);
+  }
+
+  public void forEachField(Consumer<ProgramField> consumer) {
+    forEach(clazz -> clazz.forEachProgramField(consumer));
+  }
+
+  public void forEachMethod(Consumer<ProgramMethod> consumer) {
+    forEach(clazz -> clazz.forEachProgramMethod(consumer));
+  }
+
+  @Override
+  public Iterator<DexProgramClass> iterator() {
+    return classes.iterator();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
new file mode 100644
index 0000000..57fc75a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -0,0 +1,34 @@
+// 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.graph;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class ProgramPackageCollection implements Iterable<ProgramPackage> {
+
+  private final Map<String, ProgramPackage> packages;
+
+  private ProgramPackageCollection(Map<String, ProgramPackage> packages) {
+    this.packages = packages;
+  }
+
+  public static ProgramPackageCollection create(AppView<?> appView) {
+    Map<String, ProgramPackage> packages = new HashMap<>();
+    assert !appView.appInfo().getSyntheticItems().hasPendingSyntheticClasses();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      packages
+          .computeIfAbsent(clazz.getType().getPackageDescriptor(), ProgramPackage::new)
+          .add(clazz);
+    }
+    return new ProgramPackageCollection(packages);
+  }
+
+  @Override
+  public Iterator<ProgramPackage> iterator() {
+    return packages.values().iterator();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/SyntheticItems.java b/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
index 28bb51a..20c073a 100644
--- a/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
@@ -40,6 +40,10 @@
     return new SyntheticItems(ImmutableSet.of());
   }
 
+  public boolean hasPendingSyntheticClasses() {
+    return !pendingClasses.isEmpty();
+  }
+
   public Collection<DexProgramClass> getPendingSyntheticClasses() {
     return Collections.unmodifiableCollection(pendingClasses.values());
   }
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
new file mode 100644
index 0000000..8523937
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -0,0 +1,89 @@
+// 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.repackaging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramPackage;
+import com.android.tools.r8.graph.ProgramPackageCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.utils.Timing;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Entry-point for supporting the -repackageclasses and -flattenpackagehierarchy directives.
+ *
+ * <p>This pass moves all classes in the program into a user-specified package. Some classes may not
+ * be allowed to be renamed, and thus must remain in the original package.
+ *
+ * <p>A complication is that there can be (i) references to package-private or protected items that
+ * must remain in the package, and (ii) references from methods that must remain in the package to
+ * package-private or protected items. To ensure that such references remain valid after
+ * repackaging, an analysis is run that finds the minimal set of classes that must remain in the
+ * original package due to accessibility constraints.
+ */
+public class Repackaging {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final ProguardConfiguration proguardConfiguration;
+
+  public Repackaging(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    this.proguardConfiguration = appView.options().getProguardConfiguration();
+  }
+
+  public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+    timing.begin("Repackage classes");
+    run(executorService);
+    timing.end();
+  }
+
+  private void run(ExecutorService executorService) throws ExecutionException {
+    if (proguardConfiguration.getPackageObfuscationMode().isNone()) {
+      return;
+    }
+
+    // For each package, find the set of classes that can be repackaged, and move them to the
+    // desired namespace.
+    ProgramPackageCollection packages = ProgramPackageCollection.create(appView);
+    for (ProgramPackage pkg : packages) {
+      Iterable<DexProgramClass> classesToRepackage =
+          computeClassesToRepackage(pkg, executorService);
+      // TODO(b/165783399): Move each class in `classesToRepackage`.
+      // TODO(b/165783399): Investigate if repackaging can lead to different dynamic dispatch. See,
+      //  for example, CrossPackageInvokeSuperToPackagePrivateMethodTest.
+    }
+  }
+
+  private Iterable<DexProgramClass> computeClassesToRepackage(
+      ProgramPackage pkg, ExecutorService executorService) throws ExecutionException {
+    // If all classes can be minified, then we are free to move all the classes to a new package.
+    if (isMinificationAllowedForAllClasses(pkg)) {
+      return pkg;
+    }
+
+    // Otherwise, only a subset of classes in the package can be repackaged, and we need to ensure
+    // that package-private accesses continue to work after the split.
+    RepackagingConstraintGraph constraintGraph = new RepackagingConstraintGraph(appView, pkg);
+    boolean hasPackagePrivateOrProtectedItem = constraintGraph.initializeGraph();
+    if (!hasPackagePrivateOrProtectedItem) {
+      return pkg;
+    }
+    constraintGraph.populateConstraints(executorService);
+    return constraintGraph.computeClassesToRepackage();
+  }
+
+  private boolean isMinificationAllowedForAllClasses(ProgramPackage pkg) {
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    for (DexProgramClass clazz : pkg) {
+      if (!appInfo.isMinificationAllowed(clazz.getType())) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
new file mode 100644
index 0000000..52a9b86
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -0,0 +1,111 @@
+// 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.repackaging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ProgramPackage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * An undirected graph that contains a node for each class, field, and method in a given package.
+ *
+ * <p>An edge X <-> Y is added if X contains a reference to Y and Y is only accessible to X if X and
+ * Y are in the same package.
+ *
+ * <p>Once the graph is populated, we compute the set of reachable nodes from the set of root nodes
+ * that cannot be repackaged due to a -keep rule. The remaining nodes in the graph can all be
+ * repackaged.
+ */
+public class RepackagingConstraintGraph {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final ProgramPackage pkg;
+  private final Map<DexDefinition, Node> nodes = new IdentityHashMap<>();
+
+  public RepackagingConstraintGraph(AppView<AppInfoWithLiveness> appView, ProgramPackage pkg) {
+    this.appView = appView;
+    this.pkg = pkg;
+  }
+
+  public boolean initializeGraph() {
+    // Add all the items in the package into the graph. This way we know which items belong to the
+    // package without having to extract package descriptor strings and comparing them with the
+    // package descriptor.
+    boolean hasPackagePrivateOrProtectedItem = false;
+    for (DexProgramClass clazz : pkg) {
+      nodes.put(clazz, new Node(clazz));
+      hasPackagePrivateOrProtectedItem |= clazz.getAccessFlags().isPackagePrivateOrProtected();
+      for (DexEncodedMember<?, ?> member : clazz.members()) {
+        nodes.put(member, new Node(member));
+        hasPackagePrivateOrProtectedItem |= member.getAccessFlags().isPackagePrivateOrProtected();
+      }
+    }
+    return hasPackagePrivateOrProtectedItem;
+  }
+
+  public void populateConstraints(ExecutorService executorService) throws ExecutionException {
+    // Concurrently add references from methods to the graph.
+    ThreadUtils.processItems(
+        pkg::forEachMethod, this::registerReferencesFromMethod, executorService);
+
+    // TODO(b/165783399): Evaluate if it is worth to parallelize this. The work per field and class
+    //  should be little, so it may not be.
+    pkg.forEachClass(this::registerReferencesFromClass);
+    pkg.forEachField(this::registerReferencesFromField);
+  }
+
+  private void registerReferencesFromClass(DexProgramClass clazz) {
+    // TODO(b/165783399): Trace the references to the immediate super types.
+    // TODO(b/165783399): Maybe trace the references in the nest host and/or members.
+    // TODO(b/165783399): Maybe trace the references to the inner classes.
+    // TODO(b/165783399): Maybe trace the references in @kotlin.Metadata.
+  }
+
+  private void registerReferencesFromField(ProgramField field) {
+    // TODO(b/165783399): Trace the type of the field.
+    // TODO(b/165783399): Trace the references in the field annotations.
+  }
+
+  private void registerReferencesFromMethod(ProgramMethod method) {
+    // TODO(b/165783399): Trace the type references in the method signature.
+    // TODO(b/165783399): Trace the references in the method and method parameter annotations.
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.hasCode()) {
+      RepackagingUseRegistry registry = new RepackagingUseRegistry(appView, this, method);
+      definition.getCode().registerCodeReferences(method, registry);
+    }
+  }
+
+  public Iterable<DexProgramClass> computeClassesToRepackage() {
+    // TODO(b/165783399): From each node in the graph that cannot be moved elsewhere due to a -keep
+    //  rule, mark all neighbors as pinned, and repeat.
+    return Collections.emptyList();
+  }
+
+  private static class Node {
+
+    private final DexDefinition definition;
+
+    private final Set<Node> neighbors = Sets.newIdentityHashSet();
+
+    private Node(DexDefinition definition) {
+      this.definition = definition;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
new file mode 100644
index 0000000..3ffd5c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -0,0 +1,108 @@
+// 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.repackaging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class RepackagingUseRegistry extends UseRegistry {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final RepackagingConstraintGraph constraintGraph;
+  private final ProgramMethod method;
+
+  public RepackagingUseRegistry(
+      AppView<AppInfoWithLiveness> appView,
+      RepackagingConstraintGraph constraintGraph,
+      ProgramMethod method) {
+    super(appView.dexItemFactory());
+    this.appView = appView;
+    this.constraintGraph = constraintGraph;
+    this.method = method;
+  }
+
+  @Override
+  public boolean registerInitClass(DexType type) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInvokeVirtual(DexMethod method) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInvokeDirect(DexMethod method) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInvokeStatic(DexMethod method) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInvokeInterface(DexMethod method) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInvokeSuper(DexMethod method) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInstanceFieldRead(DexField field) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInstanceFieldWrite(DexField field) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerNewInstance(DexType type) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerStaticFieldRead(DexField field) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerStaticFieldWrite(DexField field) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerTypeReference(DexType type) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+
+  @Override
+  public boolean registerInstanceOf(DexType type) {
+    // TODO(b/165783399): Add reference-edges to the graph.
+    return false;
+  }
+}
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 54c1055..ffeddac 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1068,7 +1068,19 @@
     // Repackaging all classes into the single user-given (or top-level) package.
     REPACKAGE,
     // Repackaging all packages into the single user-given (or top-level) package.
-    FLATTEN
+    FLATTEN;
+
+    public boolean isNone() {
+      return this == NONE;
+    }
+
+    public boolean isFlattenPackageHierarchy() {
+      return this == FLATTEN;
+    }
+
+    public boolean isRepackageClasses() {
+      return this == REPACKAGE;
+    }
   }
 
   public static class OutlineOptions {