[Retrace] Retrace single positions lines if no stack trace position

Bug: 191513686
Change-Id: If602b7cad667c270544a453bc59355dd9e5ac14f
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index f6636c1..4bdb42c 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
+import com.android.tools.r8.utils.ListUtils;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -97,28 +98,29 @@
       // The result is empty, likely it maps to compiler synthesized items.
       return;
     }
-    List<String> initialResult = retracedResult.get(0);
-    initialResult.forEach(joinedConsumer);
-    if (retracedResult.size() <= 1) {
-      // The result is not ambiguous.
-      return;
-    }
-    Set<String> reportedFrames = new HashSet<>(initialResult);
-    for (int i = 1; i < retracedResult.size(); i++) {
-      List<String> ambiguousResult = retracedResult.get(i);
-      assert !ambiguousResult.isEmpty();
-      String topFrame = ambiguousResult.get(0);
-      if (reportedFrames.add(topFrame)) {
-        ambiguousResult.forEach(
-            retracedString -> {
-              int firstCharIndex = firstNonWhiteSpaceCharacterFromIndex(retracedString, 0);
-              retracedString =
-                  retracedString.substring(0, firstCharIndex)
-                      + "<OR> "
-                      + retracedString.substring(firstCharIndex);
-              joinedConsumer.accept(retracedString);
-            });
-      }
-    }
+    Set<String> reportedFrames = new HashSet<>();
+    ListUtils.forEachWithIndex(
+        retracedResult,
+        (potentialResults, index) -> {
+          assert !potentialResults.isEmpty();
+          // Check if we already reported position.
+          if (reportedFrames.add(potentialResults.get(0))) {
+            boolean isAmbiguous = potentialResults != retracedResult.get(0);
+            potentialResults.forEach(
+                retracedString -> {
+                  if (isAmbiguous) {
+                    int firstCharIndex = firstNonWhiteSpaceCharacterFromIndex(retracedString, 0);
+                    joinedConsumer.accept(
+                        retracedString.substring(0, firstCharIndex)
+                            + "<OR #"
+                            + (index)
+                            + "> "
+                            + retracedString.substring(firstCharIndex));
+                  } else {
+                    joinedConsumer.accept(retracedString);
+                  }
+                });
+          }
+        });
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 82717b1..edde5ab 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -34,6 +35,8 @@
   private final List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappedRanges;
   private final Retracer retracer;
 
+  private OptionalBool isAmbiguousCache = OptionalBool.UNKNOWN;
+
   public RetraceFrameResultImpl(
       RetraceClassResultImpl classResult,
       List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappedRanges,
@@ -49,22 +52,27 @@
 
   @Override
   public boolean isAmbiguous() {
-    if (mappedRanges.size() > 1) {
-      return true;
-    }
-    List<MappedRange> methodRanges = mappedRanges.get(0).getSecond();
-    if (methodRanges == null || methodRanges.isEmpty()) {
-      return false;
-    }
-    MappedRange lastRange = methodRanges.get(0);
-    for (MappedRange mappedRange : methodRanges) {
-      if (mappedRange != lastRange
-          && (mappedRange.minifiedRange == null
-              || !mappedRange.minifiedRange.equals(lastRange.minifiedRange))) {
+    if (isAmbiguousCache.isUnknown()) {
+      if (mappedRanges.size() > 1) {
+        isAmbiguousCache = OptionalBool.TRUE;
         return true;
       }
+      List<MappedRange> methodRanges = mappedRanges.get(0).getSecond();
+      if (methodRanges != null && !methodRanges.isEmpty()) {
+        MappedRange lastRange = methodRanges.get(0);
+        for (MappedRange mappedRange : methodRanges) {
+          if (mappedRange != lastRange
+              && (mappedRange.minifiedRange == null
+                  || !mappedRange.minifiedRange.equals(lastRange.minifiedRange))) {
+            isAmbiguousCache = OptionalBool.TRUE;
+            return true;
+          }
+        }
+      }
+      isAmbiguousCache = OptionalBool.FALSE;
     }
-    return false;
+    assert !isAmbiguousCache.isUnknown();
+    return isAmbiguousCache.isTrue();
   }
 
   @Override
@@ -120,12 +128,12 @@
 
   private RetracedMethodReferenceImpl getRetracedMethod(
       MethodReference methodReference, MappedRange mappedRange, int obfuscatedPosition) {
-    if (mappedRange.minifiedRange == null) {
+    if (mappedRange.minifiedRange == null || (obfuscatedPosition == -1 && !isAmbiguous())) {
       int originalLineNumber = mappedRange.getFirstLineNumberOfOriginalRange();
       return RetracedMethodReferenceImpl.create(
           methodReference, originalLineNumber > 0 ? originalLineNumber : obfuscatedPosition);
     }
-    if (obfuscatedPosition == -1 || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
+    if (!mappedRange.minifiedRange.contains(obfuscatedPosition)) {
       return RetracedMethodReferenceImpl.create(methodReference);
     }
     return RetracedMethodReferenceImpl.create(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index 33a0ea1..5f512f4 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -114,7 +114,11 @@
       return -1;
     }
     try {
-      return Integer.parseInt(getEntryInLine(lineNumber));
+      String lineNumberString = getEntryInLine(lineNumber);
+      if (lineNumberString.isEmpty()) {
+        return -1;
+      }
+      return Integer.parseInt(lineNumberString);
     } catch (NumberFormatException nfe) {
       return -1;
     }
@@ -226,15 +230,16 @@
       return this;
     }
 
-    public StackTraceElementStringProxyBuilder registerLineNumber(int startIndex, int endIndex) {
+    public StackTraceElementStringProxyBuilder registerLineNumber(
+        int startIndex, int endIndex, boolean insertSeparatorForRetraced) {
       lineNumber =
           new StringIndex(
               startIndex,
               endIndex,
               (retraced, original, verbose) ->
-                  retraced.hasLineNumber()
-                      ? retraced.getLineNumber() + ""
-                      : original.lineNumberAsString());
+                  (retraced.hasLineNumber()
+                      ? ((insertSeparatorForRetraced ? ":" : "") + retraced.getLineNumber())
+                      : original.lineNumberAsString()));
       orderedIndices.add(lineNumber);
       return this;
     }
@@ -296,6 +301,9 @@
     }
 
     public StackTraceElementStringProxy build() {
+      if (!lineNumber.hasIndex() && sourceFile.hasIndex()) {
+        registerLineNumber(sourceFile.endIndex, sourceFile.endIndex, true);
+      }
       return new StackTraceElementStringProxy(
           line,
           orderedIndices,
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
index fc8d5d9..e26e351 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
@@ -309,7 +309,7 @@
         if (startOfGroup == NO_MATCH) {
           return false;
         }
-        builder.registerLineNumber(startOfGroup, matcher.end(captureGroup));
+        builder.registerLineNumber(startOfGroup, matcher.end(captureGroup), false);
         return true;
       };
     }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 4506755..8cc0883 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
+import com.android.tools.r8.retrace.stacktraces.MultipleLinesNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NoObfuscatedLineNumberWithOverrideTest;
 import com.android.tools.r8.retrace.stacktraces.NoObfuscationRangeMappingWithStackTrace;
@@ -276,6 +277,11 @@
     runRetraceTest(new SingleLineNoLineNumberStackTrace());
   }
 
+  @Test
+  public void testMultipleLinesNoLineNumberStackTrace() throws Exception {
+    runRetraceTest(new MultipleLinesNoLineNumberStackTrace());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
index b92190e..310390c 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
@@ -27,13 +27,13 @@
     return Arrays.asList(
         "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java:7)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java:7)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java:7)",
         "    at com.android.tools.r8.R8.bar(R8.java:8)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java:8)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java:8)",
         "    at com.android.tools.r8.R8.main(Unknown Source)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java:9)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java:9)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java:9)",
         "    ... 42 more");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
index 429ee6b..38fbb08 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
@@ -54,13 +54,13 @@
     return Arrays.asList(
         "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.main(Unknown Source)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
         "    ... 42 more");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
new file mode 100644
index 0000000..54ef0bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2021, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class MultipleLinesNoLineNumberStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat foo.a.a(Unknown Source)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> foo.a:",
+        "    0:0:void method1(java.lang.String):42:42 -> a",
+        "    0:0:void main(java.lang.String[]):28 -> a",
+        "    1:1:void main(java.lang.String[]):153 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)",
+        "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.method1(Main.java)",
+        "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.main(Main.java)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
index 3f11112..23c5652 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
@@ -36,11 +36,11 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        // TODO(b/191513686): Could be retrace to ...Main.main(Main.java:3)
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)",
-        "\tat com.android.tools.r8.naming.retrace.Main.overload1(Main.java)",
-        "\t<OR> at com.android.tools.r8.naming.retrace.Main.overload2(Main.java)",
-        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)",
+        "\tat com.android.tools.r8.naming.retrace.Main.overload1(Main.java:7)",
+        "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.overload2(Main.java:11)",
+        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:7)",
+        "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:11)",
         "\tat com.android.tools.r8.naming.retrace.Main.mainPC(Main.java:42)");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
index d1659cd..f1fbedb 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/OverloadSameLineTest.java
@@ -32,8 +32,8 @@
         "Exception in thread \"main\" java.lang.NullPointerException",
         // TODO(b/199058242): Should be ambiguous and not inline frames
         "\tat com.android.tools.r8.naming.retrace.Main.overload(Main.java:7)",
-        "\t<OR> at com.android.tools.r8.naming.retrace.Main.overload(Main.java:15)",
-        "\t<OR> at com.android.tools.r8.naming.retrace.Main.overload(Main.java:13)");
+        "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.overload(Main.java:15)",
+        "\t<OR #2> at com.android.tools.r8.naming.retrace.Main.overload(Main.java:13)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
index 4f6ceb8..0b0c7ef 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
@@ -16,7 +16,8 @@
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat foo.a.a(Unknown Source)",
         "\tat foo.a.b(Unknown Source)",
-        "\tat foo.a.c(Unknown Source)");
+        "\tat foo.a.c(Unknown Source)",
+        "\tat foo.a.d(Unknown Source)");
   }
 
   @Override
@@ -26,20 +27,23 @@
         "    0:0:void method1(java.lang.String):42:42 -> a",
         "    0:0:void main(java.lang.String[]):28 -> a",
         "    0:0:void method2(java.lang.String):42:48 -> b",
-        "    0:0:void main2(java.lang.String[]):28 -> b",
-        "    void main3(java.lang.String[]):153 -> c");
+        "    0:0:void main2(java.lang.String[]):29 -> b",
+        "    void method3(java.lang.String):72:72 -> c",
+        "    void main3(java.lang.String[]):30 -> c",
+        "    void main4(java.lang.String[]):153 -> d");
   }
 
   @Override
   public List<String> retracedStackTrace() {
-    // TODO(b/191513686): Should have line-numbers for main, method1, main2 and main3.
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java)",
-        "\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main2(Main.java)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main3(Main.java)");
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:28)",
+        "\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java:42)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main2(Main.java:29)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main3(Main.java:30)",
+        "\t<OR #1> at com.android.tools.r8.naming.retrace.Main.method3(Main.java:72)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main4(Main.java:153)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
index 76b89e4..13b8bdd 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
@@ -27,13 +27,13 @@
     return Arrays.asList(
         "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.main(Unknown Source)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
-        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    <OR #1> at com.android.tools.r8.R8.foo(R8.java)",
         "    ... 42 more");
   }