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!");
+ }
+ }
+}