Version 1.2.41

Merge: Detect the naming loop due to -useuniqueclassmembernames.
CL: https://r8-review.googlesource.com/c/r8/+/25400

Merge: Reproduce b/112701848.
CL: https://r8-review.googlesource.com/c/r8/+/25340

Bug: 112701848
Change-Id: I9c668f5c9f625396010f6d322dda0e7fdb3ed78e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7fdd393..3ad2ac7 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.2.40";
+  public static final String LABEL = "1.2.41";
 
   private Version() {
   }
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 97f1228..c7b73b3 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -370,11 +370,22 @@
     // We use the origin state to allocate a name here so that we can reuse names between different
     // unrelated interfaces. This saves some space. The alternative would be to use a global state
     // for allocating names, which would save the work to search here.
+    DexString previousCandidate = null;
     DexString candidate = null;
     do {
       candidate = originState.assignNewNameFor(false);
+      // If the state returns the same candidate for two consecutive trials, it should be this case:
+      //   1) an interface method with the same signature (name, param) but different return type
+      //   has been already renamed; and 2) -useuniqueclassmembernames is set.
+      // The option forces the naming state to return the same renamed name for the same signature.
+      // So, here we break the loop in an ad-hoc manner.
+      if (candidate != null && candidate == previousCandidate) {
+        assert useUniqueMemberNames;
+        break;
+      }
       for (MethodNamingState state : collectedStates) {
         if (!state.isAvailable(candidate)) {
+          previousCandidate = candidate;
           candidate = null;
           break;
         }
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 abfa3c1..fc00d91 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -113,7 +113,7 @@
     if (state == null) {
       return true;
     }
-    assert state.getAssignedNameFor(original, proto) != candidate;
+    assert state.getAssignedNameFor(original, proto) != candidate || useUniqueMemberNames;
     return state.isAvailable(candidate);
   }
 
@@ -182,8 +182,9 @@
           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);
+            Set<DexString> renamedNames = Sets.newHashSet(row.values());
+            assert renamedNames.size() <= 1;
+            result = Iterables.getOnlyElement(renamedNames, null);
           }
         } else {
           result = renamings.get(original, proto);
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
index ef6e0c8..715de97 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -29,57 +29,80 @@
 
   @Test
   public void testCfNoMinify() throws Exception {
-    testCf(MinifyMode.NONE);
+    testCf(MinifyMode.NONE, false);
   }
 
   @Test
   public void testCfMinify() throws Exception {
-    testCf(MinifyMode.JAVA);
+    testCf(MinifyMode.JAVA, false);
+  }
+
+  @Test
+  public void testCfMinify_useUniqueClassMemberNames() throws Exception {
+    testCf(MinifyMode.JAVA, true);
   }
 
   @Test
   public void testCfMinifyAggressive() throws Exception {
-    testCf(MinifyMode.AGGRESSIVE);
+    testCf(MinifyMode.AGGRESSIVE, false);
+  }
+
+  @Test
+  public void testCfMinifyAggressive_useUniqueClassMemberNames() throws Exception {
+    testCf(MinifyMode.AGGRESSIVE, true);
   }
 
   @Test
   public void testDexNoMinify() throws Exception {
-    testDex(MinifyMode.NONE);
+    testDex(MinifyMode.NONE, false);
   }
 
   @Test
   public void testDexMinify() throws Exception {
-    testDex(MinifyMode.JAVA);
+    testDex(MinifyMode.JAVA, false);
+  }
+
+  @Test
+  public void testDexMinify_useUniqueClassMemberNames() throws Exception {
+    testDex(MinifyMode.JAVA, true);
   }
 
   @Test
   public void testDexMinifyAggressive() throws Exception {
-    testDex(MinifyMode.AGGRESSIVE);
+    testDex(MinifyMode.AGGRESSIVE, false);
   }
 
-  private void testCf(MinifyMode minify) throws Exception {
+  @Test
+  public void testDexMinifyAggressive_useUniqueClassMemberNames() throws Exception {
+    testDex(MinifyMode.AGGRESSIVE, true);
+  }
+
+  private void testCf(MinifyMode minify, boolean useUniqueClassMemberNames) throws Exception {
     ProcessResult runInput =
         ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
     assertEquals(0, runInput.exitCode);
     Path outCf = temp.getRoot().toPath().resolve("cf.zip");
-    build(new ClassFileConsumer.ArchiveConsumer(outCf), minify);
+    build(new ClassFileConsumer.ArchiveConsumer(outCf), minify, useUniqueClassMemberNames);
     ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
     assertEquals(runInput.toString(), runCf.toString());
   }
 
-  private void testDex(MinifyMode minify) throws Exception {
+  private void testDex(MinifyMode minify, boolean useUniqueClassMemberNames) throws Exception {
     ProcessResult runInput =
         ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
     assertEquals(0, runInput.exitCode);
     Path outDex = temp.getRoot().toPath().resolve("dex.zip");
-    build(new DexIndexedConsumer.ArchiveConsumer(outDex), minify);
+    build(new DexIndexedConsumer.ArchiveConsumer(outDex), minify, useUniqueClassMemberNames);
     ProcessResult runDex =
         ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
     assertEquals(runInput.stdout, runDex.stdout);
     assertEquals(runInput.exitCode, runDex.exitCode);
   }
 
-  private void build(ProgramConsumer consumer, MinifyMode minify) throws Exception {
+  private void build(
+      ProgramConsumer consumer,
+      MinifyMode minify,
+      boolean useUniqueClassMemberNames) throws Exception {
     List<String> config =
         Arrays.asList(
             "-keep public class " + CLASS.getCanonicalName() + " {",
@@ -92,6 +115,7 @@
                 pgConfig -> {
                   pgConfig.setPrintMapping(true);
                   pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
+                  pgConfig.setUseUniqueClassMemberNames(useUniqueClassMemberNames);
                   if (!minify.isMinify()) {
                     pgConfig.disableObfuscation();
                   }