Introduce new field name minifier

Bug: 128973195, 127932803, 128656974, 128600647
Change-Id: I0f64019de1fe02e3b87ed94bc86b03a2478e3946
diff --git a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
index 80f3d93..f1cadda 100644
--- a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.graph;
 
 public class BottomUpClassHierarchyTraversal<T extends DexClass>
-    extends ClassHierarchyTraversal<T> {
+    extends ClassHierarchyTraversal<T, BottomUpClassHierarchyTraversal<T>> {
 
   private BottomUpClassHierarchyTraversal(
       AppView<? extends AppInfoWithSubtyping> appView, Scope scope) {
@@ -31,10 +31,19 @@
   }
 
   @Override
+  BottomUpClassHierarchyTraversal<T> self() {
+    return this;
+  }
+
+  @Override
   void addDependentsToWorklist(DexClass clazz) {
     @SuppressWarnings("unchecked")
     T clazzWithTypeT = (T) clazz;
 
+    if (excludeInterfaces && clazzWithTypeT.isInterface()) {
+      return;
+    }
+
     if (visited.contains(clazzWithTypeT)) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
index eb21910..7bf1464 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
@@ -11,7 +11,8 @@
 import java.util.Set;
 import java.util.function.Consumer;
 
-abstract class ClassHierarchyTraversal<T extends DexClass> {
+abstract class ClassHierarchyTraversal<
+    T extends DexClass, CHT extends ClassHierarchyTraversal<T, CHT>> {
 
   enum Scope {
     ALL_CLASSES,
@@ -24,11 +25,20 @@
   final Set<T> visited = new HashSet<>();
   final Deque<T> worklist = new ArrayDeque<>();
 
+  boolean excludeInterfaces = false;
+
   ClassHierarchyTraversal(AppView<? extends AppInfoWithSubtyping> appView, Scope scope) {
     this.appView = appView;
     this.scope = scope;
   }
 
+  abstract CHT self();
+
+  public CHT excludeInterfaces() {
+    this.excludeInterfaces = true;
+    return self();
+  }
+
   public void visit(Iterable<DexProgramClass> sources, Consumer<T> visitor) {
     Iterator<DexProgramClass> sourceIterator = sources.iterator();
 
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 cfc1507..a7c20c0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -12,10 +12,12 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -712,6 +714,15 @@
     return initializationOfParentTypesMayHaveSideEffects(definitions, ignore);
   }
 
+  public Iterable<DexType> allImmediateSupertypes() {
+    Iterator<DexType> iterator =
+        superType != null
+            ? Iterators.concat(
+                Iterators.singletonIterator(superType), Iterators.forArray(interfaces.values))
+            : Iterators.forArray(interfaces.values);
+    return () -> iterator;
+  }
+
   public boolean initializationOfParentTypesMayHaveSideEffects(DexDefinitionSupplier definitions) {
     return initializationOfParentTypesMayHaveSideEffects(definitions, Predicates.alwaysFalse());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 594ea9b..410025a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -245,6 +245,11 @@
         || name.contains(Java8MethodRewriter.UTILITY_CLASS_NAME_PREFIX);
   }
 
+  public boolean isProgramType(DexDefinitionSupplier definitions) {
+    DexClass clazz = definitions.definitionFor(this);
+    return clazz != null && clazz.isProgramClass();
+  }
+
   public int elementSizeForPrimitiveArrayType() {
     assert isPrimitiveArrayType();
     switch (descriptor.content[1]) {
diff --git a/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
index 9e7fbe4..2fe8bd2 100644
--- a/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.graph;
 
-public class TopDownClassHierarchyTraversal<T extends DexClass> extends ClassHierarchyTraversal<T> {
+public class TopDownClassHierarchyTraversal<T extends DexClass>
+    extends ClassHierarchyTraversal<T, TopDownClassHierarchyTraversal<T>> {
 
   private TopDownClassHierarchyTraversal(
       AppView<? extends AppInfoWithSubtyping> appView, Scope scope) {
@@ -30,10 +31,19 @@
   }
 
   @Override
+  TopDownClassHierarchyTraversal<T> self() {
+    return this;
+  }
+
+  @Override
   void addDependentsToWorklist(DexClass clazz) {
     @SuppressWarnings("unchecked")
     T clazzWithTypeT = (T) clazz;
 
+    if (excludeInterfaces && clazzWithTypeT.isInterface()) {
+      return;
+    }
+
     if (visited.contains(clazzWithTypeT)) {
       return;
     }
@@ -51,11 +61,13 @@
     }
 
     // Add super interfaces to worklist.
-    for (DexType interfaceType : clazz.interfaces.values) {
-      DexClass definition = appView.definitionFor(interfaceType);
-      if (definition != null) {
-        if (scope != Scope.ONLY_PROGRAM_CLASSES || definition.isProgramClass()) {
-          addDependentsToWorklist(definition);
+    if (!excludeInterfaces) {
+      for (DexType interfaceType : clazz.interfaces.values) {
+        DexClass definition = appView.definitionFor(interfaceType);
+        if (definition != null) {
+          if (scope != Scope.ONLY_PROGRAM_CLASSES || definition.isProgramClass()) {
+            addDependentsToWorklist(definition);
+          }
         }
       }
     }
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 22f5379..2130b37 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -4,36 +4,37 @@
 package com.android.tools.r8.naming;
 
 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;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
-import java.io.PrintStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
+import java.util.TreeSet;
 
-class FieldNameMinifier extends MemberNameMinifier<DexField, DexType> {
+class FieldNameMinifier {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final Map<DexField, DexString> renaming = new IdentityHashMap<>();
+  private Map<DexType, ReservedFieldNamingState> reservedNamingStates = new IdentityHashMap<>();
+  private final MemberNamingStrategy strategy;
 
   FieldNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) {
-    super(appView, strategy);
-  }
-
-  @Override
-  Function<DexType, ?> getKeyTransform() {
-    if (overloadAggressively) {
-      // Use the type as the key, hence reuse names per type.
-      return Function.identity();
-    } else {
-      // Always use the same key, hence do not reuse names per type.
-      return a -> Void.class;
-    }
+    this.appView = appView;
+    this.strategy = strategy;
   }
 
   FieldRenaming computeRenaming(Collection<DexClass> interfaces, Timing timing) {
@@ -41,19 +42,14 @@
     // shadow a reserved field in subclasses. While there is no concept of virtual field
     // dispatch in Java, field resolution still traverses the super type chain and external
     // code might use a subtype to reference the field.
-    timing.begin("reserve-classes");
-    reserveNamesInSubtypes(appView.dexItemFactory().objectType, globalState);
-    timing.end();
-    // Next, reserve field names in interfaces. These should only be static.
-    timing.begin("reserve-interfaces");
-    for (DexClass iface : interfaces) {
-      reserveNamesInSubtypes(iface.type, globalState);
-    }
+    timing.begin("reserve-names");
+    reserveFieldNames();
     timing.end();
     // Rename the definitions.
     timing.begin("rename-definitions");
-    renameFieldsInClasses();
     renameFieldsInInterfaces(interfaces);
+    propagateReservedFieldNamesUpwards();
+    renameFieldsInClasses();
     timing.end();
     // Rename the references that are not rebound to definitions for some reasons.
     timing.begin("rename-references");
@@ -75,81 +71,148 @@
     }
   }
 
-  private void reserveNamesInSubtypes(DexType type, NamingState<DexType, ?> state) {
-    DexClass holder = appView.definitionFor(type);
-    if (holder == null) {
-      return;
-    }
-    // If there is a mapping file, it can be that fields in libraries should be renamed and
-    // therefore not reserved.
-    NamingState<DexType, ?> newState = computeStateIfAbsent(type, t -> state.createChild());
-    holder.forEachField(
-        field -> reserveFieldName(field, newState, alwaysReserveMemberNames(holder)));
-    appView
-        .appInfo()
-        .forAllExtendsSubtypes(type, subtype -> reserveNamesInSubtypes(subtype, newState));
+  private ReservedFieldNamingState getReservedFieldNamingState(DexType type) {
+    return reservedNamingStates.get(type);
   }
 
-  private void reserveFieldName(
-      DexEncodedField encodedField, NamingState<DexType, ?> state, boolean alwaysReserve) {
-    DexField field = encodedField.field;
-    if (alwaysReserve || strategy.noObfuscation().contains(field)) {
-      state.reserveName(field.name, field.type);
-    }
+  private ReservedFieldNamingState getOrCreateReservedFieldNamingState(DexType type) {
+    return reservedNamingStates.computeIfAbsent(
+        type, ignore -> new ReservedFieldNamingState(appView));
   }
 
-  private void renameFieldsInClasses() {
-    renameFieldsInSubclasses(appView.dexItemFactory().objectType, null);
-  }
+  private void reserveFieldNames() {
+    // Reserve all field names that need to be reserved.
+    for (DexClass clazz : appView.appInfo().app().asDirect().allClasses()) {
+      ReservedFieldNamingState reservedNames = null;
+      for (DexEncodedField field : clazz.fields()) {
+        if (clazz.isLibraryClass() || appView.rootSet().noObfuscation.contains(field.field)) {
+          if (reservedNames == null) {
+            reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
+          }
+          reservedNames.markReservedDirectly(field.field);
+        }
+      }
 
-  private void renameFieldsInSubclasses(DexType type, DexType parent) {
-    DexClass clazz = appView.definitionFor(type);
-    if (clazz == null) {
-      return;
-    }
-    assert !clazz.isInterface();
-    assert clazz.superType == parent;
-
-    NamingState<DexType, ?> state = minifierState.getState(clazz.type);
-    assert state != null;
-    for (DexEncodedField field : clazz.fields()) {
-      renameField(field, state);
-    }
-    for (DexType subclass : appView.appInfo().allExtendsSubtypes(type)) {
-      renameFieldsInSubclasses(subclass, type);
-    }
-  }
-
-  private void renameFieldsInInterfaces(Collection<DexClass> interfaces) {
-    for (DexClass iface : interfaces) {
-      renameFieldsInInterface(iface);
-    }
-  }
-
-  private void renameFieldsInInterface(DexClass clazz) {
-    assert clazz.isInterface();
-    NamingState<DexType, ?> state = minifierState.getState(clazz.type);
-    assert state != null;
-    for (DexEncodedField field : clazz.fields()) {
-      renameField(field, state);
-    }
-  }
-
-  private void renameField(DexEncodedField encodedField, NamingState<DexType, ?> state) {
-    DexField field = encodedField.field;
-
-    Set<String> loggingFilter = appView.options().extensiveFieldMinifierLoggingFilter;
-    if (!loggingFilter.isEmpty()) {
-      if (loggingFilter.contains(field.toSourceString())) {
-        print(field, state, System.out);
+      // For interfaces, propagate reserved names to all implementing classes.
+      if (clazz.isInterface() && reservedNames != null) {
+        for (DexType implementationType : appView.appInfo().allImplementsSubtypes(clazz.type)) {
+          DexClass implementation = appView.definitionFor(implementationType);
+          if (implementation != null) {
+            assert !implementation.isInterface();
+            getOrCreateReservedFieldNamingState(implementationType)
+                .includeReservations(reservedNames);
+          }
+        }
       }
     }
 
-    if (!state.isReserved(field.name, field.type)) {
-      renaming.put(field, state.assignNewNameFor(field, field.name, field.type));
+    propagateReservedFieldNamesUpwards();
+  }
+
+  private void propagateReservedFieldNamesUpwards() {
+    BottomUpClassHierarchyTraversal.forProgramClasses(appView)
+        .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 renameFieldsInClasses() {
+    Map<DexType, FieldNamingState> states = new IdentityHashMap<>();
+    TopDownClassHierarchyTraversal.forAllClasses(appView)
+        .excludeInterfaces()
+        .visit(
+            appView.appInfo().classes(),
+            clazz -> {
+              assert !clazz.isInterface();
+
+              FieldNamingState parentState =
+                  clazz.superType == null
+                      ? new FieldNamingState(appView, strategy)
+                      : states
+                          .computeIfAbsent(
+                              clazz.superType, key -> new FieldNamingState(appView, strategy))
+                          .clone();
+
+              ReservedFieldNamingState reservedNames =
+                  getOrCreateReservedFieldNamingState(clazz.type);
+              FieldNamingState state = parentState.createChildState(reservedNames);
+              if (clazz.isProgramClass()) {
+                for (DexEncodedField field : clazz.fields()) {
+                  renameField(field, state);
+                }
+              }
+
+              assert !states.containsKey(clazz.type);
+              states.put(clazz.type, state);
+            });
+  }
+
+  private void renameFieldsInInterfaces(Collection<DexClass> interfaces) {
+    InterfacePartitioning partioning = new InterfacePartitioning(appView);
+    for (Set<DexClass> partition : partioning.sortedPartitions(interfaces)) {
+      renameFieldsInInterfacePartition(partition);
     }
   }
 
+  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);
+
+    FieldNamingState state = new FieldNamingState(appView, strategy, reservedNamesInPartition);
+    for (DexClass clazz : partition) {
+      if (clazz.isProgramClass()) {
+        assert clazz.isInterface();
+        for (DexEncodedField field : clazz.fields()) {
+          DexString newName = renameField(field, state);
+          namesToBeReservedInImplementsSubclasses.markReservedDirectly(newName, field.field.type);
+        }
+      }
+    }
+
+    Set<DexType> visited = Sets.newIdentityHashSet();
+    for (DexClass clazz : partition) {
+      for (DexType implementationType : appView.appInfo().allImplementsSubtypes(clazz.type)) {
+        if (!visited.add(implementationType)) {
+          continue;
+        }
+
+        DexClass implementation = appView.definitionFor(implementationType);
+        if (implementation != null) {
+          getOrCreateReservedFieldNamingState(implementationType)
+              .includeReservations(namesToBeReservedInImplementsSubclasses);
+        }
+      }
+    }
+  }
+
+  private DexString renameField(DexEncodedField encodedField, FieldNamingState state) {
+    DexField field = encodedField.field;
+    DexString newName = state.getOrCreateNameFor(field);
+    if (newName != field.name) {
+      renaming.put(field, newName);
+    }
+    return newName;
+  }
+
   private void renameNonReboundReferences() {
     // TODO(b/123068484): Collect non-rebound references instead of visiting all references.
     AppInfoWithLiveness appInfo = appView.appInfo();
@@ -190,11 +253,70 @@
     }
   }
 
-  private void print(DexField field, NamingState<DexType, ?> state, PrintStream out) {
-    out.println("--------------------------------------------------------------------------------");
-    out.println("FieldNameMinifier(`" + field.toSourceString() + "`)");
-    out.println("--------------------------------------------------------------------------------");
-    state.printState(field.type, minifierState::getStateKey, "", out);
-    out.println();
+  static class InterfacePartitioning {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final Set<DexType> visited = Sets.newIdentityHashSet();
+
+    InterfacePartitioning(AppView<AppInfoWithLiveness> appView) {
+      this.appView = appView;
+    }
+
+    private List<Set<DexClass>> sortedPartitions(Collection<DexClass> interfaces) {
+      List<Set<DexClass>> partitions = new ArrayList<>();
+      for (DexClass clazz : interfaces) {
+        if (clazz != null && visited.add(clazz.type)) {
+          Set<DexClass> partition = buildSortedPartition(clazz);
+          assert !partition.isEmpty();
+          assert partition.stream().allMatch(DexClass::isInterface);
+          assert partition.stream().map(DexClass::getType).allMatch(visited::contains);
+          partitions.add(partition);
+        }
+      }
+      return partitions;
+    }
+
+    private Set<DexClass> buildSortedPartition(DexClass src) {
+      Set<DexClass> partition = new TreeSet<>((x, y) -> x.type.slowCompareTo(y.type));
+
+      Deque<DexType> worklist = new ArrayDeque<>();
+      worklist.add(src.type);
+
+      while (!worklist.isEmpty()) {
+        DexType type = worklist.removeFirst();
+
+        DexClass clazz = appView.definitionFor(type);
+        if (clazz == null) {
+          continue;
+        }
+
+        for (DexType superinterface : clazz.interfaces.values) {
+          if (visited.add(superinterface)) {
+            worklist.add(superinterface);
+          }
+        }
+
+        if (clazz.isInterface()) {
+          partition.add(clazz);
+
+          for (DexType subtype : appView.appInfo().allImmediateSubtypes(type)) {
+            if (visited.add(subtype)) {
+              worklist.add(subtype);
+            }
+          }
+        } else if (clazz.type != appView.dexItemFactory().objectType) {
+          if (visited.add(clazz.superType)) {
+            worklist.add(clazz.superType);
+          }
+          for (DexType subclass : appView.appInfo().allExtendsSubtypes(type)) {
+            if (visited.add(subclass)) {
+              worklist.add(subclass);
+            }
+          }
+        }
+      }
+
+      return partition;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
new file mode 100644
index 0000000..acb09f0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2019, 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.Minifier.MinifierMemberNamingStrategy.EMPTY_CHAR_ARRAY;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.FieldNamingState.InternalState;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class FieldNamingState extends FieldNamingStateBase<InternalState> implements Cloneable {
+
+  private final ReservedFieldNamingState reservedNames;
+  private final MemberNamingStrategy strategy;
+
+  public FieldNamingState(AppView<? extends AppInfo> appView, MemberNamingStrategy strategy) {
+    this(appView, strategy, new ReservedFieldNamingState(appView));
+  }
+
+  public FieldNamingState(
+      AppView<? extends AppInfo> appView,
+      MemberNamingStrategy strategy,
+      ReservedFieldNamingState reservedNames) {
+    this(appView, strategy, reservedNames, new IdentityHashMap<>());
+  }
+
+  private FieldNamingState(
+      AppView<? extends AppInfo> appView,
+      MemberNamingStrategy strategy,
+      ReservedFieldNamingState reservedNames,
+      Map<DexType, InternalState> internalStates) {
+    super(appView, internalStates);
+    this.reservedNames = reservedNames;
+    this.strategy = strategy;
+  }
+
+  public FieldNamingState createChildState(ReservedFieldNamingState reservedNames) {
+    FieldNamingState childState =
+        new FieldNamingState(appView, strategy, reservedNames, internalStates);
+    childState.includeReservations(this.reservedNames);
+    return childState;
+  }
+
+  public DexString getOrCreateNameFor(DexField field) {
+    DexEncodedField encodedField = appView.appInfo().resolveField(field);
+    if (encodedField != null) {
+      DexClass clazz = appView.definitionFor(encodedField.field.holder);
+      if (clazz == null || clazz.isLibraryClass()) {
+        return field.name;
+      }
+      if (!appView.options().getProguardConfiguration().hasApplyMappingFile()
+          && appView.rootSet().noObfuscation.contains(encodedField.field)) {
+        return field.name;
+      }
+    }
+    return getOrCreateInternalState(field).createNewName(field);
+  }
+
+  public void includeReservations(ReservedFieldNamingState reservedNames) {
+    this.reservedNames.includeReservations(reservedNames);
+  }
+
+  @Override
+  public InternalState createInternalState() {
+    return new InternalState();
+  }
+
+  @Override
+  public FieldNamingState clone() {
+    Map<DexType, InternalState> internalStatesClone = new IdentityHashMap<>();
+    for (Map.Entry<DexType, InternalState> entry : internalStates.entrySet()) {
+      internalStatesClone.put(entry.getKey(), entry.getValue().clone());
+    }
+    return new FieldNamingState(appView, strategy, reservedNames, internalStatesClone);
+  }
+
+  class InternalState implements Cloneable {
+
+    private final Iterator<String> dictionaryIterator;
+    private int nextNameIndex;
+
+    public InternalState() {
+      this(1);
+    }
+
+    public InternalState(int nextNameIndex) {
+      this(
+          nextNameIndex,
+          appView.options().getProguardConfiguration().getObfuscationDictionary().iterator());
+    }
+
+    public InternalState(int nextNameIndex, Iterator<String> dictionaryIterator) {
+      this.dictionaryIterator = dictionaryIterator;
+      this.nextNameIndex = nextNameIndex;
+    }
+
+    public DexString createNewName(DexField field) {
+      DexString name;
+      do {
+        name = nextNameAccordingToStrategy(field);
+      } while (reservedNames.isReserved(name, field.type)
+          && !strategy.breakOnNotAvailable(field, name));
+      return name;
+    }
+
+    private DexString nextNameAccordingToStrategy(DexField field) {
+      if (!strategy.bypassDictionary() && dictionaryIterator.hasNext()) {
+        return appView.dexItemFactory().createString(dictionaryIterator.next());
+      } else {
+        return strategy.next(field, this);
+      }
+    }
+
+    public DexString nextNameAccordingToState() {
+      return appView
+          .dexItemFactory()
+          .createString(StringUtils.numberToIdentifier(EMPTY_CHAR_ARRAY, nextNameIndex++, false));
+    }
+
+    @Override
+    public InternalState clone() {
+      return new InternalState(nextNameIndex, dictionaryIterator);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java b/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
new file mode 100644
index 0000000..94a4c53
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import java.util.Map;
+
+abstract class FieldNamingStateBase<T> {
+
+  final AppView<? extends AppInfo> appView;
+  final Map<DexType, T> internalStates;
+
+  FieldNamingStateBase(AppView<? extends AppInfo> appView, Map<DexType, T> internalStates) {
+    this.appView = appView;
+    this.internalStates = internalStates;
+  }
+
+  final T getInternalState(DexType type) {
+    DexType internalStateKey = getInternalStateKey(type);
+    return internalStates.get(internalStateKey);
+  }
+
+  final T getOrCreateInternalState(DexField field) {
+    return getOrCreateInternalState(field.type);
+  }
+
+  final T getOrCreateInternalState(DexType type) {
+    DexType internalStateKey = getInternalStateKey(type);
+    return internalStates.computeIfAbsent(internalStateKey, key -> createInternalState());
+  }
+
+  private DexType getInternalStateKey(DexType type) {
+    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
+    return proguardConfiguration.isOverloadAggressively()
+        ? type
+        : appView.dexItemFactory().voidType;
+  }
+
+  abstract T createInternalState();
+}
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 4ec7337..ee769f0 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MethodNameMinifier.FrontierState;
-import com.android.tools.r8.naming.MethodNameMinifier.MethodNamingState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
@@ -33,24 +32,84 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 public class InterfaceMethodNameMinifier {
 
+  /**
+   * Capture a (name, proto)-pair for a {@link MethodNamingState}. Each method
+   * methodState.METHOD(...) simply defers to parent.METHOD(name, proto, ...). This allows R8 to
+   * assign the same name to methods with different prototypes, which is needed for multi-interface
+   * lambdas.
+   */
+  static class InterfaceMethodNamingState {
+
+    private final MethodNamingState<?> parent;
+    private final DexString name;
+    private final DexProto proto;
+    private final DexMethod method;
+
+    InterfaceMethodNamingState(
+        MethodNamingState<?> parent, DexMethod method, DexString name, DexProto proto) {
+      this.method = method;
+      assert parent != null;
+      this.parent = parent;
+      this.name = name;
+      this.proto = proto;
+    }
+
+    DexString assignNewName() {
+      return parent.assignNewNameFor(method, name, proto);
+    }
+
+    boolean isReserved() {
+      return parent.isReserved(name, proto);
+    }
+
+    boolean isAvailable(DexString candidate) {
+      return parent.isAvailable(proto, candidate);
+    }
+
+    void addRenaming(DexString newName) {
+      parent.addRenaming(name, proto, newName);
+    }
+
+    DexString getName() {
+      return name;
+    }
+
+    DexProto getProto() {
+      return proto;
+    }
+
+    void print(
+        String indentation,
+        Function<MethodNamingState<?>, DexType> stateKeyGetter,
+        PrintStream out) {
+      DexType stateKey = stateKeyGetter.apply(parent);
+      out.print(indentation);
+      out.print(stateKey != null ? stateKey.toSourceString() : "<?>");
+      out.print(".");
+      out.print(name.toSourceString());
+      out.println(proto.toSmaliString());
+      parent.printState(proto, stateKeyGetter, indentation + "  ", out);
+    }
+  }
+
   private final AppView<AppInfoWithLiveness> appView;
   private final Set<DexCallSite> desugaredCallSites;
   private final Equivalence<DexMethod> equivalence;
   private final FrontierState frontierState;
-  private final MemberNameMinifier<DexMethod, DexProto>.State minifierState;
+  private final MethodNameMinifier.State minifierState;
 
   private final Map<DexCallSite, DexString> callSiteRenamings = new IdentityHashMap<>();
 
   /** A map from DexMethods to all the states linked to interfaces they appear in. */
-  private final Map<Wrapper<DexMethod>, Set<NamingState<DexProto, ?>>> globalStateMap =
-      new HashMap<>();
+  private final Map<Wrapper<DexMethod>, Set<MethodNamingState<?>>> globalStateMap = new HashMap<>();
 
   /** A map from DexMethods to the first interface state it was seen in. Used to pick good names. */
-  private final Map<Wrapper<DexMethod>, NamingState<DexProto, ?>> originStates = new HashMap<>();
+  private final Map<Wrapper<DexMethod>, MethodNamingState<?>> originStates = new HashMap<>();
 
   /**
    * A map from DexMethods to all the definitions seen. Needed as the Wrapper equalizes them all.
@@ -62,7 +121,7 @@
       Set<DexCallSite> desugaredCallSites,
       Equivalence<DexMethod> equivalence,
       FrontierState frontierState,
-      MemberNameMinifier<DexMethod, DexProto>.State minifierState) {
+      MethodNameMinifier.State minifierState) {
     this.appView = appView;
     this.desugaredCallSites = desugaredCallSites;
     this.equivalence = equivalence;
@@ -98,8 +157,7 @@
       allInterfaceTypes.add(iface.type);
     }
     for (DexClass iface : interfaces) {
-      Set<NamingState<DexProto, ?>> collectedStates =
-          getReachableStates(iface.type, allInterfaceTypes);
+      Set<MethodNamingState<?>> collectedStates = getReachableStates(iface.type, allInterfaceTypes);
       for (DexEncodedMethod method : shuffleMethods(iface.methods(), appView.options())) {
         addStatesToGlobalMapForMethod(method.method, collectedStates, iface.type);
       }
@@ -133,7 +191,7 @@
           callSites.put(callSite, implementedMethods.iterator().next().method);
           for (DexEncodedMethod method : implementedMethods) {
             DexType iface = method.method.holder;
-            Set<NamingState<DexProto, ?>> collectedStates =
+            Set<MethodNamingState<?>> collectedStates =
                 getReachableStates(iface, allInterfaceTypes);
             addStatesToGlobalMapForMethod(method.method, collectedStates, iface);
             callSiteMethods.add(equivalence.wrap(method.method));
@@ -222,7 +280,7 @@
     for (Wrapper<DexMethod> wrapper : unifiedMethods) {
       DexMethod unifiedMethod = wrapper.get();
       assert unifiedMethod != null;
-      for (NamingState<DexProto, ?> namingState : globalStateMap.get(wrapper)) {
+      for (MethodNamingState<?> namingState : globalStateMap.get(wrapper)) {
         if (!namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
           namingState.reserveName(unifiedMethod.name, unifiedMethod.proto);
           changed = true;
@@ -234,15 +292,15 @@
 
   private void assignNameToInterfaceMethod(
       Wrapper<DexMethod> key, Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification) {
-    List<MethodNamingState> collectedStates = new ArrayList<>();
+    List<InterfaceMethodNamingState> collectedStates = new ArrayList<>();
     Set<DexMethod> sourceMethods = Sets.newIdentityHashSet();
     for (Wrapper<DexMethod> k : unification.getOrDefault(key, Collections.singleton(key))) {
       DexMethod unifiedMethod = k.get();
       assert unifiedMethod != null;
       sourceMethods.addAll(sourceMethodsMap.get(k));
-      for (NamingState<DexProto, ?> namingState : globalStateMap.get(k)) {
+      for (MethodNamingState<?> namingState : globalStateMap.get(k)) {
         collectedStates.add(
-            new MethodNamingState(
+            new InterfaceMethodNamingState(
                 namingState, unifiedMethod, unifiedMethod.name, unifiedMethod.proto));
       }
     }
@@ -257,15 +315,15 @@
       }
     }
 
-    MethodNamingState originState =
-        new MethodNamingState(originStates.get(key), method, method.name, method.proto);
+    InterfaceMethodNamingState originState =
+        new InterfaceMethodNamingState(originStates.get(key), method, method.name, method.proto);
     assignNameForInterfaceMethodInAllStates(collectedStates, sourceMethods, originState);
   }
 
   private void assignNameForInterfaceMethodInAllStates(
-      List<MethodNamingState> collectedStates,
+      List<InterfaceMethodNamingState> collectedStates,
       Set<DexMethod> sourceMethods,
-      MethodNamingState originState) {
+      InterfaceMethodNamingState originState) {
     assert !anyIsReserved(collectedStates);
 
     // We use the origin state to allocate a name here so that we can reuse names between different
@@ -274,14 +332,14 @@
     DexString candidate;
     do {
       candidate = originState.assignNewName();
-      for (MethodNamingState state : collectedStates) {
+      for (InterfaceMethodNamingState state : collectedStates) {
         if (!state.isAvailable(candidate)) {
           candidate = null;
           break;
         }
       }
     } while (candidate == null);
-    for (MethodNamingState state : collectedStates) {
+    for (InterfaceMethodNamingState state : collectedStates) {
       state.addRenaming(candidate);
     }
     // Rename all methods in interfaces that gave rise to this renaming.
@@ -291,7 +349,7 @@
   }
 
   private void addStatesToGlobalMapForMethod(
-      DexMethod method, Set<NamingState<DexProto, ?>> collectedStates, DexType originInterface) {
+      DexMethod method, Set<MethodNamingState<?>> collectedStates, DexType originInterface) {
     assert collectedStates.stream().noneMatch(Objects::isNull);
     Wrapper<DexMethod> key = equivalence.wrap(method);
     globalStateMap.computeIfAbsent(key, k -> new HashSet<>()).addAll(collectedStates);
@@ -307,7 +365,7 @@
       DexMethod unifiedMethod = wrapper.get();
       assert unifiedMethod != null;
 
-      for (NamingState<DexProto, ?> namingState : globalStateMap.get(wrapper)) {
+      for (MethodNamingState<?> namingState : globalStateMap.get(wrapper)) {
         if (namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
           return true;
         }
@@ -316,10 +374,10 @@
     return false;
   }
 
-  private boolean anyIsReserved(List<MethodNamingState> collectedStates) {
+  private boolean anyIsReserved(List<InterfaceMethodNamingState> collectedStates) {
     DexString name = collectedStates.get(0).getName();
     Map<DexProto, Boolean> globalStateCache = new IdentityHashMap<>();
-    for (MethodNamingState state : collectedStates) {
+    for (InterfaceMethodNamingState state : collectedStates) {
       assert state.getName() == name;
       boolean isReservedInGlobalState =
           globalStateCache.computeIfAbsent(
@@ -332,18 +390,18 @@
     return false;
   }
 
-  private Set<NamingState<DexProto, ?>> getReachableStates(DexType type, Set<DexType> allInterfaces) {
+  private Set<MethodNamingState<?>> getReachableStates(DexType type, Set<DexType> allInterfaces) {
     Set<DexType> reachableInterfaces = getReachableInterfaces(type, allInterfaces);
-    Set<NamingState<DexProto, ?>> reachableStates = new HashSet<>();
+    Set<MethodNamingState<?>> reachableStates = new HashSet<>();
     for (DexType reachableInterface : reachableInterfaces) {
       // Add the interface itself.
-      NamingState<DexProto, ?> ifaceState = minifierState.getState(reachableInterface);
+      MethodNamingState<?> ifaceState = minifierState.getState(reachableInterface);
       assert ifaceState != null;
       reachableStates.add(ifaceState);
 
       // And the frontiers that correspond to the classes that implement the interface.
       for (DexType frontier : appView.appInfo().allImplementsSubtypes(reachableInterface)) {
-        NamingState<DexProto, ?> state = minifierState.getState(frontierState.get(frontier));
+        MethodNamingState<?> state = minifierState.getState(frontierState.get(frontier));
         assert state != null;
         reachableStates.add(state);
       }
@@ -388,7 +446,7 @@
   private void print(
       DexMethod method,
       Set<DexMethod> sourceMethods,
-      List<MethodNamingState> collectedStates,
+      List<InterfaceMethodNamingState> collectedStates,
       PrintStream out) {
     out.println("-----------------------------------------------------------------------");
     out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)");
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
deleted file mode 100644
index d8007fc..0000000
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// 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 com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CachedHashValueDexItem;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.naming.NamingState.InternalState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-
-abstract class MemberNameMinifier<MemberType, StateType extends CachedHashValueDexItem> {
-
-  protected final AppView<AppInfoWithLiveness> appView;
-  protected final List<String> dictionary;
-
-  protected final Map<MemberType, DexString> renaming = new IdentityHashMap<>();
-  protected final NamingState<StateType, ?> globalState;
-  protected final boolean overloadAggressively;
-  protected final boolean useApplyMapping;
-  protected final MemberNamingStrategy strategy;
-
-  protected final State minifierState = new State();
-
-  // 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, NamingState<StateType, ?>> states = HashBiMap.create();
-
-  MemberNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) {
-    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
-    this.appView = appView;
-    this.dictionary = proguardConfiguration.getObfuscationDictionary();
-    this.overloadAggressively = proguardConfiguration.isOverloadAggressively();
-    this.globalState =
-        NamingState.createRoot(appView.dexItemFactory(), dictionary, getKeyTransform(), strategy);
-    this.useApplyMapping = proguardConfiguration.hasApplyMappingFile();
-    this.strategy = strategy;
-  }
-
-  abstract Function<StateType, ?> getKeyTransform();
-
-  protected NamingState<StateType, ?> computeStateIfAbsent(
-      DexType type, Function<DexType, NamingState<StateType, ?>> f) {
-    return states.computeIfAbsent(type, f);
-  }
-
-  protected boolean alwaysReserveMemberNames(DexClass holder) {
-    return !useApplyMapping && holder.isNotProgramClass();
-  }
-
-  // A class that provides access to the minification state. An instance of this class is passed
-  // from the method name minifier to the interface method name minifier.
-  class State {
-
-    DexString getRenaming(MemberType key) {
-      return renaming.get(key);
-    }
-
-    void putRenaming(MemberType key, DexString value) {
-      renaming.put(key, value);
-    }
-
-    NamingState<StateType, ?> getState(DexType type) {
-      return states.get(type);
-    }
-
-    DexType getStateKey(NamingState<StateType, ?> state) {
-      return states.inverse().get(state);
-    }
-
-    boolean isReservedInGlobalState(DexString name, StateType state) {
-      return globalState.isReserved(name, state);
-    }
-  }
-
-  interface MemberNamingStrategy {
-    DexString next(DexReference source, InternalState internalState);
-
-    boolean bypassDictionary();
-
-    boolean breakOnNotAvailable(DexReference source, DexString name);
-
-    Set<DexReference> noObfuscation();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
new file mode 100644
index 0000000..5c37c46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexString;
+import java.util.Set;
+
+public interface MemberNamingStrategy {
+
+  DexString next(DexMethod method, MethodNamingState.InternalState internalState);
+
+  DexString next(DexField field, FieldNamingState.InternalState internalState);
+
+  boolean bypassDictionary();
+
+  boolean breakOnNotAvailable(DexReference source, DexString name);
+
+  Set<DexReference> noObfuscation();
+}
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 d26a3d9..a8892d8 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -18,8 +18,9 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
-import java.io.PrintStream;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
@@ -29,82 +30,126 @@
 
 /**
  * A pass to rename methods using common, short names.
- * <p>
- * To assign names, we model the scopes of methods names and overloading/shadowing based on the
- * subtyping tree of classes. Such a naming scope is encoded by {@link NamingState}. It keeps
+ *
+ * <p>To assign names, we model the scopes of methods names and overloading/shadowing based on the
+ * subtyping tree of classes. Such a naming scope is encoded by {@link MethodNamingState}. It keeps
  * track of its parent node, names that have been reserved (due to keep annotations or otherwise)
  * and what names have been used for renaming so far.
- * <p>
- * As in the Dalvik VM method dispatch takes argument and return types of methods into account, we
- * can further reuse names if the prototypes of two methods differ. For this, we store the above
- * state separately for each proto using a map from protos to {@link NamingState.InternalState}
- * objects. These internal state objects are also linked.
- * <p>
- * Name assignment happens in 4 stages. In the first stage, we record all names that are used by
- * library classes or are flagged using a keep rule as reserved. This step also allocates the
- * {@link NamingState} objects for library classes. We can fully allocate these objects as we
- * never perform naming for library classes. For non-library classes, we only allocate a state
- * for the highest non-library class, i.e., we allocate states for every direct subtype of a library
- * class. The states at the boundary between library and program classes are referred to as the
- * frontier states in the code.
- * <p>
- * When reserving names in program classes, we reserve them in the state of the corresponding
+ *
+ * <p>As in the Dalvik VM method dispatch takes argument and return types of methods into account,
+ * we can further reuse names if the prototypes of two methods differ. For this, we store the above
+ * state separately for each proto using a map from protos to {@link
+ * MethodNamingState.InternalState} objects. These internal state objects are also linked.
+ *
+ * <p>Name assignment happens in 4 stages. In the first stage, we record all names that are used by
+ * library classes or are flagged using a keep rule as reserved. This step also allocates the {@link
+ * MethodNamingState} objects for library classes. We can fully allocate these objects as we never
+ * perform naming for library classes. For non-library classes, we only allocate a state for the
+ * highest non-library class, i.e., we allocate states for every direct subtype of a library class.
+ * The states at the boundary between library and program classes are referred to as the frontier
+ * states in the code.
+ *
+ * <p>When reserving names in program classes, we reserve them in the state of the corresponding
  * frontier class. This is to ensure that the names are not used for renaming in any supertype.
  * Thus, they will still be available in the subtype where they are reserved. Note that name
  * reservation only blocks names from being used for minification. We assume that the input program
  * is correctly named.
- * <p>
- * In stage 2, we reserve names that stem from interfaces. These are not propagated to
+ *
+ * <p>In stage 2, we reserve names that stem from interfaces. These are not propagated to
  * subinterfaces or implementing classes. Instead, stage 3 makes sure to query related states when
  * making naming decisions.
- * <p>
- * In stage 3, we compute minified names for all interface methods. We do this first to reduce
+ *
+ * <p>In stage 3, we compute minified names for all interface methods. We do this first to reduce
  * assignment conflicts. Interfaces do not build a tree-like inheritance structure we can exploit.
  * Thus, we have to infer the structure on the fly. For this, we compute a sets of reachable
- * interfaces. i.e., interfaces that are related via subtyping. Based on these sets, we then
- * find, for each method signature, the classes and interfaces this method signature is defined in.
- * For classes, as we still use frontier states at this point, we do not have to consider subtype
+ * interfaces. i.e., interfaces that are related via subtyping. Based on these sets, we then find,
+ * for each method signature, the classes and interfaces this method signature is defined in. For
+ * classes, as we still use frontier states at this point, we do not have to consider subtype
  * relations. For interfaces, we reserve the name in all reachable interfaces and thus ensure
  * availability.
- * <p>
- * Name assignment in this phase is a search over all impacted naming states. Using the naming state
- * of the interface this method first originated from, we propose names until we find a matching
- * one. We use the naming state of the interface to not impact name availability in naming states of
- * classes. Hence, skipping over names during interface naming does not impact their availability in
- * the next phase.
- * <p>
- * In the final stage, we assign names to methods by traversing the subtype tree, now allocating
+ *
+ * <p>Name assignment in this phase is a search over all impacted naming states. Using the naming
+ * state of the interface this method first originated from, we propose names until we find a
+ * matching one. We use the naming state of the interface to not impact name availability in naming
+ * states of classes. Hence, skipping over names during interface naming does not impact their
+ * availability in the next phase.
+ *
+ * <p>In the final stage, we assign names to methods by traversing the subtype tree, now allocating
  * separate naming states for each class starting from the frontier. In the first swoop, we allocate
  * all non-private methods, updating naming states accordingly. In a second swoop, we then allocate
  * private methods, as those may safely use names that are used by a public method further down in
  * the subtyping tree.
- * <p>
- * Finally, the computed renamings are returned as a map from {@link DexMethod} to
- * {@link DexString}. The MethodNameMinifier object should not be retained to ensure all
- * intermediate state is freed.
- * <p>
- * TODO(herhut): Currently, we do not minify members of annotation interfaces, as this would require
- * parsing and minification of the string arguments to annotations.
+ *
+ * <p>Finally, the computed renamings are returned as a map from {@link DexMethod} to {@link
+ * DexString}. The MethodNameMinifier object should not be retained to ensure all intermediate state
+ * is freed.
+ *
+ * <p>TODO(b/130338621): Currently, we do not minify members of annotation interfaces, as this would
+ * require parsing and minification of the string arguments to annotations.
  */
-class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> {
+class MethodNameMinifier {
 
-  private final Equivalence<DexMethod> equivalence;
+  // A class that provides access to the minification state. An instance of this class is passed
+  // from the method name minifier to the interface method name minifier.
+  class State {
 
-  private final FrontierState frontierState = new FrontierState();
-  private final MemberNamingStrategy strategy;
+    DexString getRenaming(DexMethod key) {
+      return renaming.get(key);
+    }
 
-  MethodNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) {
-    super(appView, strategy);
-    this.strategy = strategy;
-    equivalence =
-        overloadAggressively
-            ? MethodSignatureEquivalence.get()
-            : MethodJavaSignatureEquivalence.get();
+    void putRenaming(DexMethod key, DexString value) {
+      renaming.put(key, value);
+    }
+
+    MethodNamingState<?> getState(DexType type) {
+      return states.get(type);
+    }
+
+    DexType getStateKey(MethodNamingState<?> state) {
+      return states.inverse().get(state);
+    }
+
+    boolean isReservedInGlobalState(DexString name, DexProto state) {
+      return globalState.isReserved(name, state);
+    }
   }
 
-  @Override
-  Function<DexProto, ?> getKeyTransform() {
-    if (overloadAggressively) {
+  private final AppView<AppInfoWithLiveness> appView;
+  private final Equivalence<DexMethod> equivalence;
+  private final MemberNamingStrategy strategy;
+
+  private final Map<DexMethod, DexString> renaming = new IdentityHashMap<>();
+  private final MethodNamingState<?> globalState;
+
+  private final State minifierState = new State();
+  private final FrontierState frontierState = new FrontierState();
+
+  // 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, MethodNamingState<?>> states = HashBiMap.create();
+
+  MethodNameMinifier(AppView<AppInfoWithLiveness> appView, MemberNamingStrategy strategy) {
+    this.appView = appView;
+    this.equivalence =
+        appView.options().getProguardConfiguration().isOverloadAggressively()
+            ? MethodSignatureEquivalence.get()
+            : MethodJavaSignatureEquivalence.get();
+    this.globalState = MethodNamingState.createRoot(appView, getKeyTransform(), strategy);
+    this.strategy = strategy;
+  }
+
+  private MethodNamingState<?> computeStateIfAbsent(
+      DexType type, Function<DexType, MethodNamingState<?>> f) {
+    return states.computeIfAbsent(type, f);
+  }
+
+  private boolean alwaysReserveMemberNames(DexClass holder) {
+    return !appView.options().getProguardConfiguration().hasApplyMappingFile()
+        && holder.isNotProgramClass();
+  }
+
+  private Function<DexProto, ?> getKeyTransform() {
+    if (appView.options().getProguardConfiguration().isOverloadAggressively()) {
       // Use the full proto as key, hence reuse names based on full signature.
       return a -> a;
     } else {
@@ -162,7 +207,7 @@
     boolean shouldAssignName = holder != null && !alwaysReserveMemberNames(holder);
     if (shouldAssignName) {
       Map<Wrapper<DexMethod>, DexString> renamingAtThisLevel = new HashMap<>();
-      NamingState<DexProto, ?> state =
+      MethodNamingState<?> state =
           computeStateIfAbsent(type, k -> minifierState.getState(holder.superType).createChild());
       for (DexEncodedMethod method : holder.allMethodsSorted()) {
         assignNameToMethod(method, state, renamingAtThisLevel, doPrivates);
@@ -182,7 +227,7 @@
 
   private void assignNameToMethod(
       DexEncodedMethod encodedMethod,
-      NamingState<DexProto, ?> state,
+      MethodNamingState<?> state,
       Map<Wrapper<DexMethod>, DexString> renamingAtThisLevel,
       boolean doPrivates) {
     if (encodedMethod.accessFlags.isPrivate() != doPrivates) {
@@ -207,8 +252,10 @@
   }
 
   private void reserveNamesInClasses(
-      DexType type, DexType libraryFrontier, NamingState<DexProto, ?> parent) {
-    NamingState<DexProto, ?> state =
+      DexType type, DexType libraryFrontier, MethodNamingState<?> parent) {
+    assert appView.isInterface(type).isFalse();
+
+    MethodNamingState<?> state =
         frontierState.allocateNamingStateAndReserve(type, libraryFrontier, parent);
 
     // If this is a library class (or effectively a library class as it is missing) move the
@@ -224,19 +271,18 @@
 
     private final Map<DexType, DexType> frontiers = new IdentityHashMap<>();
 
-    NamingState<DexProto, ?> allocateNamingStateAndReserve(
-        DexType type, DexType frontier, NamingState<DexProto, ?> parent) {
+    MethodNamingState<?> allocateNamingStateAndReserve(
+        DexType type, DexType frontier, MethodNamingState<?> parent) {
       if (frontier != type) {
         frontiers.put(type, frontier);
       }
 
-      NamingState<DexProto, ?> state =
+      MethodNamingState<?> state =
           computeStateIfAbsent(
               frontier,
               ignore ->
                   parent == null
-                      ? NamingState.createRoot(
-                          appView.dexItemFactory(), dictionary, getKeyTransform(), strategy)
+                      ? MethodNamingState.createRoot(appView, getKeyTransform(), strategy)
                       : parent.createChild());
 
       DexClass holder = appView.definitionFor(type);
@@ -256,7 +302,7 @@
       return state;
     }
 
-    private void reserveNamesForMethod(DexMethod method, NamingState<DexProto, ?> state) {
+    private void reserveNamesForMethod(DexMethod method, MethodNamingState<?> state) {
       state.reserveName(method.name, method.proto);
       globalState.reserveName(method.name, method.proto);
     }
@@ -271,65 +317,6 @@
     }
   }
 
-  /**
-   * Capture a (name, proto)-pair for a {@link NamingState}. Each method methodState.METHOD(...)
-   * simply defers to parent.METHOD(name, proto, ...). This allows R8 to assign the same name to
-   * methods with different prototypes, which is needed for multi-interface lambdas.
-   */
-  static class MethodNamingState {
-
-    private final NamingState<DexProto, ?> parent;
-    private final DexString name;
-    private final DexProto proto;
-    private final DexMethod method;
-
-    MethodNamingState(
-        NamingState<DexProto, ?> parent, DexMethod method, DexString name, DexProto proto) {
-      this.method = method;
-      assert parent != null;
-      this.parent = parent;
-      this.name = name;
-      this.proto = proto;
-    }
-
-    DexString assignNewName() {
-      return parent.assignNewNameFor(method, name, proto);
-    }
-
-    boolean isReserved() {
-      return parent.isReserved(name, proto);
-    }
-
-    boolean isAvailable(DexString candidate) {
-      return parent.isAvailable(proto, candidate);
-    }
-
-    void addRenaming(DexString newName) {
-      parent.addRenaming(name, proto, newName);
-    }
-
-    DexString getName() {
-      return name;
-    }
-
-    DexProto getProto() {
-      return proto;
-    }
-
-    void print(
-        String indentation,
-        Function<NamingState<DexProto, ?>, DexType> stateKeyGetter,
-        PrintStream out) {
-      DexType stateKey = stateKeyGetter.apply(parent);
-      out.print(indentation);
-      out.print(stateKey != null ? stateKey.toSourceString() : "<?>");
-      out.print(".");
-      out.print(name.toSourceString());
-      out.println(proto.toSmaliString());
-      parent.printState(proto, stateKeyGetter, indentation + "  ", out);
-    }
-  }
-
   // Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
   // Used to ensure that the generated output is deterministic.
   static Iterable<DexEncodedMethod> shuffleMethods(
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/naming/NamingState.java
rename to src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 4a4be05..ada0dfa 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -5,12 +5,13 @@
 
 import static com.android.tools.r8.naming.Minifier.MinifierMemberNamingStrategy.EMPTY_CHAR_ARRAY;
 
-import com.android.tools.r8.graph.CachedHashValueDexItem;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.naming.MemberNameMinifier.MemberNamingStrategy;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.Sets;
@@ -18,44 +19,39 @@
 import java.io.PrintStream;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Function;
 
-class NamingState<ProtoType extends CachedHashValueDexItem, KeyType> {
+class MethodNamingState<KeyType> {
 
-  private final NamingState<ProtoType, KeyType> parent;
+  private final AppView<? extends AppInfo> appView;
+  private final MethodNamingState<KeyType> parent;
   private final Map<KeyType, InternalState> usedNames = new HashMap<>();
-  private final DexItemFactory itemFactory;
-  private final List<String> dictionary;
-  private final Function<ProtoType, KeyType> keyTransform;
+  private final Function<DexProto, KeyType> keyTransform;
   private final MemberNamingStrategy strategy;
 
-  static <S, T extends CachedHashValueDexItem> NamingState<T, S> createRoot(
-      DexItemFactory itemFactory,
-      List<String> dictionary,
-      Function<T, S> keyTransform,
+  static <S> MethodNamingState<S> createRoot(
+      AppView<? extends AppInfo> appView,
+      Function<DexProto, S> keyTransform,
       MemberNamingStrategy strategy) {
-    return new NamingState<>(null, itemFactory, dictionary, keyTransform, strategy);
+    return new MethodNamingState<>(null, appView, keyTransform, strategy);
   }
 
-  private NamingState(
-      NamingState<ProtoType, KeyType> parent,
-      DexItemFactory itemFactory,
-      List<String> dictionary,
-      Function<ProtoType, KeyType> keyTransform,
+  private MethodNamingState(
+      MethodNamingState<KeyType> parent,
+      AppView<? extends AppInfo> appView,
+      Function<DexProto, KeyType> keyTransform,
       MemberNamingStrategy strategy) {
+    this.appView = appView;
     this.parent = parent;
-    this.itemFactory = itemFactory;
-    this.dictionary = dictionary;
     this.keyTransform = keyTransform;
     this.strategy = strategy;
   }
 
-  public NamingState<ProtoType, KeyType> createChild() {
-    return new NamingState<>(this, itemFactory, dictionary, keyTransform, strategy);
+  public MethodNamingState<KeyType> createChild() {
+    return new MethodNamingState<>(this, appView, keyTransform, strategy);
   }
 
   private InternalState findInternalStateFor(KeyType key) {
@@ -71,7 +67,7 @@
     InternalState result = usedNames.get(key);
     if (result == null) {
       InternalState parentState = parent != null ? parent.getOrCreateInternalStateFor(key) : null;
-      result = new InternalState(itemFactory, parentState, dictionary);
+      result = new InternalState(appView, parentState);
       usedNames.put(key, result);
     }
     return result;
@@ -85,7 +81,7 @@
     return state.getAssignedNameFor(name, key);
   }
 
-  public DexString assignNewNameFor(DexReference source, DexString original, ProtoType proto) {
+  public DexString assignNewNameFor(DexMethod source, DexString original, DexProto proto) {
     KeyType key = keyTransform.apply(proto);
     DexString result = getAssignedNameFor(original, key);
     if (result == null) {
@@ -95,13 +91,13 @@
     return result;
   }
 
-  public void reserveName(DexString name, ProtoType proto) {
+  public void reserveName(DexString name, DexProto proto) {
     KeyType key = keyTransform.apply(proto);
     InternalState state = getOrCreateInternalStateFor(key);
     state.reserveName(name);
   }
 
-  public boolean isReserved(DexString name, ProtoType proto) {
+  public boolean isReserved(DexString name, DexProto proto) {
     KeyType key = keyTransform.apply(proto);
     InternalState state = findInternalStateFor(key);
     if (state == null) {
@@ -110,7 +106,7 @@
     return state.isReserved(name);
   }
 
-  public boolean isAvailable(ProtoType proto, DexString candidate) {
+  public boolean isAvailable(DexProto proto, DexString candidate) {
     KeyType key = keyTransform.apply(proto);
     InternalState state = findInternalStateFor(key);
     if (state == null) {
@@ -119,15 +115,15 @@
     return state.isAvailable(candidate);
   }
 
-  public void addRenaming(DexString original, ProtoType proto, DexString newName) {
+  public void addRenaming(DexString original, DexProto proto, DexString newName) {
     KeyType key = keyTransform.apply(proto);
     InternalState state = getOrCreateInternalStateFor(key);
     state.addRenaming(original, key, newName);
   }
 
   void printState(
-      ProtoType proto,
-      Function<NamingState<ProtoType, ?>, DexType> stateKeyGetter,
+      DexProto proto,
+      Function<MethodNamingState<?>, DexType> stateKeyGetter,
       String indentation,
       PrintStream out) {
     KeyType key = keyTransform.apply(proto);
@@ -170,9 +166,11 @@
       this.dictionaryIterator = dictionaryIterator;
     }
 
-    private InternalState(
-        DexItemFactory itemFactory, InternalState parentInternalState, List<String> dictionary) {
-      this(itemFactory, parentInternalState, dictionary.iterator());
+    private InternalState(AppView<? extends AppInfo> appView, InternalState parentInternalState) {
+      this(
+          appView.dexItemFactory(),
+          parentInternalState,
+          appView.options().getProguardConfiguration().getObfuscationDictionary().iterator());
     }
 
     private boolean isReserved(DexString name) {
@@ -219,7 +217,7 @@
       return result;
     }
 
-    private DexString getNewNameFor(DexReference source) {
+    private DexString getNewNameFor(DexMethod source) {
       DexString name;
       do {
         name = nextSuggestedName(source);
@@ -234,7 +232,7 @@
       renamings.put(original, proto, newName);
     }
 
-    DexString nextSuggestedName(DexReference source) {
+    DexString nextSuggestedName(DexMethod source) {
       if (!strategy.bypassDictionary() && dictionaryIterator.hasNext()) {
         return itemFactory.createString(dictionaryIterator.next());
       } else {
@@ -243,11 +241,11 @@
     }
 
     void printInternalState(
-        NamingState<ProtoType, ?> expectedNamingState,
-        Function<NamingState<ProtoType, ?>, DexType> stateKeyGetter,
+        MethodNamingState<?> expectedNamingState,
+        Function<MethodNamingState<?>, DexType> stateKeyGetter,
         String indentation,
         PrintStream out) {
-      assert expectedNamingState == NamingState.this;
+      assert expectedNamingState == MethodNamingState.this;
 
       DexType stateKey = stateKeyGetter.apply(expectedNamingState);
       out.print(indentation);
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 f01665a..e9dc45b 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -6,7 +6,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -15,9 +17,7 @@
 import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
 import com.android.tools.r8.naming.ClassNameMinifier.PackageNamingStrategy;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
-import com.android.tools.r8.naming.MemberNameMinifier.MemberNamingStrategy;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
-import com.android.tools.r8.naming.NamingState.InternalState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
@@ -151,12 +151,17 @@
     }
 
     @Override
-    public DexString next(DexReference dexReference, InternalState internalState) {
+    public DexString next(DexMethod method, MethodNamingState.InternalState internalState) {
       int counter = internalState.incrementAndGet();
       return factory.createString(StringUtils.numberToIdentifier(EMPTY_CHAR_ARRAY, counter, false));
     }
 
     @Override
+    public DexString next(DexField field, FieldNamingState.InternalState internalState) {
+      return internalState.nextNameAccordingToState();
+    }
+
+    @Override
     public boolean bypassDictionary() {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 3a62413..4796cb9 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -18,13 +18,11 @@
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.ClassNameMinifier.Namespace;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
-import com.android.tools.r8.naming.MemberNameMinifier.MemberNamingStrategy;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
 import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
-import com.android.tools.r8.naming.NamingState.InternalState;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
@@ -241,7 +239,16 @@
     }
 
     @Override
-    public DexString next(DexReference reference, InternalState internalState) {
+    public DexString next(DexMethod method, MethodNamingState.InternalState internalState) {
+      return next(method);
+    }
+
+    @Override
+    public DexString next(DexField field, FieldNamingState.InternalState internalState) {
+      return next(field);
+    }
+
+    private DexString next(DexReference reference) {
       if (mappedNames.containsKey(reference)) {
         return factory.createString(mappedNames.get(reference).getRenamedName());
       } else if (reference.isDexMethod()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
new file mode 100644
index 0000000..11b17bf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2019, 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 com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.ReservedFieldNamingState.InternalState;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class ReservedFieldNamingState extends FieldNamingStateBase<InternalState> {
+
+  public ReservedFieldNamingState(AppView<? extends AppInfo> appView) {
+    super(appView, new IdentityHashMap<>());
+  }
+
+  public boolean isReserved(DexString name, DexType type) {
+    InternalState internalState = getInternalState(type);
+    return internalState != null && internalState.isReserved(name);
+  }
+
+  public void markReservedDirectly(DexField field) {
+    markReservedDirectly(field.name, field.type);
+  }
+
+  public void markReservedDirectly(DexString name, DexType type) {
+    getOrCreateInternalState(type).markReservedDirectly(name);
+  }
+
+  public void includeReservations(ReservedFieldNamingState reservedNames) {
+    for (Map.Entry<DexType, InternalState> entry : reservedNames.internalStates.entrySet()) {
+      getOrCreateInternalState(entry.getKey()).includeReservations(entry.getValue());
+    }
+  }
+
+  public void includeReservationsFromBelow(ReservedFieldNamingState reservedNames) {
+    for (Map.Entry<DexType, InternalState> entry : reservedNames.internalStates.entrySet()) {
+      getOrCreateInternalState(entry.getKey()).includeReservationsFromBelow(entry.getValue());
+    }
+  }
+
+  @Override
+  InternalState createInternalState() {
+    return new InternalState();
+  }
+
+  static class InternalState {
+
+    private Set<DexString> reservedNamesDirect = Sets.newIdentityHashSet();
+    private Set<DexString> reservedNamesBelow = Sets.newIdentityHashSet();
+
+    public boolean isReserved(DexString name) {
+      return reservedNamesDirect.contains(name) || reservedNamesBelow.contains(name);
+    }
+
+    public void markReservedDirectly(DexString name) {
+      reservedNamesDirect.add(name);
+    }
+
+    public void includeReservations(InternalState state) {
+      reservedNamesDirect.addAll(state.reservedNamesDirect);
+    }
+
+    public void includeReservationsFromBelow(InternalState state) {
+      reservedNamesBelow.addAll(state.reservedNamesDirect);
+      reservedNamesBelow.addAll(state.reservedNamesBelow);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
index d5f32e7..2041fac 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
@@ -4,13 +4,12 @@
 
 package com.android.tools.r8.naming;
 
-import static org.hamcrest.CoreMatchers.containsString;
-
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.naming.testclasses.Greeting;
+import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 
 /** Regression test for b/128600647. */
@@ -18,6 +17,7 @@
 
   @Test
   public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("Greeter: Hello world!");
     testForR8(Backend.DEX)
         .addProgramClasses(
             TestClass.class, Greeter.class, Greeting.class, Greeting.getGreetingBase(), Tag.class)
@@ -27,7 +27,7 @@
         .enableInliningAnnotations()
         .enableMergeAnnotations()
         .run(TestClass.class)
-        .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
+        .assertSuccessWithOutput(expectedOutput);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/naming/OverloadedReservedFieldNamingTest.java b/src/test/java/com/android/tools/r8/naming/OverloadedReservedFieldNamingTest.java
new file mode 100644
index 0000000..2d0ea1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/OverloadedReservedFieldNamingTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2019, 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.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class OverloadedReservedFieldNamingTest extends TestBase {
+
+  private final boolean overloadAggressively;
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{1}, overload aggressively: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+  }
+
+  public OverloadedReservedFieldNamingTest(
+      boolean overloadAggressively, TestParameters parameters) {
+    this.overloadAggressively = overloadAggressively;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(OverloadedReservedFieldNamingTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-keep class " + A.class.getTypeName() + " { boolean a; }",
+            overloadAggressively ? "-overloadaggressively" : "")
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::verifyAggressiveOverloading)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(StringUtils.lines("Hello world!"));
+  }
+
+  private void verifyAggressiveOverloading(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(A.class);
+    assertThat(classSubject, isPresent());
+
+    FieldSubject fieldSubject = classSubject.uniqueFieldWithName("a");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+
+    FieldSubject helloFieldSubject = classSubject.uniqueFieldWithName("hello");
+    assertThat(helloFieldSubject, isPresent());
+    assertEquals(overloadAggressively ? "a" : "b", helloFieldSubject.getFinalName());
+
+    FieldSubject worldFieldSubject = classSubject.uniqueFieldWithName("world");
+    assertThat(worldFieldSubject, isPresent());
+    assertEquals(overloadAggressively ? "a" : "c", worldFieldSubject.getFinalName());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new A());
+    }
+  }
+
+  static class A {
+
+    static final boolean a = System.currentTimeMillis() >= 0;
+    static final String hello = System.currentTimeMillis() >= 0 ? "Hello" : null;
+    static final Object world = System.currentTimeMillis() >= 0 ? " world!" : null;
+
+    @Override
+    public String toString() {
+      return a ? (hello + world) : null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java
index 535e95f..e93b0fa 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java
@@ -5,7 +5,10 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.AnyOf.anyOf;
+import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
@@ -73,13 +76,12 @@
     assertThat(aFieldSubject, isPresent());
 
     if (reserveName) {
-      assertEquals("a", f1FieldSubject.getFinalName());
-      // TODO(b/128973195): J.a should not be renamed to the same as I.f1.
+      assertEquals("b", f1FieldSubject.getFinalName());
       assertEquals("a", aFieldSubject.getFinalName());
     } else {
-      // TODO(b/128973195): I.f1 Should not be renamed to the same as J.a.
-      assertEquals("a", f1FieldSubject.getFinalName());
-      assertEquals("a", aFieldSubject.getFinalName());
+      assertThat(f1FieldSubject.getFinalName(), anyOf(is("a"), is("b")));
+      assertThat(aFieldSubject.getFinalName(), anyOf(is("a"), is("b")));
+      assertNotEquals(aFieldSubject.getFinalName(), f1FieldSubject.getFinalName());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
index 5009360..77190ce 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
@@ -99,51 +99,41 @@
     assertThat(aFieldSubject, isPresent());
 
     if (reserveName) {
-      // TODO(b/128973195): A.f1 should be renamed to e instead of a.
       assertThat(f1FieldSubject, isRenamed());
-      assertEquals("a", f1FieldSubject.getFinalName());
+      assertEquals("e", f1FieldSubject.getFinalName());
 
-      // TODO(b/128973195): B.f2 should be renamed to f instead of b.
       assertThat(f2FieldSubject, isRenamed());
-      assertEquals("b", f2FieldSubject.getFinalName());
+      assertEquals("f", f2FieldSubject.getFinalName());
 
-      // TODO(b/128973195): I.f3 should be renamed to b instead of a.
       assertThat(f3FieldSubject, isRenamed());
-      assertEquals("a", f3FieldSubject.getFinalName());
+      assertEquals("b", f3FieldSubject.getFinalName());
 
-      // TODO(b/128973195): J.f4 should be renamed to c instead of b.
       assertThat(f4FieldSubject, isRenamed());
-      assertEquals("b", f4FieldSubject.getFinalName());
+      assertEquals("c", f4FieldSubject.getFinalName());
 
-      // TODO(b/128973195): K.f5 should be renamed to d instead of a.
       assertThat(f5FieldSubject, isRenamed());
-      assertEquals("a", f5FieldSubject.getFinalName());
+      assertEquals("d", f5FieldSubject.getFinalName());
 
       // B.a should not be renamed because it is not allowed to be minified.
       assertThat(aFieldSubject, not(isRenamed()));
     } else {
-      // TODO(b/128973195): A.f1 should be renamed to d instead of a.
       assertThat(f1FieldSubject, isRenamed());
-      assertEquals("a", f1FieldSubject.getFinalName());
+      assertEquals("d", f1FieldSubject.getFinalName());
 
-      // TODO(b/128973195): B.f2 should be renamed to e instead of b.
       assertThat(f2FieldSubject, isRenamed());
-      assertEquals("b", f2FieldSubject.getFinalName());
+      assertEquals("e", f2FieldSubject.getFinalName());
 
-      // TODO(b/128973195): I.f3 should be renamed to a instead of a.
       assertThat(f3FieldSubject, isRenamed());
       assertEquals("a", f3FieldSubject.getFinalName());
 
       assertThat(f4FieldSubject, isRenamed());
       assertEquals("b", f4FieldSubject.getFinalName());
 
-      // TODO(b/128973195): K.f5 should be renamed to c instead of a.
       assertThat(f5FieldSubject, isRenamed());
-      assertEquals("a", f5FieldSubject.getFinalName());
+      assertEquals("c", f5FieldSubject.getFinalName());
 
-      // TODO(b/128973195): C.a should be renamed to f instead of c.
       assertThat(aFieldSubject, isRenamed());
-      assertEquals("c", aFieldSubject.getFinalName());
+      assertEquals("f", aFieldSubject.getFinalName());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
index a60537d..057ba0c 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
@@ -77,8 +77,7 @@
 
       // Interface fields are visited/renamed before fields on classes. Thus, the interface field
       // I.f1 will be visited first and assigned the name b (since the name a is reserved).
-      // TODO(b/128973195): Should be renamed to b instead of a.
-      assertEquals("a", f1FieldSubject.getFinalName());
+      assertEquals("b", f1FieldSubject.getFinalName());
 
     } else {
       // Interface fields are visited/renamed before fields on classes. Thus, the interface field
@@ -91,7 +90,7 @@
       assertEquals("b", aFieldSubject.getFinalName());
     }
 
-    inspect(inspector);
+    inspect(inspector, "c");
   }
 
   @Test
@@ -110,10 +109,10 @@
             testForD8().addProgramClasses(I.class, J.class).compile().writeToZip())
         .run(TestClass.class)
         .assertSuccessWithOutput(expectedOutput)
-        .inspect(this::inspect);
+        .inspect(inspector -> inspect(inspector, "b"));
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, String expectedNameForF2) {
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
 
@@ -121,8 +120,7 @@
     assertThat(f2FieldSubject, isPresent());
     assertThat(f2FieldSubject, isRenamed());
 
-    // TODO(b/128973195): A.f2 should be renamed to c.
-    assertEquals("a", f2FieldSubject.getFinalName());
+    assertEquals(expectedNameForF2, f2FieldSubject.getFinalName());
   }
 
   @NeverMerge
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
index f19a00d..c349aed 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
@@ -103,9 +103,7 @@
     FieldSubject f2FieldSubject = aClassSubject.uniqueFieldWithName("f2");
     assertThat(f2FieldSubject, isPresent());
     assertThat(f2FieldSubject, isRenamed());
-
-    // TODO(b/128973195): A.f2 should be renamed to c instead of a.
-    assertEquals("a", f2FieldSubject.getFinalName());
+    assertEquals("c", f2FieldSubject.getFinalName());
   }
 
   interface I {
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
index 99fc246..dc1fe84 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
@@ -5,15 +5,15 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -26,6 +26,7 @@
 import org.junit.runners.Parameterized;
 
 // AbstractChecker -> X:
+@NeverMerge
 abstract class AbstractChecker {
   // String tag -> p
   private String tag = "PrivateInitialTag_AbstractChecker";
@@ -74,19 +75,16 @@
 
 @RunWith(Parameterized.class)
 public class MemberResolutionTest extends TestBase {
-  private static final List<Class<?>> CLASSES =
-      ImmutableList.of(
-          AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class);
 
-  private Backend backend;
+  private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
   }
 
-  public MemberResolutionTest(Backend backend) {
-    this.backend = backend;
+  public MemberResolutionTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
@@ -105,31 +103,33 @@
             "  void foo() -> a");
     FileUtils.writeTextFile(mapPath, pgMap);
 
-    AndroidApp app = readClasses(CLASSES);
-    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
-    builder
-        .addProguardConfiguration(
-            ImmutableList.of(
-                keepMainProguardConfiguration(MemberResolutionTestMain.class),
-                // Do not turn on -allowaccessmodification
-                "-applymapping " + mapPath,
-                "-dontobfuscate"), // to use the renamed names in test-mapping.txt
-            Origin.unknown())
-        .addLibraryFiles(runtimeJar(backend));
-    AndroidApp processedApp =
-        ToolHelper.runR8(
-            builder.build(),
-            options -> {
-              options.enableInlining = false;
-              options.enableVerticalClassMerging = false;
-            });
+    String expectedOutput =
+        StringUtils.lines(
+            "AbstractChecker#check:PrivateInitialTag_AbstractChecker",
+            "ConcreteChecker#check:NewTag");
 
-    String outputBefore = runOnJava(MemberResolutionTestMain.class);
-    String outputAfter = runOnVM(processedApp, MemberResolutionTestMain.class, backend);
-    assertEquals(outputBefore, outputAfter);
+    if (parameters.getBackend() == Backend.CF) {
+      testForJvm()
+          .addTestClasspath()
+          .run(parameters.getRuntime(), MemberResolutionTestMain.class)
+          .assertSuccessWithOutput(expectedOutput);
+    }
 
-    CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
-    ClassSubject base = codeInspector.clazz(AbstractChecker.class);
+    CodeInspector inspector =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(
+                AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class)
+            .addKeepMainRule(MemberResolutionTestMain.class)
+            .addKeepRules("-applymapping " + mapPath)
+            .enableMergeAnnotations()
+            .noMinification()
+            .addOptionsModification(options -> options.enableInlining = false)
+            .setMinApi(parameters.getRuntime())
+            .run(parameters.getRuntime(), MemberResolutionTestMain.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject base = inspector.clazz(AbstractChecker.class);
     assertThat(base, isPresent());
     FieldSubject p = base.field("java.lang.String", "tag");
     assertThat(p, isPresent());
@@ -140,7 +140,7 @@
     assertThat(x, isRenamed());
     assertEquals("x", x.getFinalName());
 
-    ClassSubject sub = codeInspector.clazz(ConcreteChecker.class);
+    ClassSubject sub = inspector.clazz(ConcreteChecker.class);
     assertThat(sub, isPresent());
     FieldSubject q = sub.field("java.lang.String", "tag");
     assertThat(q, isPresent());
diff --git a/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java b/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java
index 100b359..9a0696b 100644
--- a/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java
+++ b/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 
@@ -18,12 +17,10 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class B128656974 extends TestBase {
 
-  @Ignore("b/128656974")
   @Test
   public void testField() throws Exception {
     Class<?> main = TestClassMainForField.class;
@@ -125,5 +122,4 @@
       instance.foo();
     }
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index e17f713..c8b757c 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper;
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -36,10 +37,6 @@
 
 @RunWith(Parameterized.class)
 public class TreeShakingSpecificTest {
-  enum Backend {
-    DEX,
-    CF
-  }
 
   private Backend backend;