Merge "Add tests to cover the implicit keeping of default constructors"
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 543e8cd..9232e08 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -14,7 +14,6 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collections;
 
 /** Interface for receiving String resource. */
 public interface StringConsumer {
@@ -101,7 +100,7 @@
     public void accept(String string, DiagnosticsHandler handler) {
       super.accept(string, handler);
       try {
-        Files.write(outputPath, Collections.singletonList(string), encoding);
+        Files.write(outputPath, string.getBytes(encoding));
       } catch (IOException e) {
         handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
       }
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 1fa8aaa..9e85188 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -32,8 +32,8 @@
     this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
     this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
     this.overloadAggressively = options.proguardConfiguration.isOverloadAggressively();
-    this.globalState =
-        NamingState.createRoot(appInfo.dexItemFactory, dictionary, getKeyTransform());
+    this.globalState = NamingState.createRoot(
+        appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames);
   }
 
   abstract Function<StateType, ?> getKeyTransform();
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 3138d32..ce37763 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -343,7 +343,8 @@
         computeStateIfAbsent(
             libraryFrontier,
             ignore -> parent == null
-                ? NamingState.createRoot(appInfo.dexItemFactory, dictionary, getKeyTransform())
+                ? NamingState.createRoot(
+                    appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames)
                 : parent.createChild());
     if (holder != null) {
       boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index b3ff544..abfa3c1 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -7,9 +7,11 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.HashBiMap;
+import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -20,80 +22,86 @@
 class NamingState<ProtoType extends CachedHashValueDexItem, KeyType> {
 
   private final NamingState<ProtoType, KeyType> parent;
-  private final Map<KeyType, InternalState> usedNames = new HashMap<>();
+  private final Map<KeyType, InternalState<ProtoType>> usedNames = new HashMap<>();
   private final DexItemFactory itemFactory;
   private final ImmutableList<String> dictionary;
   private final Function<ProtoType, KeyType> keyTransform;
+  private final boolean useUniqueMemberNames;
 
   static <S, T extends CachedHashValueDexItem> NamingState<T, S> createRoot(
-      DexItemFactory itemFactory, ImmutableList<String> dictionary, Function<T, S> keyTransform) {
-    return new NamingState<>(null, itemFactory, dictionary, keyTransform);
+      DexItemFactory itemFactory,
+      ImmutableList<String> dictionary,
+      Function<T, S> keyTransform,
+      boolean useUniqueMemberNames) {
+    return new NamingState<>(null, itemFactory, dictionary, keyTransform, useUniqueMemberNames);
   }
 
   private NamingState(
       NamingState<ProtoType, KeyType> parent,
       DexItemFactory itemFactory,
       ImmutableList<String> dictionary,
-      Function<ProtoType, KeyType> keyTransform) {
+      Function<ProtoType, KeyType> keyTransform,
+      boolean useUniqueMemberNames) {
     this.parent = parent;
     this.itemFactory = itemFactory;
     this.dictionary = dictionary;
     this.keyTransform = keyTransform;
+    this.useUniqueMemberNames = useUniqueMemberNames;
   }
 
   public NamingState<ProtoType, KeyType> createChild() {
-    return new NamingState<>(this, itemFactory, dictionary, keyTransform);
+    return new NamingState<>(this, itemFactory, dictionary, keyTransform, useUniqueMemberNames);
   }
 
-  private InternalState findInternalStateFor(ProtoType proto) {
+  private InternalState<ProtoType> findInternalStateFor(ProtoType proto) {
     KeyType key = keyTransform.apply(proto);
-    InternalState result = usedNames.get(key);
+    InternalState<ProtoType> result = usedNames.get(key);
     if (result == null && parent != null) {
       result = parent.findInternalStateFor(proto);
     }
     return result;
   }
 
-  private InternalState getOrCreateInternalStateFor(ProtoType proto) {
+  private InternalState<ProtoType> getOrCreateInternalStateFor(ProtoType proto) {
     // TODO(herhut): Maybe allocate these sparsely and search via state chain.
     KeyType key = keyTransform.apply(proto);
-    InternalState result = usedNames.get(key);
+    InternalState<ProtoType> result = usedNames.get(key);
     if (result == null) {
       if (parent != null) {
-        InternalState parentState = parent.getOrCreateInternalStateFor(proto);
+        InternalState<ProtoType> parentState = parent.getOrCreateInternalStateFor(proto);
         result = parentState.createChild();
       } else {
-        result = new InternalState(itemFactory, null, dictionary);
+        result = new InternalState<>(itemFactory, null, dictionary);
       }
       usedNames.put(key, result);
     }
     return result;
   }
 
-  public DexString getAssignedNameFor(DexString name, ProtoType proto) {
-    InternalState state = findInternalStateFor(proto);
+  private DexString getAssignedNameFor(DexString name, ProtoType proto) {
+    InternalState<ProtoType> state = findInternalStateFor(proto);
     if (state == null) {
       return null;
     }
-    return state.getAssignedNameFor(name);
+    return state.getAssignedNameFor(name, proto);
   }
 
   public DexString assignNewNameFor(DexString original, ProtoType proto, boolean markAsUsed) {
     DexString result = getAssignedNameFor(original, proto);
     if (result == null) {
-      InternalState state = getOrCreateInternalStateFor(proto);
-      result = state.getNameFor(original, markAsUsed);
+      InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
+      result = state.getNameFor(original, proto, markAsUsed);
     }
     return result;
   }
 
   public void reserveName(DexString name, ProtoType proto) {
-    InternalState state = getOrCreateInternalStateFor(proto);
+    InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
     state.reserveName(name);
   }
 
   public boolean isReserved(DexString name, ProtoType proto) {
-    InternalState state = findInternalStateFor(proto);
+    InternalState<ProtoType> state = findInternalStateFor(proto);
     if (state == null) {
       return false;
     }
@@ -101,32 +109,34 @@
   }
 
   public boolean isAvailable(DexString original, ProtoType proto, DexString candidate) {
-    InternalState state = findInternalStateFor(proto);
+    InternalState<ProtoType> state = findInternalStateFor(proto);
     if (state == null) {
       return true;
     }
-    assert state.getAssignedNameFor(original) != candidate;
+    assert state.getAssignedNameFor(original, proto) != candidate;
     return state.isAvailable(candidate);
   }
 
   public void addRenaming(DexString original, ProtoType proto, DexString newName) {
-    InternalState state = getOrCreateInternalStateFor(proto);
-    state.addRenaming(original, newName);
+    InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
+    state.addRenaming(original, proto, newName);
   }
 
-  private static class InternalState {
+  private class InternalState<InternalProtoType extends CachedHashValueDexItem> {
 
     private static final int INITIAL_NAME_COUNT = 1;
-    private final static char[] EMPTY_CHAR_ARRARY = new char[0];
+    private final char[] EMPTY_CHAR_ARRARY = new char[0];
 
     protected final DexItemFactory itemFactory;
-    private final InternalState parentInternalState;
+    private final InternalState<InternalProtoType> parentInternalState;
     private Set<DexString> reservedNames = null;
-    private Map<DexString, DexString> renamings = null;
+    private Table<DexString, InternalProtoType, DexString> renamings = null;
     private int nameCount;
     private final Iterator<String> dictionaryIterator;
 
-    private InternalState(DexItemFactory itemFactory, InternalState parentInternalState,
+    private InternalState(
+        DexItemFactory itemFactory,
+        InternalState<InternalProtoType> parentInternalState,
         Iterator<String> dictionaryIterator) {
       this.itemFactory = itemFactory;
       this.parentInternalState = parentInternalState;
@@ -135,7 +145,9 @@
       this.dictionaryIterator = dictionaryIterator;
     }
 
-    private InternalState(DexItemFactory itemFactory, InternalState parentInternalState,
+    private InternalState(
+        DexItemFactory itemFactory,
+        InternalState<InternalProtoType> parentInternalState,
         List<String> dictionary) {
       this(itemFactory, parentInternalState, dictionary.iterator());
     }
@@ -151,27 +163,40 @@
           && (parentInternalState == null || parentInternalState.isAvailable(name));
     }
 
-    public InternalState createChild() {
-      return new InternalState(itemFactory, this, dictionaryIterator);
+    InternalState<InternalProtoType> createChild() {
+      return new InternalState<>(itemFactory, this, dictionaryIterator);
     }
 
-    public void reserveName(DexString name) {
+    void reserveName(DexString name) {
       if (reservedNames == null) {
         reservedNames = Sets.newIdentityHashSet();
       }
       reservedNames.add(name);
     }
 
-    public DexString getAssignedNameFor(DexString original) {
-      DexString result = renamings == null ? null : renamings.get(original);
+    DexString getAssignedNameFor(DexString original, InternalProtoType proto) {
+      DexString result = null;
+      if (renamings != null) {
+        if (useUniqueMemberNames) {
+          Map<InternalProtoType, DexString> row = renamings.row(original);
+          if (row != null) {
+            // Either not renamed yet (0) or renamed (1). If renamed, return the same renamed name
+            // so that other members with the same name can be renamed to the same renamed name.
+            assert row.values().size() <= 1;
+            result = Iterables.getOnlyElement(row.values(), null);
+          }
+        } else {
+          result = renamings.get(original, proto);
+        }
+      }
       if (result == null && parentInternalState != null) {
-        result = parentInternalState.getAssignedNameFor(original);
+        result = parentInternalState.getAssignedNameFor(original, proto);
       }
       return result;
     }
 
-    public DexString getNameFor(DexString original, boolean markAsUsed) {
-      DexString name = getAssignedNameFor(original);
+    DexString getNameFor(DexString original, InternalProtoType proto, boolean markAsUsed) {
+      DexString name = getAssignedNameFor(original, proto);
       if (name != null) {
         return name;
       }
@@ -179,19 +204,19 @@
         name = itemFactory.createString(nextSuggestedName());
       } while (!isAvailable(name));
       if (markAsUsed) {
-        addRenaming(original, name);
+        addRenaming(original, proto, name);
       }
       return name;
     }
 
-    public void addRenaming(DexString original, DexString newName) {
+    void addRenaming(DexString original, InternalProtoType proto, DexString newName) {
       if (renamings == null) {
-        renamings = HashBiMap.create();
+        renamings = HashBasedTable.create();
       }
-      renamings.put(original, newName);
+      renamings.put(original, proto, newName);
     }
 
-    protected String nextSuggestedName() {
+    String nextSuggestedName() {
       if (dictionaryIterator.hasNext()) {
         return dictionaryIterator.next();
       } else {
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
index f949aea..a510cee 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -290,7 +290,6 @@
             + "  static <methods>;"
             + "}\n",
         keepMainProguardConfiguration(CLASS_NAME),
-        "-useuniqueclassmembernames",
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
@@ -478,7 +477,6 @@
             + "  <methods>;"
             + "}\n",
         keepMainProguardConfiguration(CLASS_NAME),
-        "-useuniqueclassmembernames",
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
@@ -631,15 +629,11 @@
     MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(subM2.isPresent());
     assertTrue(subM2.isRenamed());
-    // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
-    assertEquals(subM1.getFinalName(), subM2.getFinalName());
+    assertNotEquals(subM1.getFinalName(), subM2.getFinalName());
 
-    // TODO(b/73149686): The current impl of method minifier visits classes in a hierarchical order.
-    // Hence, already renamed same names in Super are not resolvable as the internal naming state
-    // uses only _name_ to resolve methods in the hierarchy.
-    // // No matter what, overloading methods should be renamed to the same name.
-    // assertEquals(m1.getFinalName(), subM1.getFinalName());
-    // assertEquals(m2.getFinalName(), subM2.getFinalName());
+    // No matter what, overloading methods should be renamed to the same name.
+    assertEquals(m1.getFinalName(), subM1.getFinalName());
+    assertEquals(m2.getFinalName(), subM2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);