summaryrefslogtreecommitdiff
path: root/asm-commons/src/test/java/org/objectweb/asm/commons/JsrInlinerAdapterTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'asm-commons/src/test/java/org/objectweb/asm/commons/JsrInlinerAdapterTest.java')
-rw-r--r--asm-commons/src/test/java/org/objectweb/asm/commons/JsrInlinerAdapterTest.java1539
1 files changed, 1539 insertions, 0 deletions
diff --git a/asm-commons/src/test/java/org/objectweb/asm/commons/JsrInlinerAdapterTest.java b/asm-commons/src/test/java/org/objectweb/asm/commons/JsrInlinerAdapterTest.java
new file mode 100644
index 00000000..a96eff5e
--- /dev/null
+++ b/asm-commons/src/test/java/org/objectweb/asm/commons/JsrInlinerAdapterTest.java
@@ -0,0 +1,1539 @@
+// ASM: a very sm14all and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package org.objectweb.asm.commons;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.objectweb.asm.commons.MethodNodeBuilder.buildClassWithMethod;
+import static org.objectweb.asm.commons.MethodNodeBuilder.toText;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.test.AsmTest;
+import org.objectweb.asm.test.ClassFile;
+import org.objectweb.asm.tree.MethodNode;
+
+/**
+ * Unit tests for {@link JSRInlinerAdapter}.
+ *
+ * @author Eric Bruneton
+ */
+class JsrInlinerAdapterTest extends AsmTest {
+
+ // Some local variable numbers used in tests.
+ private static final int LOCAL1 = 1;
+ private static final int LOCAL2 = 2;
+ private static final int LOCAL3 = 3;
+ private static final int LOCAL4 = 4;
+ private static final int LOCAL5 = 5;
+
+ // Labels used to generate test cases.
+ private final Label label0 = new Label();
+ private final Label label1 = new Label();
+ private final Label label2 = new Label();
+ private final Label label3 = new Label();
+ private final Label label4 = new Label();
+ private final Label label5 = new Label();
+ private final Label label6 = new Label();
+ private final Label label7 = new Label();
+ private final Label label8 = new Label();
+ private final Label label9 = new Label();
+ private final Label label10 = new Label();
+ private final Label label11 = new Label();
+ private final Label label12 = new Label();
+
+ // Labels used to generate expected results.
+ private final Label expectedLabel0 = new Label();
+ private final Label expectedLabel1 = new Label();
+ private final Label expectedLabel2 = new Label();
+ private final Label expectedLabel3 = new Label();
+ private final Label expectedLabel4 = new Label();
+ private final Label expectedLabel5 = new Label();
+ private final Label expectedLabel6 = new Label();
+ private final Label expectedLabel7 = new Label();
+ private final Label expectedLabel8 = new Label();
+ private final Label expectedLabel9 = new Label();
+ private final Label expectedLabel10 = new Label();
+ private final Label expectedLabel11 = new Label();
+ private final Label expectedLabel12 = new Label();
+ private final Label expectedLabel13 = new Label();
+ private final Label expectedLabel14 = new Label();
+ private final Label expectedLabel15 = new Label();
+ private final Label expectedLabel16 = new Label();
+ private final Label expectedLabel17 = new Label();
+ private final Label expectedLabel18 = new Label();
+ private final Label expectedLabel19 = new Label();
+ private final Label expectedLabel20 = new Label();
+ private final Label expectedLabel21 = new Label();
+ private final Label expectedLabel22 = new Label();
+ private final Label expectedLabel23 = new Label();
+ private final Label expectedLabel24 = new Label();
+ private final Label expectedLabel25 = new Label();
+ private final Label expectedLabel26 = new Label();
+ private final Label expectedLabel27 = new Label();
+ private final Label expectedLabel28 = new Label();
+
+ @Test
+ void testConstructor() {
+ new JSRInlinerAdapter(null, Opcodes.ACC_PUBLIC, "name", "()V", null, null);
+ assertThrows(
+ IllegalStateException.class,
+ () -> new JSRInlinerAdapter(null, Opcodes.ACC_PUBLIC, "name", "()V", null, null) {});
+ }
+
+ /**
+ * Tests a method which has the most basic <code>try{}finally</code> form imaginable. More
+ * specifically:
+ *
+ * <pre>
+ * public void a() {
+ * int a = 0;
+ * try {
+ * a++;
+ * } finally {
+ * a--;
+ * }
+ * }
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_basicTryFinally() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // Body of try block.
+ .iinc(1, 1)
+ .go(label3)
+ .label(label1) // Exception handler.
+ .astore(3)
+ .jsr(label2)
+ .aload(3)
+ .athrow()
+ .label(label2) // Subroutine.
+ .astore(2)
+ .iinc(1, -1)
+ .ret(2)
+ .label(label3) // Non-exceptional exit from try block.
+ .jsr(label2)
+ .label(label4)
+ .vreturn()
+ .trycatch(label0, label1, label1)
+ .trycatch(label3, label4, label1)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // Try/catch block.
+ .iinc(1, 1)
+ .go(expectedLabel3)
+ .label(expectedLabel1) // Exception handler.
+ .astore(3)
+ .aconst_null()
+ .go(expectedLabel6)
+ .label(expectedLabel2)
+ .aload(3)
+ .athrow()
+ .label(expectedLabel3) // On non-exceptional exit, try block leads here.
+ .aconst_null()
+ .go(expectedLabel7)
+ .label(expectedLabel4)
+ .label(expectedLabel5)
+ .vreturn()
+ .label(expectedLabel6) // First instantiation of subroutine.
+ .astore(2)
+ .iinc(1, -1)
+ .go(expectedLabel2)
+ .label()
+ .label(expectedLabel7) // Second instantiation of subroutine.
+ .astore(2)
+ .iinc(1, -1)
+ .go(expectedLabel4)
+ .label()
+ .trycatch(expectedLabel0, expectedLabel1, expectedLabel1)
+ .trycatch(expectedLabel3, expectedLabel5, expectedLabel1)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> MethodNodeBuilder.buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * Tests a method which has an if/else in the finally clause. More specifically:
+ *
+ * <pre>
+ * public void a() {
+ * int a = 0;
+ * try {
+ * a++;
+ * } finally {
+ * if (a == 0) {
+ * a += 2;
+ * } else {
+ * a += 3;
+ * }
+ * }
+ * }
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_ifElseInFinally() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // Body of try block.
+ .iinc(1, 1)
+ .go(label5)
+ .label(label1) // Exception handler.
+ .astore(3)
+ .jsr(label2)
+ .aload(3)
+ .athrow()
+ .label(label2) // Subroutine.
+ .astore(2)
+ .iload(1)
+ .ifne(label3)
+ .iinc(1, 2)
+ .go(label4)
+ .label(label3) // Test "a != 0".
+ .iinc(1, 3)
+ .label(label4) // Common exit.
+ .ret(2)
+ .label(label5) // Non-exceptional exit from try block.
+ .jsr(label2)
+ .label(label6) // Used in the TRYCATCH below.
+ .vreturn()
+ .trycatch(label0, label1, label1)
+ .trycatch(label5, label6, label1)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // Try/catch block.
+ .iinc(1, 1)
+ .go(expectedLabel3)
+ .label(expectedLabel1) // Exception handler.
+ .astore(3)
+ .aconst_null()
+ .go(expectedLabel6)
+ .label(expectedLabel2)
+ .aload(3)
+ .athrow()
+ .label(expectedLabel3) // On non-exceptional exit, try block leads here.
+ .aconst_null()
+ .go(expectedLabel9)
+ .label(expectedLabel4)
+ .label(expectedLabel5)
+ .vreturn()
+ .label(expectedLabel6) // First instantiation of subroutine.
+ .astore(2)
+ .iload(1)
+ .ifne(expectedLabel7)
+ .iinc(1, 2)
+ .go(expectedLabel8)
+ .label(expectedLabel7) // Test "a != 0".
+ .iinc(1, 3)
+ .label(expectedLabel8) // Common exit.
+ .go(expectedLabel2)
+ .label()
+ .label(expectedLabel9) // Second instantiation of subroutine.
+ .astore(2)
+ .iload(1)
+ .ifne(expectedLabel10)
+ .iinc(1, 2)
+ .go(expectedLabel11)
+ .label(expectedLabel10) // Test "a != 0".
+ .iinc(1, 3)
+ .label(expectedLabel11) // Common exit.
+ .go(expectedLabel4)
+ .label()
+ .trycatch(expectedLabel0, expectedLabel1, expectedLabel1)
+ .trycatch(expectedLabel3, expectedLabel5, expectedLabel1)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * Tests a method which has a lookupswitch or tableswitch w/in the finally clause. More
+ * specifically:
+ *
+ * <pre>
+ * public void a() {
+ * int a = 0;
+ * try {
+ * a++;
+ * } finally {
+ * switch (a) {
+ * case 0:
+ * a += 2;
+ * break;
+ * default:
+ * a += 3;
+ * }
+ * }
+ * }
+ * </pre>
+ */
+ @ParameterizedTest
+ @ValueSource(strings = {"true", "false"})
+ void testInlineJsr_lookupOrTableSwitchInFinally(final boolean useTableSwitch) {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // Body of try block.
+ .iinc(1, 1)
+ .go(label6)
+ .label(label1) // Exception handler.
+ .astore(3)
+ .jsr(label2)
+ .aload(3)
+ .athrow()
+ .label(label2) // Subroutine.
+ .astore(2)
+ .iload(1)
+ .switchto(label4, 0, label3, useTableSwitch)
+ .label(label3) // First switch case.
+ .iinc(1, 2)
+ .go(label5)
+ .label(label4) // Default switch case.
+ .iinc(1, 3)
+ .label(label5) // Common exit.
+ .ret(2)
+ .label(label6) // Non-exceptional exit from try block.
+ .jsr(label2)
+ .label(label7)
+ .vreturn()
+ .trycatch(label0, label1, label1)
+ .trycatch(label6, label7, label1)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // Try/catch block.
+ .iinc(1, 1)
+ .go(expectedLabel3)
+ .label(expectedLabel1) // Exception handler.
+ .astore(3)
+ .aconst_null()
+ .go(expectedLabel6)
+ .label(expectedLabel2)
+ .aload(3)
+ .athrow()
+ .label(expectedLabel3) // On non-exceptional exit, try block leads here.
+ .aconst_null()
+ .go(expectedLabel10)
+ .label(expectedLabel4)
+ .label(expectedLabel5)
+ .vreturn()
+ .label(expectedLabel6) // First instantiation of subroutine.
+ .astore(2)
+ .iload(1)
+ .switchto(expectedLabel8, 0, expectedLabel7, useTableSwitch)
+ .label(expectedLabel7) // First switch case.
+ .iinc(1, 2)
+ .go(expectedLabel9)
+ .label(expectedLabel8) // Default switch case.
+ .iinc(1, 3)
+ .label(expectedLabel9) // Common exit.
+ .go(expectedLabel2)
+ .label()
+ .label(expectedLabel10) // Second instantiation of subroutine.
+ .astore(2)
+ .iload(1)
+ .switchto(expectedLabel12, 0, expectedLabel11, useTableSwitch)
+ .label(expectedLabel11) // First switch case.
+ .iinc(1, 2)
+ .go(expectedLabel13)
+ .label(expectedLabel12) // Default switch case.
+ .iinc(1, 3)
+ .label(expectedLabel13) // Common exit.
+ .go(expectedLabel4)
+ .label()
+ .trycatch(expectedLabel0, expectedLabel1, expectedLabel1)
+ .trycatch(expectedLabel3, expectedLabel5, expectedLabel1)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * Tests a simple nested finally. More specifically:
+ *
+ * <pre>
+ * public void a1() {
+ * int a = 0;
+ * try {
+ * a += 1;
+ * } finally {
+ * try {
+ * a += 2;
+ * } finally {
+ * a += 3;
+ * }
+ * }
+ * }
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_simpleNestedFinally() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(2, 6)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // Body of try block.
+ .iinc(1, 1)
+ .jsr(label2)
+ .go(label5)
+ .label(label1) // First exception handler.
+ .jsr(label2)
+ .athrow()
+ .label(label2) // First subroutine.
+ .astore(2)
+ .iinc(1, 2)
+ .jsr(label4)
+ .ret(2)
+ .label(label3) // Second exception handler.
+ .jsr(label4)
+ .athrow()
+ .label(label4) // Second subroutine.
+ .astore(3)
+ .iinc(1, 3)
+ .ret(3)
+ .label(label5) // On normal exit, try block jumps here.
+ .vreturn()
+ .trycatch(label0, label1, label1)
+ .trycatch(label2, label3, label3)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(2, 6)
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // Body of try block.
+ .iinc(1, 1)
+ .aconst_null()
+ .go(expectedLabel5)
+ .label(expectedLabel1)
+ .go(expectedLabel4)
+ .label(expectedLabel2) // First exception handler.
+ .aconst_null()
+ .go(expectedLabel9)
+ .label(expectedLabel3)
+ .athrow()
+ .label(expectedLabel4) // On normal exit, try block jumps here.
+ .vreturn()
+ .label(expectedLabel5) // First instantiation of first subroutine.
+ .astore(2)
+ .iinc(1, 2)
+ .aconst_null()
+ .go(expectedLabel13)
+ .label(expectedLabel6)
+ .go(expectedLabel1)
+ .label(expectedLabel7)
+ .aconst_null()
+ .go(expectedLabel14)
+ .label(expectedLabel8)
+ .athrow()
+ .label()
+ .label(expectedLabel9) // Second instantiation of first subroutine.
+ .astore(2)
+ .iinc(1, 2)
+ .aconst_null()
+ .go(expectedLabel15)
+ .label(expectedLabel10)
+ .go(expectedLabel3)
+ .label(expectedLabel11)
+ .aconst_null()
+ .go(expectedLabel16)
+ .label(expectedLabel12)
+ .athrow()
+ .label()
+ .label(expectedLabel13) // First instantiation of second subroutine.
+ .astore(3)
+ .iinc(1, 3)
+ .go(expectedLabel6)
+ .label()
+ .label(expectedLabel14) // Second instantiation of second subroutine.
+ .astore(3)
+ .iinc(1, 3)
+ .go(expectedLabel8)
+ .label()
+ .label(expectedLabel15) // Third instantiation of second subroutine.
+ .astore(3)
+ .iinc(1, 3)
+ .go(expectedLabel10)
+ .label()
+ .label(expectedLabel16) // Fourth instantiation of second subroutine.
+ .astore(3)
+ .iinc(1, 3)
+ .go(expectedLabel12)
+ .label()
+ .trycatch(expectedLabel0, expectedLabel2, expectedLabel2)
+ .trycatch(expectedLabel5, expectedLabel7, expectedLabel7)
+ .trycatch(expectedLabel9, expectedLabel11, expectedLabel11)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * This tests a subroutine which has no ret statement, but ends in a "return" instead.
+ *
+ * <p>We structure this as a try/finally with a break in the finally. Because the while loop is
+ * infinite, it's clear from the byte code that the only path which reaches the RETURN instruction
+ * is through the subroutine.
+ *
+ * <pre>
+ * public void a1() {
+ * int a = 0;
+ * while (true) {
+ * try {
+ * a += 1;
+ * } finally {
+ * a += 2;
+ * break;
+ * }
+ * }
+ * }
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_subroutineWithNoRet() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // While loop header/try block.
+ .iinc(1, 1)
+ .jsr(label2)
+ .go(label3)
+ .label(label1) // Implicit catch block.
+ .astore(2)
+ .jsr(label2)
+ .aload(2)
+ .athrow()
+ .label(label2) // Subroutine ...
+ .astore(3)
+ .iinc(1, 2)
+ .go(label4) // ... note that it does not return!
+ .label(label3) // End of the loop... goes back to the top!
+ .go(label0)
+ .label(label4)
+ .vreturn()
+ .trycatch(label0, label1, label1)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // While loop header/try block.
+ .iinc(1, 1)
+ .aconst_null()
+ .go(expectedLabel5)
+ .label(expectedLabel1)
+ .go(expectedLabel4)
+ .label(expectedLabel2) // Implicit catch block.
+ .astore(2)
+ .aconst_null()
+ .go(expectedLabel7)
+ .label(expectedLabel3)
+ .aload(2)
+ .athrow()
+ .label(expectedLabel4) // End of the loop... goes back to the top!
+ .go(expectedLabel0)
+ .label()
+ .label(expectedLabel5) // First instantiation of subroutine ...
+ .astore(3)
+ .iinc(1, 2)
+ .go(expectedLabel6) // ... note that it does not return!
+ .label(expectedLabel6)
+ .vreturn()
+ .label(expectedLabel7) // Second instantiation of subroutine ...
+ .astore(3)
+ .iinc(1, 2)
+ .go(expectedLabel8) // ... note that it does not return!
+ .label(expectedLabel8)
+ .vreturn()
+ .trycatch(expectedLabel0, expectedLabel2, expectedLabel2)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * This tests a subroutine which has no ret statement, but ends in a "return" instead. The code
+ * after the JSR appears to fall through the end of the method, but is in fact unreachable and
+ * therefore valid.
+ *
+ * <pre>
+ * JSR L0
+ * GOTO L1
+ * L0:
+ * ASTORE 0
+ * RETURN
+ * L1:
+ * ACONST_NULL
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_subroutineWithNoRet2() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 1)
+ .jsr(label0)
+ .go(label1)
+ .label(label0)
+ .astore(0)
+ .vreturn()
+ .label(label1)
+ .aconst_null()
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 1)
+ .aconst_null()
+ .go(expectedLabel2)
+ .label(expectedLabel0)
+ .go(expectedLabel1)
+ .label(expectedLabel1)
+ .aconst_null()
+ .label(expectedLabel2) // First instantiation of subroutine.
+ .astore(0)
+ .vreturn()
+ .label()
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * This tests a subroutine which has no ret statement, but instead exits implicitly by branching
+ * to code which is not part of the subroutine. (Sadly, this is legal)
+ *
+ * <p>We structure this as a try/finally in a loop with a break in the finally. The loop is not
+ * trivially infinite, so the RETURN statement is reachable both from the JSR subroutine and from
+ * the main entry point.
+ *
+ * <pre>
+ * public void a1() {
+ * int a = 0;
+ * while (null == null) {
+ * try {
+ * a += 1;
+ * } finally {
+ * a += 2;
+ * break;
+ * }
+ * }
+ * }
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_implicitExit() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // While loop header.
+ .aconst_null()
+ .ifnonnull(label5)
+ .label(label1) // Try block.
+ .iinc(1, 1)
+ .jsr(label3)
+ .go(label4)
+ .label(label2) // Implicit catch block.
+ .astore(2)
+ .jsr(label3)
+ .aload(2)
+ .athrow()
+ .label(label3) // Subroutine ...
+ .astore(3)
+ .iinc(1, 2)
+ .go(label5) // ... note that it does not return!
+ .label(label4) // End of the loop... goes back to the top!
+ .go(label1)
+ .label(label5)
+ .vreturn()
+ .trycatch(label1, label2, label2)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 4)
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // While loop header.
+ .aconst_null()
+ .ifnonnull(expectedLabel6)
+ .label(expectedLabel1) // While loop header/try block.
+ .iinc(1, 1)
+ .aconst_null()
+ .go(expectedLabel7)
+ .label(expectedLabel2)
+ .go(expectedLabel5)
+ .label(expectedLabel3) // Implicit catch block.
+ .astore(2)
+ .aconst_null()
+ .go(expectedLabel8)
+ .label(expectedLabel4)
+ .aload(2)
+ .athrow()
+ .label(expectedLabel5) // End of the loop... goes back to the top!
+ .go(expectedLabel1)
+ .label(expectedLabel6) // Exit, not part of subroutine.
+ // Note that the two subroutine instantiations branch here.
+ .vreturn()
+ .label(expectedLabel7) // First instantiation of subroutine ...
+ .astore(3)
+ .iinc(1, 2)
+ .go(expectedLabel6) // ... note that it does not return!
+ .label()
+ .label(expectedLabel8) // Second instantiation of subroutine ...
+ .astore(3)
+ .iinc(1, 2)
+ .go(expectedLabel6) // ... note that it does not return!
+ .label()
+ .trycatch(expectedLabel1, expectedLabel3, expectedLabel3)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * Tests a nested try/finally with implicit exit from one subroutine to the other subroutine.
+ * Equivalent to the following java code:
+ *
+ * <pre>
+ * void m(boolean b) {
+ * try {
+ * return;
+ * } finally {
+ * while (b) {
+ * try {
+ * return;
+ * } finally {
+ * // NOTE --- this break avoids the second return above (weird)
+ * if (b) {
+ * break;
+ * }
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * <p>This example is from the paper, "Subroutine Inlining and Bytecode Abstraction to Simplify
+ * Static and Dynamic Analysis" by Cyrille Artho and Armin Biere.
+ */
+ @Test
+ void testInlineJsr_implicitExitToAnotherSubroutine() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 6)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // First try.
+ .jsr(label2)
+ .vreturn()
+ .label(label1) // Exception handler for first try.
+ .astore(LOCAL2)
+ .jsr(label2)
+ .aload(LOCAL2)
+ .athrow()
+ .label(label2) // First finally handler.
+ .astore(LOCAL4)
+ .go(label6)
+ .label(label3) // Body of while loop, also second try.
+ .jsr(label5)
+ .vreturn()
+ .label(label4) // Exception handler for second try.
+ .astore(LOCAL3)
+ .jsr(label5)
+ .aload(LOCAL3)
+ .athrow()
+ .label(label5) // Second finally handler.
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(label7)
+ .ret(LOCAL5)
+ .label(label6) // Test for the while loop.
+ .iload(LOCAL1)
+ .ifne(label3) // Falls through.
+ .label(label7) // Exit from finally block.
+ .ret(LOCAL4)
+ .trycatch(label0, label1, label1)
+ .trycatch(label3, label4, label4)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 6)
+ // --- Main Subroutine ---
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // First try.
+ .aconst_null()
+ .go(expectedLabel4)
+ .label(expectedLabel1)
+ .vreturn()
+ .label(expectedLabel2) // Exception handler for first try.
+ .astore(LOCAL2)
+ .aconst_null()
+ .go(expectedLabel11)
+ .label(expectedLabel3)
+ .aload(LOCAL2)
+ .athrow()
+ .label()
+ // --- First instantiation of first subroutine ---
+ .label(expectedLabel4) // First finally handler.
+ .astore(LOCAL4)
+ .go(expectedLabel9)
+ .label(expectedLabel5) // Body of while loop, also second try.
+ .aconst_null()
+ .go(expectedLabel18)
+ .label(expectedLabel6)
+ .vreturn()
+ .label(expectedLabel7) // Exception handler for second try.
+ .astore(LOCAL3)
+ .aconst_null()
+ .go(expectedLabel19)
+ .label(expectedLabel8)
+ .aload(LOCAL3)
+ .athrow()
+ .label(expectedLabel9) // Test for the while loop.
+ .iload(LOCAL1)
+ .ifne(expectedLabel5) // Falls through.
+ .label(expectedLabel10) // Exit from finally block.
+ .go(expectedLabel1)
+ // --- Second instantiation of first subroutine ---
+ .label(expectedLabel11) // First finally handler.
+ .astore(LOCAL4)
+ .go(expectedLabel16)
+ .label(expectedLabel12) // Body of while loop, also second try.
+ .aconst_null()
+ .go(expectedLabel20)
+ .label(expectedLabel13)
+ .vreturn()
+ .label(expectedLabel14) // Exception handler for second try.
+ .astore(LOCAL3)
+ .aconst_null()
+ .go(expectedLabel21)
+ .label(expectedLabel15)
+ .aload(LOCAL3)
+ .athrow()
+ .label(expectedLabel16) // Test for the while loop.
+ .iload(LOCAL1)
+ .ifne(expectedLabel12) // Falls through.
+ .label(expectedLabel17) // Exit from finally block.
+ .go(expectedLabel3)
+ // --- Second subroutine's 4 instantiations ---
+ .label(expectedLabel18) // First instantiation.
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel10)
+ .go(expectedLabel6)
+ .label()
+ .label(expectedLabel19) // Second instantiation.
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel10)
+ .go(expectedLabel8)
+ .label()
+ .label(expectedLabel20) // Third instantiation.
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel17)
+ .go(expectedLabel13)
+ .label()
+ .label(expectedLabel21) // Fourth instantiation.
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel17)
+ .go(expectedLabel15)
+ .label()
+ .trycatch(expectedLabel0, expectedLabel2, expectedLabel2)
+ .trycatch(expectedLabel5, expectedLabel7, expectedLabel7)
+ .trycatch(expectedLabel12, expectedLabel14, expectedLabel14)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * This tests two subroutines, neither of which exit. Instead, they both branch to a common set of
+ * code which returns from the method. This code is not reachable except through these
+ * subroutines, and since they do not invoke each other, it must be copied into both of them.
+ *
+ * <p>I don't believe this can be represented in Java.
+ */
+ @Test
+ void testInlineJsr_commonCodeWhichMustBeDuplicated() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 2)
+ .iconst_0()
+ .istore(1)
+ // Invoke the two subroutines, each twice.
+ .jsr(label0)
+ .jsr(label0)
+ .jsr(label1)
+ .jsr(label1)
+ .vreturn()
+ .label(label0) // Subroutine 1.
+ .iinc(1, 1)
+ .go(label2) // ... note that it does not return!
+ .label(label1) // Subroutine 2.
+ .iinc(1, 2)
+ .go(label2) // ... note that it does not return!
+ .label(label2) // Common code to both subroutines: exit method.
+ .vreturn()
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 2)
+ .iconst_0()
+ .istore(1)
+ // Invoke the two subroutines, each twice.
+ .aconst_null()
+ .go(expectedLabel4)
+ .label(expectedLabel0)
+ .aconst_null()
+ .go(expectedLabel6)
+ .label(expectedLabel1)
+ .aconst_null()
+ .go(expectedLabel8)
+ .label(expectedLabel2)
+ .aconst_null()
+ .go(expectedLabel10)
+ .label(expectedLabel3)
+ .vreturn()
+ .label()
+ .label(expectedLabel4) // Instantiation 1 of subroutine 1.
+ .iinc(1, 1)
+ .go(expectedLabel5) // ... note that it does not return!
+ .label(expectedLabel5)
+ .vreturn()
+ .label(expectedLabel6) // Instantiation 2 of subroutine 1.
+ .iinc(1, 1)
+ .go(expectedLabel7) // ... note that it does not return!
+ .label(expectedLabel7)
+ .vreturn()
+ .label(expectedLabel8) // Instantiation 1 of subroutine 2.
+ .iinc(1, 2)
+ .go(expectedLabel9) // ...note that it does not return!
+ .label(expectedLabel9)
+ .vreturn()
+ .label(expectedLabel10) // Instantiation 2 of subroutine 2.
+ .iinc(1, 2)
+ .go(expectedLabel11) // ... note that it does not return!
+ .label(expectedLabel11)
+ .vreturn()
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * This tests a simple subroutine where the control flow jumps back and forth between the
+ * subroutine and the caller.
+ *
+ * <p>This would not normally be produced by a java compiler.
+ */
+ @Test
+ void testInlineJsr_interleavedCode() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 3)
+ .iconst_0()
+ .istore(1)
+ // Invoke the subroutine, each twice.
+ .jsr(label0)
+ .go(label1)
+ .label(label0) // Subroutine 1.
+ .astore(2)
+ .iinc(1, 1)
+ .go(label2)
+ .label(label1) // Second part of main subroutine.
+ .iinc(1, 2)
+ .go(label3)
+ .label(label2) // Second part of subroutine 1.
+ .iinc(1, 4)
+ .ret(2)
+ .label(label3) // Third part of main subroutine.
+ .jsr(label0)
+ .vreturn()
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 3)
+ // Main routine.
+ .iconst_0()
+ .istore(1)
+ .aconst_null()
+ .go(expectedLabel4)
+ .label(expectedLabel0)
+ .go(expectedLabel1)
+ .label(expectedLabel1)
+ .iinc(1, 2)
+ .go(expectedLabel2)
+ .label(expectedLabel2)
+ .aconst_null()
+ .go(expectedLabel6)
+ .label(expectedLabel3)
+ .vreturn()
+ .label(expectedLabel4) // Instantiation #1.
+ .astore(2)
+ .iinc(1, 1)
+ .go(expectedLabel5)
+ .label(expectedLabel5)
+ .iinc(1, 4)
+ .go(expectedLabel0)
+ .label()
+ .label(expectedLabel6) // Instantiation #2.
+ .astore(2)
+ .iinc(1, 1)
+ .go(expectedLabel7)
+ .label(expectedLabel7)
+ .iinc(1, 4)
+ .go(expectedLabel3)
+ .label()
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * Tests a nested try/finally with implicit exit from one subroutine to the other subroutine, and
+ * with a surrounding try/catch thrown in the mix. Equivalent to the following java code:
+ *
+ * <pre>
+ * void m(int b) {
+ * try {
+ * try {
+ * return;
+ * } finally {
+ * while (b) {
+ * try {
+ * return;
+ * } finally {
+ * // NOTE --- this break avoids the second return above (weird)
+ * if (b) {
+ * break;
+ * }
+ * }
+ * }
+ * }
+ * } catch (Exception e) {
+ * b += 3;
+ * return;
+ * }
+ * }
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_implicitExitInTryCatch() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 6)
+ .iconst_0()
+ .istore(1)
+ .label(label0) // Outermost try.
+ .label(label1) // First try.
+ .jsr(label3)
+ .vreturn()
+ .label(label2) // Exception handler for first try.
+ .astore(LOCAL2)
+ .jsr(label3)
+ .aload(LOCAL2)
+ .athrow()
+ .label(label3) // First finally handler.
+ .astore(LOCAL4)
+ .go(label7)
+ .label(label4) // Body of while loop, also second try.
+ .jsr(label6)
+ .vreturn()
+ .label(label5) // Exception handler for second try.
+ .astore(LOCAL3)
+ .jsr(label6)
+ .aload(LOCAL3)
+ .athrow()
+ .label(label6) // Second finally handler.
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(label8)
+ .ret(LOCAL5)
+ .label(label7) // Test for the while loop.
+ .iload(LOCAL1)
+ .ifne(label4) // Falls through.
+ .label(label8) // Exit from finally block.
+ .ret(LOCAL4)
+ .label(label9) // Outermost catch.
+ .iinc(LOCAL1, 3)
+ .vreturn()
+ .trycatch(label1, label2, label2)
+ .trycatch(label4, label5, label5)
+ .trycatch(label0, label9, label9)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 6)
+ // --- Main Subroutine ---
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel0) // Outermost try / first try.
+ .aconst_null()
+ .go(expectedLabel5)
+ .label(expectedLabel1)
+ .vreturn()
+ .label(expectedLabel2) // Exception handler for first try.
+ .astore(LOCAL2)
+ .aconst_null()
+ .go(expectedLabel13)
+ .label(expectedLabel3)
+ .aload(LOCAL2)
+ .athrow()
+ .label(expectedLabel4) // Outermost catch.
+ .iinc(LOCAL1, 3)
+ .vreturn()
+ // --- First instantiation of first subroutine ---
+ .label(expectedLabel5) // First finally handler.
+ .astore(LOCAL4)
+ .go(expectedLabel10)
+ .label(expectedLabel6) // Body of while loop, also second try.
+ .aconst_null()
+ .go(expectedLabel21)
+ .label(expectedLabel7)
+ .vreturn()
+ .label(expectedLabel8) // Exception handler for second try.
+ .astore(LOCAL3)
+ .aconst_null()
+ .go(expectedLabel23)
+ .label(expectedLabel9)
+ .aload(LOCAL3)
+ .athrow()
+ .label(expectedLabel10) // Test for the while loop.
+ .iload(LOCAL1)
+ .ifne(expectedLabel6) // Falls through.
+ .label(expectedLabel11) // Exit from finally block.
+ .go(expectedLabel1)
+ .label(expectedLabel12)
+ // --- Second instantiation of first subroutine ---
+ .label(expectedLabel13) // First finally handler.
+ .astore(LOCAL4)
+ .go(expectedLabel18)
+ .label(expectedLabel14) // Body of while loop, also second try.
+ .aconst_null()
+ .go(expectedLabel25)
+ .label(expectedLabel15)
+ .vreturn()
+ .label(expectedLabel16) // Exception handler for second try.
+ .astore(LOCAL3)
+ .aconst_null()
+ .go(expectedLabel27)
+ .label(expectedLabel17)
+ .aload(LOCAL3)
+ .athrow()
+ .label(expectedLabel18) // Test for the while loop.
+ .iload(LOCAL1)
+ .ifne(expectedLabel14) // Falls through.
+ .label(expectedLabel19) // Exit from finally block.
+ .go(expectedLabel3)
+ .label(expectedLabel20)
+ // --- Second subroutine's 4 instantiations ---
+ .label(expectedLabel21)
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel11)
+ .go(expectedLabel7)
+ .label(expectedLabel22)
+ .label(expectedLabel23)
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel11)
+ .go(expectedLabel9)
+ .label(expectedLabel24)
+ .label(expectedLabel25)
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel19)
+ .go(expectedLabel15)
+ .label(expectedLabel26)
+ .label(expectedLabel27)
+ .astore(LOCAL5)
+ .iload(LOCAL1)
+ .ifne(expectedLabel19)
+ .go(expectedLabel17)
+ .label(expectedLabel28)
+ // Main subroutine handlers.
+ .trycatch(expectedLabel0, expectedLabel2, expectedLabel2)
+ .trycatch(expectedLabel0, expectedLabel4, expectedLabel4)
+ // First instance of first subroutine try/catch handlers.
+ // Note: reuses handler code from main subroutine.
+ .trycatch(expectedLabel6, expectedLabel8, expectedLabel8)
+ .trycatch(expectedLabel5, expectedLabel12, expectedLabel4)
+ // Second instance of first sub try/catch handlers.
+ .trycatch(expectedLabel14, expectedLabel16, expectedLabel16)
+ .trycatch(expectedLabel13, expectedLabel20, expectedLabel4)
+ // All 4 instances of second subroutine.
+ .trycatch(expectedLabel21, expectedLabel22, expectedLabel4)
+ .trycatch(expectedLabel23, expectedLabel24, expectedLabel4)
+ .trycatch(expectedLabel25, expectedLabel26, expectedLabel4)
+ .trycatch(expectedLabel27, expectedLabel28, expectedLabel4)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * Tests an example coming from distilled down version of
+ * com/sun/corba/ee/impl/protocol/CorbaClientDelegateImpl from GlassFish 2. See issue #317823.
+ */
+ @Test
+ void testInlineJsr_glassFish2CorbaClientDelegateImplExample() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(3, 3)
+ .label(label0)
+ .jsr(label4)
+ .label(label1)
+ .go(label5)
+ .label(label2)
+ .pop()
+ .jsr(label4)
+ .label(label3)
+ .aconst_null()
+ .athrow()
+ .label(label4)
+ .astore(1)
+ .ret(1)
+ .label(label5)
+ .aconst_null()
+ .aconst_null()
+ .aconst_null()
+ .pop()
+ .pop()
+ .pop()
+ .label(label6)
+ .go(label8)
+ .label(label7)
+ .pop()
+ .go(label8)
+ .aconst_null()
+ .athrow()
+ .label(label8)
+ .iconst_0()
+ .ifne(label0)
+ .jsr(label12)
+ .label(label9)
+ .vreturn()
+ .label(label10)
+ .pop()
+ .jsr(label12)
+ .label(label11)
+ .aconst_null()
+ .athrow()
+ .label(label12)
+ .astore(2)
+ .ret(2)
+ .trycatch(label0, label1, label2)
+ .trycatch(label2, label3, label2)
+ .trycatch(label0, label6, label7)
+ .trycatch(label0, label9, label10)
+ .trycatch(label10, label11, label10)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(3, 3)
+ // --- Main Subroutine ---
+ .label(expectedLabel0)
+ .aconst_null()
+ .go(expectedLabel15)
+ .label(expectedLabel1)
+ .label(expectedLabel2)
+ .go(expectedLabel6)
+ .label(expectedLabel3)
+ .pop()
+ .aconst_null()
+ .go(expectedLabel17)
+ .label(expectedLabel4)
+ .label(expectedLabel5)
+ .aconst_null()
+ .athrow()
+ .label(expectedLabel6)
+ .aconst_null()
+ .aconst_null()
+ .aconst_null()
+ .pop()
+ .pop()
+ .pop()
+ .label(expectedLabel7)
+ .go(expectedLabel9)
+ .label(expectedLabel8)
+ .pop()
+ .go(expectedLabel9)
+ // [some dead code skipped here]
+ .label(expectedLabel9)
+ .iconst_0()
+ .ifne(expectedLabel0)
+ .aconst_null()
+ .go(expectedLabel19)
+ .label(expectedLabel10)
+ .label(expectedLabel11)
+ .vreturn()
+ .label(expectedLabel12)
+ .pop()
+ .aconst_null()
+ .go(expectedLabel20)
+ .label(expectedLabel13)
+ .label(expectedLabel14)
+ .aconst_null()
+ .athrow()
+ // --- First instantiation of first subroutine ---
+ .label()
+ .label(expectedLabel15)
+ .astore(1)
+ .go(expectedLabel1)
+ .label(expectedLabel16)
+ // --- Second instantiation of first subroutine ---
+ .label(expectedLabel17)
+ .astore(1)
+ .go(expectedLabel4)
+ .label(expectedLabel18)
+ // --- First instantiation of second subroutine ---
+ .label(expectedLabel19)
+ .astore(2)
+ .go(expectedLabel10)
+ // --- Second instantiation of second subroutine ---
+ .label(expectedLabel20)
+ .astore(2)
+ .go(expectedLabel13)
+ .trycatch(expectedLabel0, expectedLabel2, expectedLabel3)
+ .trycatch(expectedLabel3, expectedLabel5, expectedLabel3)
+ .trycatch(expectedLabel0, expectedLabel7, expectedLabel8)
+ .trycatch(expectedLabel0, expectedLabel11, expectedLabel12)
+ .trycatch(expectedLabel12, expectedLabel14, expectedLabel12)
+ .trycatch(expectedLabel15, expectedLabel16, expectedLabel8)
+ .trycatch(expectedLabel15, expectedLabel16, expectedLabel12)
+ .trycatch(expectedLabel17, expectedLabel18, expectedLabel8)
+ .trycatch(expectedLabel17, expectedLabel18, expectedLabel12)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /**
+ * Tests a method which has line numbers and local variable declarations. More specifically:
+ *
+ * <pre>
+ * public void a() {
+ * 1 int a = 0;
+ * 2 try {
+ * 3 a++;
+ * 4 } finally {
+ * 5 a--;
+ * 6 }
+ * }
+ * LV "a" from 1 to 6
+ * </pre>
+ */
+ @Test
+ void testInlineJsr_basicLineNumberAndLocalVars() {
+ MethodNode inputMethod =
+ new MethodNodeBuilder(1, 4)
+ .label(label0)
+ .line(1, label0)
+ .iconst_0()
+ .istore(1)
+ .label(label1) // Body of try block.
+ .line(3, label1)
+ .iinc(1, 1)
+ .go(label4)
+ .label(label2) // Exception handler.
+ .astore(3)
+ .jsr(label3)
+ .aload(3)
+ .athrow()
+ .label(label3) // Subroutine.
+ .line(5, label3)
+ .astore(2)
+ .iinc(1, -1)
+ .ret(2)
+ .label(label4) // Non-exceptional exit from try block.
+ .jsr(label3)
+ .label(label5)
+ .vreturn()
+ .trycatch(label1, label2, label2)
+ .trycatch(label4, label5, label2)
+ .localvar("a", "I", 1, label0, label5)
+ .build();
+
+ MethodNode inlinedMethod = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null);
+ inputMethod.accept(
+ new JSRInlinerAdapter(inlinedMethod, Opcodes.ACC_PUBLIC, "m", "()V", null, null));
+
+ MethodNode expectedMethod =
+ new MethodNodeBuilder(1, 4)
+ .label(expectedLabel0)
+ .line(1, expectedLabel0)
+ .iconst_0()
+ .istore(1)
+ .label(expectedLabel1) // Try/catch block.
+ .line(3, expectedLabel1)
+ .iinc(1, 1)
+ .go(expectedLabel4)
+ .label(expectedLabel2) // Exception handler.
+ .astore(3)
+ .aconst_null()
+ .go(expectedLabel7)
+ .label(expectedLabel3)
+ .aload(3)
+ .athrow()
+ .label(expectedLabel4) // On non-exceptional exit, try block leads here.
+ .aconst_null()
+ .go(expectedLabel9)
+ .label(expectedLabel5)
+ .label(expectedLabel6)
+ .vreturn()
+ .label(expectedLabel7) // First instantiation of subroutine.
+ .line(5, expectedLabel7)
+ .astore(2)
+ .iinc(1, -1)
+ .go(expectedLabel3)
+ .label(expectedLabel8)
+ .label(expectedLabel9) // Second instantiation of subroutine.
+ .line(5, expectedLabel9)
+ .astore(2)
+ .iinc(1, -1)
+ .go(expectedLabel5)
+ .label(expectedLabel10)
+ .trycatch(expectedLabel1, expectedLabel2, expectedLabel2)
+ .trycatch(expectedLabel4, expectedLabel6, expectedLabel2)
+ .localvar("a", "I", 1, expectedLabel0, expectedLabel6)
+ .localvar("a", "I", 1, expectedLabel7, expectedLabel8)
+ .localvar("a", "I", 1, expectedLabel9, expectedLabel10)
+ .build();
+ assertEquals(toText(expectedMethod), toText(inlinedMethod));
+ assertDoesNotThrow(() -> buildClassWithMethod(inlinedMethod).newInstance());
+ }
+
+ /** Tests that classes transformed with JSRInlinerAdapter can be loaded and instantiated. */
+ @ParameterizedTest
+ @MethodSource(ALL_CLASSES_AND_LATEST_API)
+ void testInlineJsr_precompiledClass(
+ final PrecompiledClass classParameter, final Api apiParameter) {
+ ClassReader classReader = new ClassReader(classParameter.getBytes());
+ ClassWriter classWriter = new ClassWriter(0);
+
+ classReader.accept(new JsrInlinerClassAdapter(apiParameter.value(), classWriter), 0);
+
+ ClassFile classFile = new ClassFile(classWriter.toByteArray());
+ if (classParameter.isNotCompatibleWithCurrentJdk()) {
+ assertThrows(UnsupportedClassVersionError.class, () -> classFile.newInstance());
+ } else {
+ assertDoesNotThrow(() -> classFile.newInstance());
+ }
+ }
+
+ static class JsrInlinerClassAdapter extends ClassVisitor {
+
+ JsrInlinerClassAdapter(final int api, final ClassVisitor classVisitor) {
+ super(api, classVisitor);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions) {
+ MethodVisitor methodVisitor =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
+ return new JSRInlinerAdapter(
+ api, methodVisitor, access, name, descriptor, signature, exceptions) {};
+ }
+ }
+}