Add regression test for behavior of zero-line entries on JVM.

Current ASM version silently strips the zero-valued entries
from inputs.

Bug: b/260389461
Change-Id: I990f625552fb29805da5c6a8d569d37862401dd5
diff --git a/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
new file mode 100644
index 0000000..018c618
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2022, 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 org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.IntBox;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Label;
+
+@RunWith(Parameterized.class)
+public class AsmZeroLineEntryRegressionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultCfRuntime().build();
+  }
+
+  public AsmZeroLineEntryRegressionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  /**
+   * Reference test showing JVM printing of zero-line valued entries in the line number table.
+   *
+   * <p>JVM spec defines line values to be u2 (unsigned 2-byte) values without other restrictions.
+   */
+  @Test
+  public void testReference() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(getClassWithZeroLineEntry())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectOriginalStackTrace(st -> checkLineNumber(st, 0));
+  }
+
+  /**
+   * Regression test for ASM stripping out zero-line entries.
+   *
+   * <p>See b/260389461
+   */
+  @Test
+  public void testAsmIdentity() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(
+            getClassAfterAsmIdentity(
+                getClassWithZeroLineEntry(), Reference.classFromClass(TestClass.class)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        // If this becomes zero ASM has been updated to not assign line zero a special meaning.
+        .inspectOriginalStackTrace(st -> checkLineNumber(st, 1));
+  }
+
+  private void checkLineNumber(StackTrace st, int lineNumber) {
+    assertThat(
+        st,
+        isSame(
+            StackTrace.builder()
+                .add(
+                    StackTraceLine.builder()
+                        .setFileName(getClass().getSimpleName() + ".java")
+                        .setClassName(TestClass.class.getTypeName())
+                        .setMethodName("main")
+                        .setLineNumber(lineNumber)
+                        .build())
+                .build()));
+  }
+
+  private byte[] getClassAfterAsmIdentity(byte[] bytes, ClassReference clazz) {
+    return transformer(bytes, clazz)
+        // Add the identity line transform. If no method transformation is added then ASM will not
+        // interpret the line table and will retain the zero.
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public void visitLineNumber(int line, Label start) {
+                super.visitLineNumber(line, start);
+              }
+            })
+        .transform();
+  }
+
+  private byte[] getClassWithZeroLineEntry() throws IOException {
+    IntBox nextLine = new IntBox(1);
+    return transformer(TestClass.class)
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public void visitLineNumber(int line, Label start) {
+                if (getContext().getReference().getMethodName().equals("main")) {
+                  int newLine = nextLine.get();
+                  if (newLine > 0) {
+                    nextLine.decrement(1);
+                  }
+                  super.visitLineNumber(newLine, start);
+                } else {
+                  super.visitLineNumber(line, start);
+                }
+              }
+            })
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+      throw new RuntimeException("BOO!");
+    }
+  }
+}