Invalid StackMapTable for exception handlers in CF

When re-using same object for calls in try-catch-finally blocks, we
might duplicate the object on the stack before storing in local. If
the exception table block spans over the dup instruction we will get
an error.

See b/122445224 for further info.

Bug: 122445224

Change-Id: If2dbdfdfd8b3f6dff9a63958d291e9f8713dae57
diff --git a/src/test/java/com/android/tools/r8/cf/CloserTest.java b/src/test/java/com/android/tools/r8/cf/CloserTest.java
new file mode 100644
index 0000000..874eb8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CloserTest.java
@@ -0,0 +1,37 @@
+// 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.cf;
+
+import com.android.tools.r8.NeverInline;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class CloserTest {
+
+  @NeverInline
+  public static CloserTest create() {
+    return new CloserTest();
+  }
+
+  OutputStream register() {
+    return System.out;
+  }
+
+  public void doSomething(String message) throws IOException {
+    System.out.println(message);
+  }
+
+  public static void main(String... args) throws IOException {
+    CloserTest closer = CloserTest.create();
+    try {
+      OutputStream out = closer.register();
+      out.write(args[0].getBytes());
+    } catch (Throwable e) {
+      closer.doSomething(e.getMessage());
+    } finally {
+      closer.doSomething("FINISHED");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java b/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java
new file mode 100644
index 0000000..a59ee83
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java
@@ -0,0 +1,55 @@
+// 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.cf;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import org.junit.Test;
+
+/**
+ * This tests that we produce valid code when having normal-flow with exceptional edges in blocks.
+ * We might perform optimizations that add stack-operations (dup, swap, etc.) before and after
+ * instructions that lie on the boundary of the exception table that is generated for a basic block:
+ *
+ * <pre>
+ *   Code:
+ *      0: invokestatic  #16                 // Method create:()Lcom/android/tools/r8/cf/CloserTest;
+ *      3: dup
+ *      4: dup
+ *      5: astore_1
+ *      ...
+ *   Exception table:
+ *      from    to  target type
+ *      3       9   24     Class java/lang/Throwable
+ *      ...
+ *   StackMap table:
+ *   StackMapTable: number_of_entries = 4
+ *         frame_type = 255
+ *         offset_delta=21
+ *         locals=[top, class com/android/tools/r8/cf/CloserTest]
+ *         stack=[class java/lang/Throwable]
+ * </pre>
+ *
+ * If we produce something like this, the JVM verifier will throw a VerifyError on @bci: 3 since we
+ * have not stored CloserTest in locals[1] (that happens in @bci 5), as described in the
+ * StackMapTable. This is because the exception handler starts at @bci 3 and not later.
+ * TODO(b/122445224)
+ */
+public class CloserTestRunner extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    testForR8(Backend.CF)
+        .addProgramClasses(CloserTest.class)
+        .addKeepMainRule(CloserTest.class)
+        .setMode(CompilationMode.RELEASE)
+        .minification(false)
+        .noTreeShaking()
+        .enableInliningAnnotations()
+        .compile()
+        .run(CloserTest.class)
+        .assertFailure();
+  }
+}