Reland "Remove single line debug entries for non-overloaded methods"

This reverts commit e959e8451dac38f6b6ae52039a6f3b3370e05900.

Change-Id: Ib8091cdee364fad443edea15178a8303660c64b1
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
new file mode 100644
index 0000000..32d3126
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
@@ -0,0 +1,20 @@
+// 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.graph;
+
+public class DexDebugInfoForSingleLineMethod extends DexDebugInfo {
+
+  private static final DexDebugInfoForSingleLineMethod INSTANCE =
+      new DexDebugInfoForSingleLineMethod(0, DexString.EMPTY_ARRAY, DexDebugEvent.EMPTY_ARRAY);
+
+  private DexDebugInfoForSingleLineMethod(
+      int startLine, DexString[] parameters, DexDebugEvent[] events) {
+    super(startLine, parameters, events);
+  }
+
+  public static DexDebugInfoForSingleLineMethod getInstance() {
+    return INSTANCE;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
index e17446d..b56ad04 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -21,10 +21,11 @@
  * the current state using the getters after a Default event.
  */
 public class DexDebugPositionState implements DexDebugEventVisitor {
+
   private int currentPc = 0;
   private int currentLine;
   private DexString currentFile = null;
-  private DexMethod currentMethod = null;
+  private DexMethod currentMethod;
   private Position currentCallerPosition = null;
 
   public DexDebugPositionState(int startLine, DexMethod method) {
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 7a70c5a..37b3626 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexDebugEventBuilder;
 import com.android.tools.r8.graph.DexDebugEventVisitor;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
 import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -67,6 +68,8 @@
 
 public class LineNumberOptimizer {
 
+  private static final int MAX_LINE_NUMBER = 65535;
+
   // PositionRemapper is a stateful function which takes a position (represented by a
   // DexDebugPositionState) and returns a remapped Position.
   private interface PositionRemapper {
@@ -373,21 +376,26 @@
 
         for (DexEncodedMethod method : methods) {
           kotlinRemapper.currentMethod = method;
-          List<MappedPosition> mappedPositions = new ArrayList<>();
+          List<MappedPosition> mappedPositions;
           Code code = method.getCode();
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
               if (appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1) {
-                optimizeDexCodePositionsForPc(method, appView, kotlinRemapper, mappedPositions);
+                mappedPositions = optimizeDexCodePositionsForPc(method, appView, kotlinRemapper);
               } else {
-                optimizeDexCodePositions(
-                    method, appView, kotlinRemapper, mappedPositions, identityMapping);
+                mappedPositions =
+                    optimizeDexCodePositions(
+                        method, appView, kotlinRemapper, identityMapping, methods.size() != 1);
               }
             } else if (code.isCfCode()
                 && doesContainPositions(code.asCfCode())
                 && !appView.isCfByteCodePassThrough(method)) {
-              optimizeCfCodePositions(method, kotlinRemapper, mappedPositions, appView);
+              mappedPositions = optimizeCfCodePositions(method, kotlinRemapper, appView);
+            } else {
+              mappedPositions = new ArrayList<>();
             }
+          } else {
+            mappedPositions = new ArrayList<>();
           }
 
           DexMethod originalMethod =
@@ -472,10 +480,20 @@
                 lastPosition = mp;
               }
             }
-            Range obfuscatedRange =
-                new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
             Range originalRange = new Range(firstPosition.originalLine, lastPosition.originalLine);
 
+            Range obfuscatedRange;
+            if (method.getCode().isDexCode()
+                && method.getCode().asDexCode().getDebugInfo()
+                    == DexDebugInfoForSingleLineMethod.getInstance()) {
+              assert firstPosition.originalLine == lastPosition.originalLine;
+              method.getCode().asDexCode().setDebugInfo(null);
+              obfuscatedRange = new Range(0, MAX_LINE_NUMBER);
+            } else {
+              obfuscatedRange =
+                  new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
+            }
+
             ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
             MappedRange lastMappedRange =
                 classNamingBuilder.addMappedRange(
@@ -498,6 +516,11 @@
             }
             i = j;
           }
+          if (method.getCode().isDexCode()
+              && method.getCode().asDexCode().getDebugInfo()
+                  == DexDebugInfoForSingleLineMethod.getInstance()) {
+            method.getCode().asDexCode().setDebugInfo(null);
+          }
         } // for each method of the group
       } // for each method group, grouped by name
     } // for each class
@@ -667,14 +690,15 @@
     return false;
   }
 
-  private static void optimizeDexCodePositions(
+  private static List<MappedPosition> optimizeDexCodePositions(
       DexEncodedMethod method,
       AppView<?> appView,
       PositionRemapper positionRemapper,
-      List<MappedPosition> mappedPositions,
-      boolean identityMapping) {
+      boolean identityMapping,
+      boolean hasOverloads) {
+    List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
-    final DexApplication application = appView.appInfo().app();
+    DexApplication application = appView.appInfo().app();
     DexCode dexCode = method.getCode().asDexCode();
     DexDebugInfo debugInfo = dexCode.getDebugInfo();
     List<DexDebugEvent> processedEvents = new ArrayList<>();
@@ -688,7 +712,7 @@
     Box<Boolean> inlinedOriginalPosition = new Box<>(false);
 
     // Debug event visitor to map line numbers.
-    DexDebugEventVisitor visitor =
+    DexDebugPositionState visitor =
         new DexDebugPositionState(
             debugInfo.startLine,
             appView.graphLens().getOriginalMethodSignature(method.getReference())) {
@@ -768,6 +792,15 @@
       event.accept(visitor);
     }
 
+    // If we only have one line event we can always retrace back uniquely.
+    if (mappedPositions.size() <= 1
+        && !hasOverloads
+        && !appView.options().debug
+        && appView.options().lineNumberOptimization != LineNumberOptimization.OFF) {
+      dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
+      return mappedPositions;
+    }
+
     DexDebugInfo optimizedDebugInfo =
         new DexDebugInfo(
             positionEventEmitter.getStartLine(),
@@ -779,13 +812,12 @@
         || verifyIdentityMapping(debugInfo, optimizedDebugInfo);
 
     dexCode.setDebugInfo(optimizedDebugInfo);
+    return mappedPositions;
   }
 
-  private static void optimizeDexCodePositionsForPc(
-      DexEncodedMethod method,
-      AppView<?> appView,
-      PositionRemapper positionRemapper,
-      List<MappedPosition> mappedPositions) {
+  private static List<MappedPosition> optimizeDexCodePositionsForPc(
+      DexEncodedMethod method, AppView<?> appView, PositionRemapper positionRemapper) {
+    List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexCode dexCode = method.getCode().asDexCode();
     DexDebugInfo debugInfo = dexCode.getDebugInfo();
@@ -834,6 +866,7 @@
     }
 
     dexCode.setDebugInfo(null);
+    return mappedPositions;
   }
 
   private static boolean verifyIdentityMapping(
@@ -846,11 +879,9 @@
     return true;
   }
 
-  private static void optimizeCfCodePositions(
-      DexEncodedMethod method,
-      PositionRemapper positionRemapper,
-      List<MappedPosition> mappedPositions,
-      AppView<?> appView) {
+  private static List<MappedPosition> optimizeCfCodePositions(
+      DexEncodedMethod method, PositionRemapper positionRemapper, AppView<?> appView) {
+    List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     CfCode oldCode = method.getCode().asCfCode();
     List<CfInstruction> oldInstructions = oldCode.getInstructions();
@@ -877,6 +908,7 @@
             oldCode.getTryCatchRanges(),
             oldCode.getLocalVariables()),
         appView);
+    return mappedPositions;
   }
 
   private static Position remapAndAdd(
diff --git a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
index 785db86..18ae157 100644
--- a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
-import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.FrameInspector;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -40,7 +39,7 @@
                   .noMinification()
                   .addKeepRules("-keepattributes SourceFile,LineNumberTable")
                   .addProgramClasses(CLASS)
-                  .setMode(CompilationMode.DEBUG)
+                  .debug()
                   .debugConfig());
     }
     return parameters.build();
diff --git a/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java b/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java
index 9771a28..c206daa 100644
--- a/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/R8DebugNonMinifiedProgramTestRunner.java
@@ -6,7 +6,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
@@ -37,22 +36,11 @@
   private static BiFunction<Backend, AndroidApiLevel, R8TestCompileResult> compiledDebug
       = memoizeBiFunction(R8DebugNonMinifiedProgramTestRunner::compileDebug);
 
-  private static BiFunction<Backend, AndroidApiLevel, R8TestCompileResult> compiledNoOptNoMinify
-      = memoizeBiFunction(R8DebugNonMinifiedProgramTestRunner::compileNoOptNoMinify);
-
   private static R8TestCompileResult compileDebug(Backend backend, AndroidApiLevel apiLevel)
       throws Exception {
     return compile(testForR8(getStaticTemp(), backend).debug(), apiLevel);
   }
 
-  private static R8TestCompileResult compileNoOptNoMinify(Backend backend, AndroidApiLevel apiLevel)
-      throws Exception {
-    return compile(
-        testForR8(getStaticTemp(), backend)
-            .addKeepRules("-dontoptimize", "-dontobfuscate", "-keepattributes LineNumberTable"),
-        apiLevel);
-  }
-
   private static R8TestCompileResult compile(R8FullTestBuilder builder, AndroidApiLevel apiLevel)
       throws Exception {
     return builder
@@ -70,24 +58,11 @@
             });
   }
 
-  private void assumeMappingIsNotToPCs() {
-    assumeTrue(
-        "Ignoring test when the line number table is removed.",
-        parameters.isCfRuntime()
-            || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport()));
-  }
-
   @Test
   public void testDebugMode() throws Throwable {
     runTest(compiledDebug.apply(parameters.getBackend(), parameters.getApiLevel()));
   }
 
-  @Test
-  public void testNoOptimizationAndNoMinification() throws Throwable {
-    assumeMappingIsNotToPCs();
-    runTest(compiledNoOptNoMinify.apply(parameters.getBackend(), parameters.getApiLevel()));
-  }
-
   private void runTest(R8TestCompileResult compileResult) throws Throwable {
     compileResult
         .run(parameters.getRuntime(), CLASS)
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 73aff7f..48b9fc8 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
@@ -66,7 +66,7 @@
     result.app.write(classesPath, OutputMode.DexIndexed);
     int numberOfDebugInfos =
         getNumberOfDebugInfos(Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
-    Assert.assertEquals(1, numberOfDebugInfos);
+    Assert.assertEquals(0, numberOfDebugInfos);
   }
 
   // Two classes which has debug info that looks exactly the same, except for SetInlineFrame.
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 17dadf2..4898ef3 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -140,7 +140,6 @@
     int expectedLineNumber = throwLocation == Location.FOO2 ? 2 : 1;
     String expectedFilePos = TEST_CLASS + ".java:" + expectedLineNumber;
     int idx = line.indexOf(expectedFilePos);
-    assertTrue(idx >= 0);
 
     // And the next character must be a non-digit or nothing.
     int idxAfter = idx + expectedFilePos.length();
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java
new file mode 100644
index 0000000..dd9bcc7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.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;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasLineNumberTable;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.utils.codeinspector.ClassSubject;
+import org.junit.Before;
+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 SingleLineInfoInlineRemoveTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SingleLineInfoInlineRemoveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Main.class)
+            .assertFailureWithErrorThatThrows(NullPointerException.class)
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              assertThat(stackTrace, isSame(expectedStackTrace));
+              ClassSubject mainSubject = inspector.clazz(Main.class);
+              assertThat(mainSubject, isPresent());
+              assertThat(mainSubject.uniqueMethodWithName("inlinee"), not(isPresent()));
+              assertThat(
+                  mainSubject.uniqueMethodWithName("shouldRemoveLineNumberForInline"),
+                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+            });
+  }
+
+  public static class Main {
+
+    @NeverInline
+    public static void printOrThrow(String message) {
+      if (System.currentTimeMillis() > 0) {
+        throw new NullPointerException(message);
+      }
+      System.out.println(message);
+    }
+
+    public static void inlinee() {
+      printOrThrow("Hello from inlinee");
+    }
+
+    @NeverInline
+    public static void shouldRemoveLineNumberForInline() {
+      inlinee();
+    }
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        shouldRemoveLineNumberForInline();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
new file mode 100644
index 0000000..d7a5df3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
@@ -0,0 +1,117 @@
+// 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;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasLineNumberTable;
+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 com.android.tools.r8.NeverClassInline;
+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.utils.codeinspector.ClassSubject;
+import org.junit.Before;
+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 SingleLineInfoMultipleCallsRemoveTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SingleLineInfoMultipleCallsRemoveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Main.class)
+            .assertFailureWithErrorThatThrows(NullPointerException.class)
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              assertThat(stackTrace, isSame(expectedStackTrace));
+              assertThat(inspector.clazz(Builder.class), isPresent());
+              ClassSubject mainSubject = inspector.clazz(Main.class);
+              assertThat(mainSubject, isPresent());
+              assertThat(
+                  mainSubject.uniqueMethodWithName("shouldRemoveLineNumberForMultipleInvokes"),
+                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+              assertThat(
+                  mainSubject.uniqueMethodWithName("main"),
+                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+            });
+  }
+
+  @NeverClassInline
+  public static class Builder {
+
+    StringBuilder sb = new StringBuilder();
+
+    @NeverInline
+    public Builder add(String str) {
+      sb.append(str);
+      return this;
+    }
+
+    @NeverInline
+    public String build() {
+      return sb.toString();
+    }
+  }
+
+  public static class Main {
+
+    @NeverInline
+    public static void printOrThrow(String message) {
+      if (System.currentTimeMillis() > 0) {
+        throw new NullPointerException(message);
+      }
+      System.out.println(message);
+    }
+
+    @NeverInline
+    public static void shouldRemoveLineNumberForMultipleInvokes() {
+      printOrThrow(new Builder().add("foo").add("bar").add("baz").build());
+    }
+
+    public static void main(String[] args) {
+      shouldRemoveLineNumberForMultipleInvokes();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleInlineTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleInlineTest.java
new file mode 100644
index 0000000..bc0d012
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleInlineTest.java
@@ -0,0 +1,112 @@
+// 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;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasLineNumberTable;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.utils.codeinspector.ClassSubject;
+import org.junit.Before;
+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 SingleLineInfoMultipleInlineTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SingleLineInfoMultipleInlineTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Main.class)
+            .assertFailureWithErrorThatThrows(NullPointerException.class)
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              assertThat(stackTrace, isSame(expectedStackTrace));
+              ClassSubject mainSubject = inspector.clazz(Main.class);
+              assertThat(mainSubject, isPresent());
+              assertThat(mainSubject.uniqueMethodWithName("inlinee"), not(isPresent()));
+              assertThat(
+                  mainSubject.uniqueMethodWithName("shouldNotRemoveLineNumberForInline"),
+                  notIf(
+                      hasLineNumberTable(),
+                      parameters.isDexRuntime()
+                          && parameters
+                              .getApiLevel()
+                              .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport())));
+            });
+  }
+
+  public static class Main {
+
+    @NeverInline
+    public static void printOrThrow(String message) {
+      if (System.currentTimeMillis() > 0) {
+        throw new NullPointerException(message);
+      }
+      System.out.println(message);
+    }
+
+    public static void inlinee() {
+      printOrThrow("Hello from inlinee");
+    }
+
+    public static void inlinee2() {
+      printOrThrow("Hello from inlinee2");
+    }
+
+    @NeverInline
+    public static void shouldNotRemoveLineNumberForInline() {
+      inlinee();
+      inlinee2();
+    }
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        shouldNotRemoveLineNumberForInline();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
new file mode 100644
index 0000000..652f401
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
@@ -0,0 +1,94 @@
+// 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;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasLineNumberTable;
+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 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.utils.codeinspector.ClassSubject;
+import org.junit.Before;
+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 SingleLineInfoRemoveTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SingleLineInfoRemoveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Main.class)
+            .assertFailure()
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              assertThat(stackTrace, isSame(expectedStackTrace));
+              ClassSubject mainSubject = inspector.clazz(Main.class);
+              assertThat(mainSubject, isPresent());
+              assertThat(
+                  mainSubject.uniqueMethodWithName("shouldRemoveLineNumber"),
+                  notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+            });
+  }
+
+  public static class Main {
+
+    @NeverInline
+    public static void printOrThrow(String message) {
+      if (System.currentTimeMillis() > 0) {
+        throw new NullPointerException(message);
+      }
+      System.out.println(message);
+    }
+
+    @NeverInline
+    public static void shouldRemoveLineNumber() {
+      printOrThrow("Hello World");
+    }
+
+    public static void main(String[] args) {
+      shouldRemoveLineNumber();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
deleted file mode 100644
index 56f306c..0000000
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2019, 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.naming.retrace;
-
-import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileName;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestBuilder;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.google.common.collect.ImmutableList;
-import java.util.Collection;
-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 DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest extends RetraceTestBase {
-
-  @Parameters(name = "{0}, mode: {1}, compat: {2}")
-  public static Collection<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(),
-        CompilationMode.values(),
-        BooleanUtils.values());
-  }
-
-  public DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest(
-      TestParameters parameters, CompilationMode mode, boolean compat) {
-    super(parameters, mode, compat);
-  }
-
-  @Override
-  public void configure(R8TestBuilder<?> builder) {
-    builder.enableInliningAnnotations();
-  }
-
-  @Override
-  public Collection<Class<?>> getClasses() {
-    return ImmutableList.of(
-        getMainClass(), InterfaceWithStaticMethod1.class, InterfaceWithStaticMethod2.class);
-  }
-
-  @Override
-  public Class<?> getMainClass() {
-    return MainDesugarStaticInterfaceMethodsRetraceTest.class;
-  }
-
-  @Test
-  public void testSourceFileAndLineNumberTable() throws Exception {
-    runTest(
-        ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
-        // Companion methods are treated as having inlined the interface method code.
-        // If compiling with synthetic marking support in the mapping file, the synthetic frames
-        // are removed and the trace will be equal to RI.
-        (StackTrace actualStackTrace, StackTrace retracedStackTrace) ->
-            assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace)));
-  }
-}
-
-interface InterfaceWithStaticMethod2 {
-
-  static void staticMethod2() {
-    throw null;
-  }
-}
-
-interface InterfaceWithStaticMethod1 {
-
-  @NeverInline
-  static void staticMethod1() {
-    InterfaceWithStaticMethod2.staticMethod2();
-  }
-}
-
-class MainDesugarStaticInterfaceMethodsRetraceTest {
-
-  public static void main(String[] args) {
-    InterfaceWithStaticMethod1.staticMethod1();
-  }
-}
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 7a78ed0..6e83620 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
@@ -567,6 +567,10 @@
     };
   }
 
+  public static Matcher<MethodSubject> hasLineNumberTable() {
+    return isMethodSatisfying("line number table", method -> method.getLineNumberTable() != null);
+  }
+
   public static Matcher<RetraceFrameResult> isInlineFrame() {
     return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override