Use a frontier approach to storing reserved names

Bug: 213415674
Bug: 213041051
Bug: 211822928
Change-Id: I4e268ccdb5cd6a0075f75308ee2e0fb1f782147e
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index f948457..70e6d04 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -19,12 +18,15 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -37,8 +39,12 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final SubtypingInfo subtypingInfo;
   private final Map<DexField, DexString> renaming = new IdentityHashMap<>();
-  private Map<DexType, ReservedFieldNamingState> reservedNamingStates = new IdentityHashMap<>();
+  private final Map<DexType, ReservedFieldNamingState> reservedNamingStates =
+      new IdentityHashMap<>();
   private final MemberNamingStrategy strategy;
+  private final Map<DexType, DexType> frontiers = new IdentityHashMap<>();
+  private final Map<DexType, Set<ReservedFieldNamingState>> frontierStatesForInterfaces =
+      new IdentityHashMap<>();
 
   FieldNameMinifier(
       AppView<AppInfoWithLiveness> appView,
@@ -60,8 +66,8 @@
     // Rename the definitions.
     timing.begin("rename-definitions");
     renameFieldsInInterfaces(interfaces);
-    propagateReservedFieldNamesUpwards();
     renameFieldsInClasses();
+    renameFieldsInUnrelatedClasspathClasses();
     timing.end();
     // Rename the references that are not rebound to definitions for some reasons.
     timing.begin("rename-references");
@@ -93,19 +99,36 @@
   }
 
   private void reserveFieldNames() {
-    // Reserve all field names that need to be reserved.
-    appView
-        .appInfo()
-        .forEachTypeInHierarchyOfLiveProgramClasses(
+    // Build up all reservations in the class hierarchy such that all reserved names are placed
+    // at the boundary between a library class and a program class - referred to as the frontier.
+    // Special handling is done for interfaces by always considering them to be roots. When
+    // traversing down the hierarchy we built up a map from interface to reservation states:
+    // - when we reach a frontier find all directly and indirectly implemented interfaces and
+    //   add the current reservation state
+    // - when we see a program class that implements a direct super type that is an interface also
+    //   add the current reservation state. Note that even though we do not visit super interfaces
+    //   here, this still works because a super interface will be in the same partition.
+    // For an in depth description see MethodNameMinifier.
+    TopDownClassHierarchyTraversal.forAllClasses(appView)
+        .visit(
+            appView.appInfo().classes(),
             clazz -> {
-              ReservedFieldNamingState reservedNames = null;
+              DexType frontier =
+                  clazz.superType == null
+                      ? appView.dexItemFactory().objectType
+                      : frontiers.getOrDefault(clazz.superType, clazz.type);
+              // If frontier != clazz.type we have seen a program class that is on the boundary.
+              // Otherwise, if we are visiting a program class then that is the frontier.
+              if (frontier != clazz.type || clazz.isProgramClass()) {
+                DexType existingValue = frontiers.put(clazz.type, frontier);
+                assert existingValue == null;
+              }
+              ReservedFieldNamingState reservationState =
+                  getOrCreateReservedFieldNamingState(frontier);
               for (DexEncodedField field : clazz.fields()) {
                 DexString reservedName = strategy.getReservedName(field, clazz);
                 if (reservedName != null) {
-                  if (reservedNames == null) {
-                    reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
-                  }
-                  reservedNames.markReservedDirectly(
+                  reservationState.markReserved(
                       reservedName, field.getReference().name, field.getReference().type);
                   // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
                   if (reservedName != field.getReference().name) {
@@ -113,51 +136,47 @@
                   }
                 }
               }
-
-              // For interfaces, propagate reserved names to all implementing classes.
-              if (clazz.isInterface() && reservedNames != null) {
-                for (DexType implementationType :
-                    subtypingInfo.allImmediateImplementsSubtypes(clazz.type)) {
-                  DexClass implementation = appView.definitionFor(implementationType);
-                  if (implementation != null) {
-                    assert !implementation.isInterface();
-                    getOrCreateReservedFieldNamingState(implementationType)
-                        .includeReservations(reservedNames);
+              if (clazz.isInterface()) {
+                frontierStatesForInterfaces.put(
+                    clazz.type, SetUtils.newIdentityHashSet(reservationState));
+              }
+              // Include all reservations from super frontier states. This will propagate reserved
+              // names for interfaces down to implementing subtypes.
+              for (DexType superType : clazz.allImmediateSupertypes()) {
+                // No need to visit object since there are no fields there.
+                if (superType != appView.dexItemFactory().objectType) {
+                  ReservedFieldNamingState superReservationState =
+                      getOrCreateReservedFieldNamingState(
+                          frontiers.getOrDefault(superType, superType));
+                  if (superReservationState != reservationState) {
+                    reservationState.includeReservations(superReservationState);
+                  }
+                  if (clazz.isProgramClass()) {
+                    DexClass superClass = appView.definitionFor(superType, clazz.asProgramClass());
+                    if (superClass != null && superClass.isInterface()) {
+                      frontierStatesForInterfaces.get(superType).add(reservationState);
+                    }
                   }
                 }
               }
-            });
-
-    // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
-    appView
-        .appInfo()
-        .forEachReferencedClasspathClass(
-            clazz -> {
-              for (DexEncodedField field : clazz.fields()) {
-                DexString reservedName = strategy.getReservedName(field, clazz);
-                if (reservedName != null && reservedName != field.getReference().name) {
-                  renaming.put(field.getReference(), reservedName);
-                }
+              if (frontier == clazz.type && clazz.isProgramClass()) {
+                patchUpAllIndirectlyImplementingInterfacesFromLibraryAndClassPath(
+                    clazz.asProgramClass(), reservationState);
               }
             });
-
-    propagateReservedFieldNamesUpwards();
   }
 
-  private void propagateReservedFieldNamesUpwards() {
-    BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo)
-        .visit(
-            appView.appInfo().classes(),
-            clazz -> {
-              ReservedFieldNamingState reservedNames = getReservedFieldNamingState(clazz.type);
-              if (reservedNames != null) {
-                for (DexType supertype : clazz.allImmediateSupertypes()) {
-                  if (supertype.isProgramType(appView)) {
-                    getOrCreateReservedFieldNamingState(supertype)
-                        .includeReservationsFromBelow(reservedNames);
-                  }
-                }
+  private void patchUpAllIndirectlyImplementingInterfacesFromLibraryAndClassPath(
+      DexProgramClass clazz, ReservedFieldNamingState reservationState) {
+    appView
+        .appInfo()
+        .traverseSuperTypes(
+            clazz,
+            (superType, superClass, isInterface) -> {
+              if (isInterface && superClass.isNotProgramClass()) {
+                frontierStatesForInterfaces.get(superType).add(reservationState);
               }
+              return TraversalContinuation.CONTINUE;
             });
   }
 
@@ -179,8 +198,7 @@
                           .clone();
 
               ReservedFieldNamingState reservedNames =
-                  getOrCreateReservedFieldNamingState(clazz.type);
-              // TODO(b/213041051): This could avoid duplication of strings downwards.
+                  getReservedFieldNamingState(frontiers.getOrDefault(clazz.type, clazz.type));
               FieldNamingState state = parentState.createChildState(reservedNames);
               if (clazz.isProgramClass()) {
                 clazz.asProgramClass().forEachProgramField(field -> renameField(field, state));
@@ -191,7 +209,24 @@
             });
   }
 
+  private void renameFieldsInUnrelatedClasspathClasses() {
+    if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
+      appView
+          .appInfo()
+          .forEachReferencedClasspathClass(
+              clazz -> {
+                for (DexEncodedField field : clazz.fields()) {
+                  DexString reservedName = strategy.getReservedName(field, clazz);
+                  if (reservedName != null && reservedName != field.getReference().name) {
+                    renaming.put(field.getReference(), reservedName);
+                  }
+                }
+              });
+    }
+  }
+
   private void renameFieldsInInterfaces(Collection<DexClass> interfaces) {
+    // TODO(b/213415674): Only consider interfaces in the hierarchy of classes.
     InterfacePartitioning partitioning = new InterfacePartitioning(this);
     for (Set<DexClass> partition : partitioning.sortedPartitions(interfaces)) {
       renameFieldsInInterfacePartition(partition);
@@ -199,18 +234,24 @@
   }
 
   private void renameFieldsInInterfacePartition(Set<DexClass> partition) {
-    ReservedFieldNamingState reservedNamesInPartition = new ReservedFieldNamingState(appView);
-    for (DexClass clazz : partition) {
-      ReservedFieldNamingState reservedNamesInInterface = getReservedFieldNamingState(clazz.type);
-      if (reservedNamesInInterface != null) {
-        reservedNamesInPartition.includeReservations(reservedNamesInInterface);
-        reservedNamesInPartition.includeReservationsFromBelow(reservedNamesInInterface);
-      }
-    }
-
     ReservedFieldNamingState namesToBeReservedInImplementsSubclasses =
         new ReservedFieldNamingState(appView);
-
+    ReservedFieldNamingState reservedNamesInPartition = new ReservedFieldNamingState(appView);
+    for (DexClass clazz : partition) {
+      ReservedFieldNamingState reservedNamesInInterface =
+          getReservedFieldNamingState(frontiers.getOrDefault(clazz.type, clazz.type));
+      if (reservedNamesInInterface != null) {
+        reservedNamesInPartition.includeReservations(reservedNamesInInterface);
+        Set<ReservedFieldNamingState> reservedFieldNamingStates =
+            frontierStatesForInterfaces.get(clazz.type);
+        assert reservedFieldNamingStates != null;
+        reservedFieldNamingStates.forEach(
+            reservedStates -> {
+              reservedNamesInPartition.includeReservations(reservedStates);
+              reservedStates.setInterfaceMinificationState(namesToBeReservedInImplementsSubclasses);
+            });
+      }
+    }
     FieldNamingState state = new FieldNamingState(appView, strategy, reservedNamesInPartition);
     for (DexClass clazz : partition) {
       if (clazz.isProgramClass()) {
@@ -220,25 +261,11 @@
             .forEachProgramField(
                 field -> {
                   DexString newName = renameField(field, state);
-                  namesToBeReservedInImplementsSubclasses.markReservedDirectly(
+                  namesToBeReservedInImplementsSubclasses.markReserved(
                       newName, field.getReference().name, field.getReference().type);
                 });
       }
     }
-
-    Set<DexType> visited = Sets.newIdentityHashSet();
-    for (DexClass clazz : partition) {
-      for (DexType implementationType : subtypingInfo.allImmediateImplementsSubtypes(clazz.type)) {
-        if (!visited.add(implementationType)) {
-          continue;
-        }
-        DexClass implementation = appView.definitionFor(implementationType);
-        if (implementation != null) {
-          getOrCreateReservedFieldNamingState(implementationType)
-              .setInterfaceMinificationState(namesToBeReservedInImplementsSubclasses);
-        }
-      }
-    }
   }
 
   private DexString renameField(ProgramField field, FieldNamingState state) {
@@ -279,12 +306,12 @@
 
   static class InterfacePartitioning {
 
-    private final FieldNameMinifier minfier;
+    private final FieldNameMinifier minifier;
     private final AppView<AppInfoWithLiveness> appView;
     private final Set<DexType> visited = Sets.newIdentityHashSet();
 
     InterfacePartitioning(FieldNameMinifier minifier) {
-      this.minfier = minifier;
+      this.minifier = minifier;
       appView = minifier.appView;
     }
 
@@ -303,7 +330,7 @@
     }
 
     private Set<DexClass> buildSortedPartition(DexClass src) {
-      Set<DexClass> partition = new TreeSet<>((x, y) -> x.type.compareTo(y.type));
+      Set<DexClass> partition = new TreeSet<>(Comparator.comparing(DexClass::getType));
 
       Deque<DexType> worklist = new ArrayDeque<>();
       worklist.add(src.type);
@@ -325,7 +352,7 @@
         if (clazz.isInterface()) {
           partition.add(clazz);
 
-          for (DexType subtype : minfier.subtypingInfo.allImmediateSubtypes(type)) {
+          for (DexType subtype : minifier.subtypingInfo.allImmediateSubtypes(type)) {
             if (visited.add(subtype)) {
               worklist.add(subtype);
             }
@@ -334,7 +361,7 @@
           if (visited.add(clazz.superType)) {
             worklist.add(clazz.superType);
           }
-          for (DexType subclass : minfier.subtypingInfo.allImmediateExtendsSubtypes(type)) {
+          for (DexType subclass : minifier.subtypingInfo.allImmediateExtendsSubtypes(type)) {
             if (visited.add(subclass)) {
               worklist.add(subclass);
             }
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 9e99065..e507112 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -45,10 +45,7 @@
   }
 
   public FieldNamingState createChildState(ReservedFieldNamingState reservedNames) {
-    FieldNamingState childState =
-        new FieldNamingState(appView, strategy, reservedNames, internalStates);
-    childState.includeReservations(this.reservedNames);
-    return childState;
+    return new FieldNamingState(appView, strategy, reservedNames, internalStates);
   }
 
   public DexString getOrCreateNameFor(ProgramField field) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 7e1c7df..cd6c271 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -134,7 +133,7 @@
   // The use of a bidirectional map allows us to map a naming state to the type it represents,
   // which is useful for debugging.
   private final BiMap<DexType, MethodReservationState<?>> reservationStates = HashBiMap.create();
-  private final Map<DexType, MethodNamingState<?>> namingStates = new HashMap<>();
+  private final Map<DexType, MethodNamingState<?>> namingStates = new IdentityHashMap<>();
   private final Map<DexType, DexType> frontiers = new IdentityHashMap<>();
 
   private final MethodNamingState<?> rootNamingState;
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
index 258646e..2b66d18 100644
--- a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -43,8 +43,8 @@
     return internalState == null ? null : internalState.getReservedByName(name);
   }
 
-  void markReservedDirectly(DexString name, DexString originalName, DexType type) {
-    getOrCreateInternalState(type).markReservedDirectly(name, originalName);
+  void markReserved(DexString name, DexString originalName, DexType type) {
+    getOrCreateInternalState(type).markReserved(name, originalName);
   }
 
   void includeReservations(ReservedFieldNamingState reservedNames) {
@@ -54,13 +54,6 @@
     includeInterfaceReservationState(reservedNames);
   }
 
-  void includeReservationsFromBelow(ReservedFieldNamingState reservedNames) {
-    for (Map.Entry<DexType, InternalState> entry : reservedNames.internalStates.entrySet()) {
-      getOrCreateInternalState(entry.getKey()).includeReservationsFromBelow(entry.getValue());
-    }
-    includeInterfaceReservationState(reservedNames);
-  }
-
   private void includeInterfaceReservationState(ReservedFieldNamingState reservedNames) {
     if (reservedNames.interfaceMinificationState != null) {
       assert interfaceMinificationState == null
@@ -71,7 +64,7 @@
 
   void setInterfaceMinificationState(ReservedFieldNamingState namingState) {
     assert namingState != null;
-    assert interfaceMinificationState == null;
+    assert interfaceMinificationState == null || interfaceMinificationState == namingState;
     this.interfaceMinificationState = namingState;
   }
 
@@ -82,25 +75,19 @@
 
   static class InternalState {
 
-    private Map<DexString, DexString> reservedNamesDirect = new IdentityHashMap<>();
-    private Map<DexString, DexString> reservedNamesBelow = new IdentityHashMap<>();
+    private final Map<DexString, DexString> reservedNames = new IdentityHashMap<>();
 
     DexString getReservedByName(DexString name) {
-      DexString reservedBy = reservedNamesDirect.get(name);
-      return reservedBy != null ? reservedBy : reservedNamesBelow.get(name);
+      DexString reservedBy = reservedNames.get(name);
+      return reservedBy != null ? reservedBy : reservedNames.get(name);
     }
 
-    void markReservedDirectly(DexString name, DexString originalName) {
-      reservedNamesDirect.put(name, originalName);
+    void markReserved(DexString name, DexString originalName) {
+      reservedNames.put(name, originalName);
     }
 
     void includeReservations(InternalState state) {
-      reservedNamesDirect.putAll(state.reservedNamesDirect);
-    }
-
-    void includeReservationsFromBelow(InternalState state) {
-      reservedNamesBelow.putAll(state.reservedNamesDirect);
-      reservedNamesBelow.putAll(state.reservedNamesBelow);
+      reservedNames.putAll(state.reservedNames);
     }
   }
 }