Always add debug information for overloaded methods

To reliably retrace by PC information the method name has to be
unique. We should therefore fallback to a line-number implementation
for overloaded methods.

Bug: 37830524
Bug: 148657906
Change-Id: Iee422da03d07b4c541f1af0fd0f43b273387a581
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 0552275..7bcf1c9 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -328,8 +328,7 @@
           Code code = method.getCode();
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
-              if (appView.options().canUseDexPcAsDebugInformation()) {
-                // TODO(b/148657906): Fall back if methods.size() > 1.
+              if (appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1) {
                 optimizeDexCodePositionsForPc(method, kotlinRemapper, mappedPositions);
               } else {
                 optimizeDexCodePositions(
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 2defd19..13afb51 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -205,6 +205,10 @@
     return addKeepRules("-keepattributes " + String.join(",", attributes));
   }
 
+  public T addKeepAttributeLineNumberTable() {
+    return addKeepRules("-keepattributes LineNumberTable");
+  }
+
   public T addKeepAllAttributes() {
     return addKeepAttributes("*");
   }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java
new file mode 100644
index 0000000..7e969c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTest.java
@@ -0,0 +1,30 @@
+// 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.debuginfo;
+
+public class DexPcWithDebugInfoForOverloadedMethodsTest {
+
+  private static void inlinee(String message) {
+    if (System.currentTimeMillis() > 0) {
+      throw new RuntimeException(message);
+    }
+  }
+
+  public static void overloaded(int x) {
+    inlinee("overloaded(int)" + x);
+  }
+
+  public static void overloaded(String y) {
+    inlinee("overloaded(String)" + y);
+  }
+
+  public static void main(String[] args) {
+    if (args.length > 0) {
+      overloaded(42);
+    } else {
+      overloaded("42");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
new file mode 100644
index 0000000..b344944
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -0,0 +1,130 @@
+// 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.debuginfo;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isTopOfStackTrace;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DexPcWithDebugInfoForOverloadedMethodsTestRunner extends TestBase {
+
+  private static final Class<?> MAIN = DexPcWithDebugInfoForOverloadedMethodsTest.class;
+  private static final int MINIFIED_LINE_POSITION = 6;
+  private static final String EXPECTED = "java.lang.RuntimeException: overloaded(String)42";
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.O)
+        .build();
+  }
+
+  public DexPcWithDebugInfoForOverloadedMethodsTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEmittedDebugInfoForOverloads()
+      throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MAIN)
+        .addKeepMainRule(MAIN)
+        .addKeepMethodRules(MAIN, "void overloaded(...)")
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            internalOptions -> {
+              // TODO(b/37830524): Remove when activated.
+              internalOptions.enablePcDebugInfoOutput = true;
+            })
+        .run(parameters.getRuntime(), MAIN)
+        .assertFailureWithErrorThatMatches(containsString(EXPECTED))
+        .inspectOriginalStackTrace(
+            (stackTrace, inspector) -> {
+              assertEquals(MAIN.getTypeName(), stackTrace.get(0).className);
+              assertEquals("overloaded", stackTrace.get(0).methodName);
+              assertThat(stackTrace.get(0).fileName, not("Unknown Source"));
+              inspect(inspector);
+            })
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              MethodSubject throwingSubject =
+                  codeInspector.clazz(MAIN).method("void", "overloaded", "java.lang.String");
+              assertThat(throwingSubject, isPresent());
+              InlinePosition inlineStack =
+                  InlinePosition.stack(
+                      InlinePosition.create(
+                          Reference.methodFromMethod(
+                              MAIN.getDeclaredMethod("inlinee", String.class)),
+                          MINIFIED_LINE_POSITION,
+                          11),
+                      InlinePosition.create(
+                          Reference.methodFromMethod(
+                              MAIN.getDeclaredMethod("overloaded", String.class)),
+                          MINIFIED_LINE_POSITION,
+                          20));
+              RetraceMethodResult retraceResult =
+                  throwingSubject
+                      .streamInstructions()
+                      .filter(InstructionSubject::isThrow)
+                      .collect(toSingle())
+                      .retraceLinePosition(codeInspector.retrace());
+              assertThat(retraceResult, isInlineFrame());
+              assertThat(retraceResult, isInlineStack(inlineStack));
+              assertThat(
+                  retraceResult,
+                  isTopOfStackTrace(
+                      stackTrace,
+                      ImmutableList.of(MINIFIED_LINE_POSITION, MINIFIED_LINE_POSITION)));
+            });
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(MAIN);
+    assertThat(clazz, isPresent());
+    assertEquals(3, clazz.allMethods().size());
+    for (FoundMethodSubject method : clazz.allMethods()) {
+      if (method.getOriginalName().equals("main")) {
+        assertNull(method.getMethod().getCode().asDexCode().getDebugInfo());
+      } else {
+        assertEquals("overloaded", method.getOriginalName());
+        assertNotNull(method.getMethod().getCode().asDexCode().getDebugInfo());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
index b8dd5f3..c47f16f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isStackTrace;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isTopOfStackTrace;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNull;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -58,14 +58,14 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(MAIN)
         .addKeepMainRule(MAIN)
-        .addKeepAttributes("LineNumberTable")
+        .addKeepAttributeLineNumberTable()
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(
             internalOptions -> {
               // TODO(b/37830524): Remove when activated.
               internalOptions.enablePcDebugInfoOutput = true;
             })
-        .run(parameters.getRuntime(), EnsureNoDebugInfoEmittedForPcOnlyTest.class)
+        .run(parameters.getRuntime(), MAIN)
         .inspectOriginalStackTrace(
             (stackTrace, inspector) -> {
               assertEquals(MAIN.getTypeName(), stackTrace.get(0).className);
@@ -99,7 +99,7 @@
               assertThat(retraceResult, isInlineStack(inlineStack));
               assertThat(
                   retraceResult,
-                  isStackTrace(
+                  isTopOfStackTrace(
                       stackTrace,
                       ImmutableList.of(INLINED_DEX_PC, INLINED_DEX_PC, INLINED_DEX_PC)));
             });
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index e1d4e6e..3fb684f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -461,13 +461,13 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isStackTrace(
+  public static Matcher<RetraceMethodResult> isTopOfStackTrace(
       StackTrace stackTrace, List<Integer> minifiedPositions) {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override
       protected boolean matchesSafely(RetraceMethodResult item) {
         List<Element> retraceElements = item.stream().collect(Collectors.toList());
-        if (retraceElements.size() != stackTrace.size()
+        if (retraceElements.size() > stackTrace.size()
             || retraceElements.size() != minifiedPositions.size()) {
           return false;
         }