Allow for empty mapped ranges in mapping files

Bug: 171395772
Bug: 159425023
Bug: 175522004
Change-Id: I8286753f7209dc0906606d661d85f8adc8fb7729
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 43a0511..1fdb08f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -98,11 +98,26 @@
         CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler);
   }
 
+  public static ClassNameMapper mapperFromString(
+      String contents, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+      throws IOException {
+    return mapperFromBufferedReader(
+        CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler, allowEmptyMappedRanges);
+  }
+
   private static ClassNameMapper mapperFromBufferedReader(
       BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
+    return mapperFromBufferedReader(reader, diagnosticsHandler, false);
+  }
+
+  private static ClassNameMapper mapperFromBufferedReader(
+      BufferedReader reader, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+      throws IOException {
     try (ProguardMapReader proguardReader =
         new ProguardMapReader(
-            reader, diagnosticsHandler != null ? diagnosticsHandler : new Reporter())) {
+            reader,
+            diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
+            allowEmptyMappedRanges)) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
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 28cdcf5..12ea2a6 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -458,7 +458,6 @@
     private MappedRange(
         Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
 
-      assert minifiedRange != null || originalRange == null;
       assert originalRange == null
           || originalRange instanceof Integer
           || originalRange instanceof Range;
@@ -510,7 +509,7 @@
         builder.append(minifiedRange).append(':');
       }
       builder.append(signature);
-      if (originalRange != null && !minifiedRange.equals(originalRange)) {
+      if (originalRange != null && !originalRange.equals(minifiedRange)) {
         builder.append(":").append(originalRange);
       }
       builder.append(" -> ").append(renamedName);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 49b46cd..7ddb042 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -63,15 +63,20 @@
   private final BufferedReader reader;
   private final JsonParser jsonParser = new JsonParser();
   private final DiagnosticsHandler diagnosticsHandler;
+  private final boolean allowEmptyMappedRanges;
 
   @Override
   public void close() throws IOException {
     reader.close();
   }
 
-  ProguardMapReader(BufferedReader reader, DiagnosticsHandler diagnosticsHandler) {
+  ProguardMapReader(
+      BufferedReader reader,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean allowEmptyMappedRanges) {
     this.reader = reader;
     this.diagnosticsHandler = diagnosticsHandler;
+    this.allowEmptyMappedRanges = allowEmptyMappedRanges;
     assert reader != null;
     assert diagnosticsHandler != null;
   }
@@ -293,7 +298,7 @@
           throw new ParseException("No number follows the colon after the method signature.");
         }
       }
-      if (mappedRange == null && originalRange != null) {
+      if (!allowEmptyMappedRanges && mappedRange == null && originalRange != null) {
         throw new ParseException("No mapping for original range " + originalRange + ".");
       }
 
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index 5c50c06..927cf6e 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -74,7 +74,7 @@
   private static SeedMapper seedMapperFromInputStream(Reporter reporter, InputStream in)
       throws IOException {
     BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter)) {
+    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter, false)) {
       SeedMapper.Builder builder = SeedMapper.builder(reporter);
       proguardReader.parse(builder);
       return builder.build();
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 35f1f63..341ac8d 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
@@ -124,9 +124,12 @@
 
   private RetracedMethodImpl getRetracedMethod(
       MethodReference methodReference, MappedRange mappedRange, int obfuscatedPosition) {
-    if (obfuscatedPosition == -1
-        || mappedRange.minifiedRange == null
-        || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
+    if (mappedRange.minifiedRange == null) {
+      int originalLineNumber = mappedRange.getFirstLineNumberOfOriginalRange();
+      return RetracedMethodImpl.create(
+          methodReference, originalLineNumber > 0 ? originalLineNumber : obfuscatedPosition);
+    }
+    if (obfuscatedPosition == -1 || !mappedRange.minifiedRange.contains(obfuscatedPosition)) {
       return RetracedMethodImpl.create(methodReference);
     }
     return RetracedMethodImpl.create(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index eed4f28..c2a6749 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -32,7 +32,7 @@
     }
     try {
       ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler);
+          ClassNameMapper.mapperFromString(proguardMapProducer.get(), diagnosticsHandler, true);
       return new RetracerImpl(classNameMapper);
     } catch (Throwable throwable) {
       throw new InvalidMappingFileException(throwable);
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 d23b24e..72221e3 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
+import com.android.tools.r8.retrace.stacktraces.NoObfuscationRangeMappingWithStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
@@ -94,6 +95,11 @@
   }
 
   @Test
+  public void testNoObfuscationRangeMappingWithStackTrace() {
+    runRetraceTest(new NoObfuscationRangeMappingWithStackTrace());
+  }
+
+  @Test
   public void testNullLineTrace() {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     NullStackTrace nullStackTrace = new NullStackTrace();
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
new file mode 100644
index 0000000..4057a91
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, 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 NoObfuscationRangeMappingWithStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat foo.a(Bar.dummy:0)",
+        "\tat foo.b(Foo.dummy:2)",
+        "\tat foo.c(Baz.dummy:8)",
+        "\tat foo.d(Qux.dummy:7)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.foo(Main.dummy:1)",
+        "\tat com.android.tools.r8.naming.retrace.Main.bar(Main.dummy:3)",
+        "\tat com.android.tools.r8.naming.retrace.Main.baz(Main.dummy:8)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> foo:",
+        "    void foo(long):1:1 -> a",
+        "    void bar(int):3 -> b",
+        "    void baz():0:0 -> c", // For 0:0 and 0 use the original line number
+        "    void main(java.lang.String[]):0 -> d");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}