Add test for invalid bytecode expecting null in stackmap
Bug: 173598082
Change-Id: I0b322eab037f7e4f73175559d6880438232cb831
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/SwitchStackFrameFallThroughTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/SwitchStackFrameFallThroughTest.java
new file mode 100644
index 0000000..826005c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/SwitchStackFrameFallThroughTest.java
@@ -0,0 +1,181 @@
+// 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.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.CfCodeDiagnostics;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class SwitchStackFrameFallThroughTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public SwitchStackFrameFallThroughTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(SwitchStackFrameFallThroughTest$MainDump.dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("java.io.IOException");
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(SwitchStackFrameFallThroughTest$MainDump.dump())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.testing.readInputStackMaps = true)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertWarningsMatch(diagnosticType(CfCodeDiagnostics.class));
+ });
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Throwable throwable = null;
+ switch (args.length) {
+ case 0:
+ throwable = new IOException();
+ break;
+ case 1:
+ throwable = new RuntimeException();
+ break;
+ }
+ System.out.println(throwable.toString());
+ }
+ }
+
+ // The above is roughly the code that below, except we move the switch targets above the switch
+ // and then jump over them. We do this to check the fall-through of the switch.
+ public static class SwitchStackFrameFallThroughTest$MainDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/cf/stackmap/SwitchStackFrameFallThroughTest$Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitSource("SwitchStackFrameFallThroughTest.java", null);
+
+ classWriter.visitInnerClass(
+ "com/android/tools/r8/cf/stackmap/SwitchStackFrameFallThroughTest$Main",
+ "com/android/tools/r8/cf/stackmap/SwitchStackFrameFallThroughTest",
+ "Main",
+ ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ // This is moved here without a jump.
+
+ Label label0 = new Label();
+ Label label1 = new Label();
+ Label label2 = new Label();
+ Label label3 = new Label();
+
+ methodVisitor.visitJumpInsn(GOTO, label2);
+
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"[Ljava/lang/String;"},
+ 1,
+ new Object[] {Opcodes.NULL});
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitTypeInsn(NEW, "java/io/IOException");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/IOException", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitJumpInsn(GOTO, label3);
+
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"[Ljava/lang/String;"},
+ 1,
+ new Object[] {Opcodes.NULL});
+ methodVisitor.visitInsn(POP);
+ methodVisitor.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitJumpInsn(GOTO, label3);
+
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 0, null);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitInsn(ARRAYLENGTH);
+ methodVisitor.visitLookupSwitchInsn(label1, new int[] {0, 1}, new Label[] {label0, label1});
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 2,
+ new Object[] {"[Ljava/lang/String;", "java/lang/Throwable"},
+ 0,
+ null);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Throwable", "toString", "()Ljava/lang/String;", false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}