Don't strip debug info when it could loose source file information.

This CL fixes the issue of stripping single line entries which can
cause the same lack of source file.

Bug: 206902024
Change-Id: Ide9c7bcbf2ca6fe88ab9fd8b07f962dde246d064
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index b67a8c2..5c84cd4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1869,8 +1869,17 @@
     return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
   }
 
+  // Debug entries may be dropped only if the source file content allows being omitted from
+  // stack traces, or if the VM will report the source file even with a null valued debug info.
+  public boolean allowDiscardingResidualDebugInfo() {
+    // TODO(b/146565491): We can drop debug info once fixed at a known min-api.
+    return sourceFileProvider != null && sourceFileProvider.allowDiscardingSourceFile();
+  }
+
   public boolean canUseDexPcAsDebugInformation() {
-    return lineNumberOptimization == LineNumberOptimization.ON && hasMinApi(AndroidApiLevel.O);
+    return lineNumberOptimization == LineNumberOptimization.ON
+        && hasMinApi(AndroidApiLevel.O)
+        && allowDiscardingResidualDebugInfo();
   }
 
   public boolean isInterfaceMethodDesugaringEnabled() {
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 8505e9f..71da5cc 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -389,10 +389,6 @@
         }
       }
 
-      boolean canStripDebugInfo =
-          appView.options().sourceFileProvider != null
-              && appView.options().sourceFileProvider.allowDiscardingSourceFile();
-
       if (isSyntheticClass) {
         onDemandClassNamingBuilder
             .get()
@@ -444,9 +440,7 @@
           List<MappedPosition> mappedPositions;
           Code code = method.getCode();
           boolean canUseDexPc =
-              canStripDebugInfo
-                  && appView.options().canUseDexPcAsDebugInformation()
-                  && methods.size() == 1;
+              appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1;
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
               if (canUseDexPc) {
@@ -485,6 +479,9 @@
               && methodMappingInfo.isEmpty()
               && obfuscatedNameDexString == originalMethod.name
               && originalMethod.holder == originalType) {
+            assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
+                || !doesContainPositions(method)
+                || appView.isCfByteCodePassThrough(method);
             continue;
           }
 
@@ -615,10 +612,10 @@
             }
             i = j;
           }
-          if (canStripDebugInfo
-              && method.getCode().isDexCode()
+          if (method.getCode().isDexCode()
               && method.getCode().asDexCode().getDebugInfo()
                   == DexDebugInfoForSingleLineMethod.getInstance()) {
+            assert appView.options().allowDiscardingResidualDebugInfo();
             method.getCode().asDexCode().setDebugInfo(null);
           }
         } // for each method of the group
@@ -946,6 +943,7 @@
         && !hasOverloads
         && !appView.options().debug
         && appView.options().lineNumberOptimization != LineNumberOptimization.OFF
+        && appView.options().allowDiscardingResidualDebugInfo()
         && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
       dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
       return mappedPositions;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
index 48b9fc8..a8eb9cc 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debuginfo;
 
+import static org.junit.Assert.assertNull;
+
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.OutputMode;
@@ -13,6 +15,8 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.DexParser;
 import com.android.tools.r8.dex.DexSection;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -31,10 +35,8 @@
     return getTestParameters().withNoneRuntime().build();
   }
 
-  private final TestParameters parameters;
-
   public CanonicalizeWithInline(TestParameters parameters) {
-    this.parameters = parameters;
+    parameters.assertNoneRuntime();
   }
 
   private int getNumberOfDebugInfos(Path file) throws IOException {
@@ -58,10 +60,17 @@
             .addProgramClasses(clazzA, clazzB)
             .addKeepRules(
                 "-keepattributes SourceFile,LineNumberTable",
-                "-keep class ** {\n" + "public void call(int);\n" + "}")
+                "-keep class ** { public void call(int); }")
             .enableInliningAnnotations()
             .enableSideEffectAnnotations()
             .compile();
+    result.inspect(
+        inspector -> {
+          DexEncodedMethod method =
+              inspector.clazz(ClassA.class).uniqueMethodWithName("call").getMethod();
+          DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
+          assertNull(debugInfo);
+        });
     Path classesPath = temp.getRoot().toPath();
     result.app.write(classesPath, OutputMode.DexIndexed);
     int numberOfDebugInfos =
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
index 37d2b6e..b3c1c08 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceFrameResult;
-import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -68,7 +67,6 @@
         .addKeepMainRule(MAIN)
         .addKeepMethodRules(MAIN, "void overloaded(...)")
         .addKeepAttributeLineNumberTable()
-        .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertFailureWithErrorThatMatches(containsString(EXPECTED))
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 30505ab..a5e76eb 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -7,8 +7,8 @@
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
 import static com.android.tools.r8.utils.InternalOptions.LineNumberOptimization.ON;
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNull;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -21,7 +21,6 @@
 import com.android.tools.r8.graph.DexDebugEntryBuilder;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
-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.MethodSubject;
@@ -38,7 +37,6 @@
 
   private static final String FILENAME_MAIN = "EnsureNoDebugInfoEmittedForPcOnlyTest.java";
   private static final Class<?> MAIN = EnsureNoDebugInfoEmittedForPcOnlyTest.class;
-  private static final int INLINED_DEX_PC = 32;
 
   private final TestParameters parameters;
 
@@ -51,8 +49,9 @@
     this.parameters = parameters;
   }
 
-  private boolean apiLevelSupportsPcOutput() {
-    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+  private boolean apiLevelSupportsPcAndSourceFileOutput() {
+    // TODO(b/146565491): Update with API level once fixed.
+    return false;
   }
 
   @Test
@@ -77,7 +76,7 @@
         .internalEnableMappingOutput()
         // TODO(b/191038746): Enable LineNumberOptimization for release builds for DEX PC Output.
         .applyIf(
-            apiLevelSupportsPcOutput(),
+            apiLevelSupportsPcAndSourceFileOutput(),
             builder ->
                 builder.addOptionsModification(
                     options -> {
@@ -98,7 +97,7 @@
         .run(parameters.getRuntime(), MAIN)
         .inspectFailure(
             inspector -> {
-              if (apiLevelSupportsPcOutput()) {
+              if (apiLevelSupportsPcAndSourceFileOutput()) {
                 checkNoDebugInfo(inspector, 5);
               } else {
                 checkHasLineNumberInfo(inspector);
@@ -122,7 +121,7 @@
 
   @Test
   public void testNoEmittedDebugInfoR8() throws Exception {
-    assumeTrue(apiLevelSupportsPcOutput());
+    assumeTrue(apiLevelSupportsPcAndSourceFileOutput());
     testForR8(parameters.getBackend())
         .addProgramClasses(MAIN)
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
index 652f401..b881f55 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
@@ -9,14 +9,18 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
+import org.hamcrest.CoreMatchers;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,14 +31,17 @@
 public class SingleLineInfoRemoveTest extends TestBase {
 
   private final TestParameters parameters;
+  private final boolean customSourceFile;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{0}, custom-source-file:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
-  public SingleLineInfoRemoveTest(TestParameters parameters) {
+  public SingleLineInfoRemoveTest(TestParameters parameters, boolean customSourceFile) {
     this.parameters = parameters;
+    this.customSourceFile = customSourceFile;
   }
 
   public StackTrace expectedStackTrace;
@@ -58,9 +65,27 @@
         .addKeepMainRule(Main.class)
         .addKeepAttributeSourceFile()
         .addKeepAttributeLineNumberTable()
+        .applyIf(
+            customSourceFile,
+            b -> b.getBuilder().setSourceFileProvider(env -> "MyCustomSourceFile"))
         .enableInliningAnnotations()
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectOriginalStackTrace(
+            stackTrace -> {
+              for (StackTraceLine line : stackTrace.getStackTraceLines()) {
+                if (customSourceFile) {
+                  assertEquals("MyCustomSourceFile", line.fileName);
+                } else if (parameters.isCfRuntime()) {
+                  assertEquals("SourceFile", line.fileName);
+                } else {
+                  assertThat(
+                      line.fileName,
+                      CoreMatchers.anyOf(
+                          CoreMatchers.is("SourceFile"), CoreMatchers.is("Unknown Source")));
+                }
+              }
+            })
         .inspectStackTrace(
             (stackTrace, inspector) -> {
               assertThat(stackTrace, isSame(expectedStackTrace));
@@ -68,10 +93,14 @@
               assertThat(mainSubject, isPresent());
               assertThat(
                   mainSubject.uniqueMethodWithName("shouldRemoveLineNumber"),
-                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+                  notIf(hasLineNumberTable(), canSingleLineDebugInfoBeDiscarded()));
             });
   }
 
+  private boolean canSingleLineDebugInfoBeDiscarded() {
+    return parameters.isDexRuntime() && !customSourceFile;
+  }
+
   public static class Main {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestRunner.java
new file mode 100644
index 0000000..637e1f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestRunner.java
@@ -0,0 +1,90 @@
+// 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.debuginfo.pc2pc;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DifferentParameterCountMultilineCodeTestRunner extends TestBase {
+
+  public static final Class<?> CLASS = DifferentParameterCountMultilineCodeTestSource.class;
+
+  private final TestParameters parameters;
+  private final boolean customSourceFile;
+
+  @Parameterized.Parameters(name = "{0}, custom-source-file:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DifferentParameterCountMultilineCodeTestRunner(
+      TestParameters parameters, boolean customSourceFile) {
+    this.parameters = parameters;
+    this.customSourceFile = customSourceFile;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASS)
+        .addKeepMainRule(CLASS)
+        // Keep all the methods but allow renaming.
+        .noTreeShaking()
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .addKeepRules("-renamesourcefileattribute " + (customSourceFile ? "X" : "SourceFile"))
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), CLASS)
+        .assertFailureWithErrorThatThrows(IllegalStateException.class)
+        .inspectOriginalStackTrace(
+            s -> {
+              for (StackTraceLine line : s.getStackTraceLines()) {
+                assertTrue("Expected line number in: " + line, line.hasLineNumber());
+                if (customSourceFile) {
+                  assertEquals("X", line.fileName);
+                } else if (parameters
+                    .getApiLevel()
+                    .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport())) {
+                  assertEquals("Unknown Source", line.fileName);
+                } else {
+                  assertEquals("SourceFile", line.fileName);
+                }
+              }
+              assertEquals("Expected 4 stack frames in:\n" + s, 4, s.getStackTraceLines().size());
+            })
+        .inspectStackTrace(
+            retracedStack ->
+                assertThat(
+                    retracedStack,
+                    StackTrace.isSame(
+                        StackTrace.builder()
+                            .add(makeLine("args0", 12))
+                            .add(makeLine("args1", 17))
+                            .add(makeLine("args2", 25))
+                            .add(makeLine("main", 32))
+                            .build())));
+  }
+
+  private StackTraceLine makeLine(String methodName, int lineNumber) {
+    return StackTraceLine.builder()
+        .setClassName(typeName(CLASS))
+        .setFileName(CLASS.getSimpleName() + ".java")
+        .setMethodName(methodName)
+        .setLineNumber(lineNumber)
+        .build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestSource.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestSource.java
new file mode 100644
index 0000000..221becb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountMultilineCodeTestSource.java
@@ -0,0 +1,35 @@
+// 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.debuginfo.pc2pc;
+
+class DifferentParameterCountMultilineCodeTestSource {
+
+  public static void args0() {
+    if (System.nanoTime() < 0) {
+      System.out.println("Not hit...");
+    }
+    throw new IllegalStateException("DONE!");
+  }
+
+  public static void args1(String arg1) {
+    if (!arg1.equals("asdf")) {
+      args0();
+    } else {
+      throw new ArithmeticException("WAT");
+    }
+  }
+
+  public static void args2(String arg1, Object arg2) {
+    if (!arg1.equals(arg2)) {
+      args1(arg1);
+    } else {
+      throw new ArithmeticException("NO");
+    }
+  }
+
+  public static void main(String[] args) {
+    args2(System.nanoTime() < 0 ? args[0] : "foo", args.length > 0 ? args[0] : "bar");
+    throw new ArithmeticException("NO AGAIN");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
new file mode 100644
index 0000000..398ecb4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
@@ -0,0 +1,102 @@
+// 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.debuginfo.pc2pc;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DifferentParameterCountSingleLineCodeTestRunner extends TestBase {
+
+  private static final Class<?> CLASS = DifferentParameterCountSingleLineCodeTestSource.class;
+
+  private final TestParameters parameters;
+  private final boolean customSourceFile;
+
+  @Parameterized.Parameters(name = "{0}, custom-source-file:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DifferentParameterCountSingleLineCodeTestRunner(
+      TestParameters parameters, boolean customSourceFile) {
+    this.parameters = parameters;
+    this.customSourceFile = customSourceFile;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASS)
+        .addKeepMainRule(CLASS)
+        // Keep all the methods but allow renaming.
+        .noTreeShaking()
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .addKeepRules("-renamesourcefileattribute " + (customSourceFile ? "X" : "SourceFile"))
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), CLASS)
+        .assertFailureWithErrorThatThrows(IllegalStateException.class)
+        .inspectOriginalStackTrace(
+            s -> {
+              for (StackTraceLine line : s.getStackTraceLines()) {
+                if (customSourceFile) {
+                  // For a custom source file, all debug info must be present.
+                  assertEquals("X", line.fileName);
+                  assertTrue("Expected line number in: " + line, line.hasLineNumber());
+                } else if (vmHasPcSupport()) {
+                  // Single line debug info is stripped. If running with PC support the PC is
+                  // printed.
+                  assertEquals("Unknown Source", line.fileName);
+                  assertTrue("Expected PC in: " + line, line.hasLineNumber());
+                } else {
+                  // Otherwise, just the bare source file is printed.
+                  assertEquals("SourceFile", line.fileName);
+                  assertFalse("Expected no line number in: " + line, line.hasLineNumber());
+                }
+              }
+              assertEquals("Expected 4 stack frames in:\n" + s, 4, s.getStackTraceLines().size());
+            })
+        .inspectStackTrace(
+            retracedStack ->
+                assertThat(
+                    retracedStack,
+                    StackTrace.isSame(
+                        StackTrace.builder()
+                            .add(makeLine("args0", 9))
+                            .add(makeLine("args1", 13))
+                            .add(makeLine("args2", 17))
+                            .add(makeLine("main", 21))
+                            .build())));
+  }
+
+  private boolean vmHasPcSupport() {
+    return parameters
+        .asDexRuntime()
+        .maxSupportedApiLevel()
+        .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+  }
+
+  private StackTraceLine makeLine(String methodName, int lineNumber) {
+    return StackTraceLine.builder()
+        .setClassName(typeName(CLASS))
+        .setFileName(CLASS.getSimpleName() + ".java")
+        .setMethodName(methodName)
+        .setLineNumber(lineNumber)
+        .build();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestSource.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestSource.java
new file mode 100644
index 0000000..4f1ea5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestSource.java
@@ -0,0 +1,23 @@
+// 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.debuginfo.pc2pc;
+
+class DifferentParameterCountSingleLineCodeTestSource {
+
+  public static RuntimeException args0() {
+    throw System.nanoTime() < 0 ? null : new IllegalStateException("DONE!");
+  }
+
+  public static RuntimeException args1(String arg1) {
+    return !arg1.equals("asdf") ? args0() : null;
+  }
+
+  public static RuntimeException args2(String arg1, Object arg2) {
+    return !arg1.equals(arg2) ? args1(arg1) : null;
+  }
+
+  public static void main(String[] args) {
+    throw args2(System.nanoTime() < 0 ? args[0] : "foo", args.length > 0 ? args[0] : "bar");
+  }
+}