Merge "Sort members by renamed name in Proguard map"
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index c5d366a..6414e13 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -35,7 +35,7 @@
 public class ClassNameMapper implements ProguardMap {
 
   public static class Builder extends ProguardMap.Builder {
-    final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
+    private final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
 
     private Builder() {
       mapBuilder = ImmutableMap.builder();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 34fdd9d..9c61a7a 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -14,6 +14,7 @@
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -172,20 +173,20 @@
   private final ImmutableMap<MethodSignature, MemberNaming> methodMembers;
   private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers;
 
-  /** Map of obfuscated name -> MappedRangesOfName */
-  public final Map<String, MappedRangesOfName> mappedRangesByName;
+  /** Map of renamed name -> MappedRangesOfName */
+  public final Map<String, MappedRangesOfName> mappedRangesByRenamedName;
 
   private ClassNamingForNameMapper(
       String renamedName,
       String originalName,
       Map<MethodSignature, MemberNaming> methodMembers,
       Map<FieldSignature, MemberNaming> fieldMembers,
-      Map<String, MappedRangesOfName> mappedRangesByName) {
+      Map<String, MappedRangesOfName> mappedRangesByRenamedName) {
     this.renamedName = renamedName;
     this.originalName = originalName;
     this.methodMembers = ImmutableMap.copyOf(methodMembers);
     this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
-    this.mappedRangesByName = mappedRangesByName;
+    this.mappedRangesByRenamedName = mappedRangesByRenamedName;
   }
 
   @Override
@@ -273,16 +274,13 @@
         });
 
     // Sort MappedRanges by sequence number to restore construction order (original Proguard-map
-    // input)
-    List<MappedRange> rangeList = new ArrayList<>();
-    for (MappedRangesOfName ranges : mappedRangesByName.values()) {
-      rangeList.addAll(ranges.mappedRanges);
+    // input).
+    List<MappedRange> mappedRangesSorted = new ArrayList<>();
+    for (MappedRangesOfName ranges : mappedRangesByRenamedName.values()) {
+      mappedRangesSorted.addAll(ranges.mappedRanges);
     }
-    rangeList.sort(
-        (lhs, rhs) -> {
-          return lhs.sequenceNumber - rhs.sequenceNumber;
-        });
-    for (MappedRange range : rangeList) {
+    mappedRangesSorted.sort(Comparator.comparingInt(range -> range.sequenceNumber));
+    for (MappedRange range : mappedRangesSorted) {
       writer.append("    ").append(range.toString()).append('\n');
     }
   }
@@ -313,7 +311,7 @@
         && renamedName.equals(that.renamedName)
         && methodMembers.equals(that.methodMembers)
         && fieldMembers.equals(that.fieldMembers)
-        && mappedRangesByName.equals(that.mappedRangesByName);
+        && mappedRangesByRenamedName.equals(that.mappedRangesByRenamedName);
   }
 
   @Override
@@ -322,7 +320,7 @@
     result = 31 * result + renamedName.hashCode();
     result = 31 * result + methodMembers.hashCode();
     result = 31 * result + fieldMembers.hashCode();
-    result = 31 * result + mappedRangesByName.hashCode();
+    result = 31 * result + mappedRangesByRenamedName.hashCode();
     return result;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 5163772..6d12230 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -212,8 +212,8 @@
         continue;
       }
 
-      IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
-          groupMethodsByName(namingLens, clazz);
+      IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
+          groupMethodsByRenamedName(namingLens, clazz);
 
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
@@ -232,8 +232,11 @@
       // First transfer renamed fields to classNamingBuilder.
       addFieldsToClassNaming(namingLens, clazz, onDemandClassNamingBuilder);
 
-      // Then process the methods.
-      for (List<DexEncodedMethod> methods : methodsByName.values()) {
+      // Then process the methods, ordered by renamed name.
+      List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
+      renamedMethodNames.sort(DexString::slowCompareTo);
+      for (DexString methodName : renamedMethodNames) {
+        List<DexEncodedMethod> methods = methodsByRenamedName.get(methodName);
         if (methods.size() > 1) {
           // If there are multiple methods with the same name (overloaded) then sort them for
           // deterministic behaviour: the algorithm will assign new line numbers in this order.
@@ -393,27 +396,18 @@
         });
   }
 
-  private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByName(
+  private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
       NamingLens namingLens, DexProgramClass clazz) {
-    IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+    IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
         new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
-    clazz.forEachMethod(
-        method -> {
-          // Add method only if renamed or contains positions.
-          if (namingLens.lookupName(method.method) != method.method.name
-              || doesContainPositions(method)) {
-            methodsByName.compute(
-                method.method.name,
-                (name, methods) -> {
-                  if (methods == null) {
-                    methods = new ArrayList<>();
-                  }
-                  methods.add(method);
-                  return methods;
-                });
-          }
-        });
-    return methodsByName;
+    for (DexEncodedMethod method : clazz.methods()) {
+      // Add method only if renamed or contains positions.
+      DexString renamedName = namingLens.lookupName(method.method);
+      if (renamedName != method.method.name || doesContainPositions(method)) {
+        methodsByRenamedName.computeIfAbsent(renamedName, key -> new ArrayList<>()).add(method);
+      }
+    }
+    return methodsByRenamedName;
   }
 
   private static boolean doesContainPositions(DexEncodedMethod method) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 5707812..97f6820 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -630,7 +630,7 @@
           return null;
         }
         ClassNamingForNameMapper.MappedRangesOfName ranges =
-            classNaming.mappedRangesByName.get(obfuscatedMethodName);
+            classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
         if (ranges == null) {
           return null;
         }
@@ -694,7 +694,7 @@
           return null;
         }
         ClassNamingForNameMapper.MappedRangesOfName ranges =
-            classNaming.mappedRangesByName.get(obfuscatedMethodName);
+            classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
         if (ranges == null) {
           return null;
         }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index 0f8ac65..ce84f51 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -185,7 +185,7 @@
     ClassNamingForNameMapper classNaming = mapper.getClassNaming(TEST_PACKAGE + "." + TEST_CLASS);
     assertNotNull(classNaming);
 
-    MappedRangesOfName rangesForMain = classNaming.mappedRangesByName.get("main");
+    MappedRangesOfName rangesForMain = classNaming.mappedRangesByRenamedName.get("main");
     assertNotNull(rangesForMain);
 
     List<MappedRange> frames = rangesForMain.allRangesForLine(expectedLineNumber);
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index abc7a73..6ae151e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static junit.framework.TestCase.assertEquals;
+
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.beust.jcommander.internal.Lists;
@@ -20,52 +21,60 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
 
-  public Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
+  private static class CompilationResult {
+    AndroidApp app;
+    String proguardMap;
+  }
+
+  private static Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
     List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
     Collections.shuffle(toShuffle);
     return new LinkedHashSet<>(toShuffle);
   }
 
-  private AndroidApp doRun()
-      throws IOException, ProguardRuleParserException, ExecutionException,
-      CompilationFailedException {
+  private CompilationResult doRun() throws IOException, CompilationFailedException {
     R8Command command =
         R8Command.builder()
             .addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK))
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
             .setMinApiLevel(AndroidApiLevel.L.getLevel())
             .build();
-    return ToolHelper.runR8(
-        command,
-        options -> {
-          // For this test just do random shuffle.
-          options.testing.irOrdering = this::shuffle;
-          // Only use one thread to process to process in the order decided by the callback.
-          options.numberOfThreads = 1;
-          // Ignore the missing classes.
-          options.ignoreMissingClasses = true;
-        });
+    CompilationResult result = new CompilationResult();
+    result.app =
+        ToolHelper.runR8(
+            command,
+            options -> {
+              // For this test just do random shuffle.
+              options.testing.irOrdering = R8GMSCoreDeterministicTest::shuffle;
+              // Only use one thread to process to process in the order decided by the callback.
+              options.numberOfThreads = 1;
+              // Ignore the missing classes.
+              options.ignoreMissingClasses = true;
+              // Store the generated Proguard map.
+              options.proguardMapConsumer =
+                  (proguardMap, handler) -> result.proguardMap = proguardMap;
+            });
+    return result;
   }
 
   @Test
   public void deterministic() throws Exception {
-
     // Run two independent compilations.
-    AndroidApp app1 = doRun();
-    AndroidApp app2 = doRun();
+    CompilationResult result1 = doRun();
+    CompilationResult result2 = doRun();
 
     // Check that the generated bytecode runs through the dex2oat verifier with no errors.
     Path combinedInput = temp.getRoot().toPath().resolve("all.jar");
     Path oatFile = temp.getRoot().toPath().resolve("all.oat");
-    app1.writeToZip(combinedInput, OutputMode.DexIndexed);
+    result1.app.writeToZip(combinedInput, OutputMode.DexIndexed);
     ToolHelper.runDex2Oat(combinedInput, oatFile);
 
     // Verify that the result of the two compilations was the same.
-    assertIdenticalApplications(app1, app2);
+    assertIdenticalApplications(result1.app, result2.app);
+    assertEquals(result1.proguardMap, result2.proguardMap);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index 6adf680..d881185 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.internal;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.utils.AndroidApp;
@@ -11,17 +13,33 @@
 
 public class R8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
 
+  private String proguardMap1 = null;
+  private String proguardMap2 = null;
+
   @Test
   public void buildFromDeployJar() throws Exception {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
-    AndroidApp app1 = buildFromDeployJar(
-        CompilerUnderTest.R8, CompilationMode.RELEASE,
-        GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
-    AndroidApp app2 = buildFromDeployJar(
-        CompilerUnderTest.R8, CompilationMode.RELEASE,
-        GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
+    AndroidApp app1 =
+        buildFromDeployJar(
+            CompilerUnderTest.R8,
+            CompilationMode.RELEASE,
+            GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+            false,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+    AndroidApp app2 =
+        buildFromDeployJar(
+            CompilerUnderTest.R8,
+            CompilationMode.RELEASE,
+            GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+            false,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap2 = proguardMap);
 
     // Verify that the result of the two compilations was the same.
     assertIdenticalApplications(app1, app2);
+    assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index ae470c6..4374d0e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.utils.AndroidApp;
 import org.junit.Test;
@@ -10,15 +12,33 @@
 public class R8GMSCoreV10TreeShakeJarVerificationTest
     extends R8GMSCoreTreeShakeJarVerificationTest {
 
+  private String proguardMap1 = null;
+  private String proguardMap2 = null;
+
   @Test
   public void buildAndTreeShakeFromDeployJar() throws Exception {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
-    AndroidApp app1 = buildAndTreeShakeFromDeployJar(
-        CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
-    AndroidApp app2 = buildAndTreeShakeFromDeployJar(
-        CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
+    AndroidApp app1 =
+        buildAndTreeShakeFromDeployJar(
+            CompilationMode.RELEASE,
+            GMSCORE_V10_DIR,
+            false,
+            GMSCORE_V10_MAX_SIZE,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+    AndroidApp app2 =
+        buildAndTreeShakeFromDeployJar(
+            CompilationMode.RELEASE,
+            GMSCORE_V10_DIR,
+            false,
+            GMSCORE_V10_MAX_SIZE,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap2 = proguardMap);
 
     // Verify that the result of the two compilations was the same.
     assertIdenticalApplications(app1, app2);
+    assertEquals(proguardMap1, proguardMap2);
   }
 }