// 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.objectweb.asm.test.Assertions.assertThat;

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.tree.MethodNode;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;

/**
 * JSRInlinerAdapter tests.
 *
 * @author Eric Bruneton
 */
public 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;

  private JSRInlinerAdapter inlinedMethod = new JSRInlinerAdapter(null, 0, "m", "()V", null, null);
  private MethodNode expectedMethod = new MethodNode(0, "m", "()V", null, null);

  // 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
  public 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
  public void testBasic() {
    new Generator(inlinedMethod)
        .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)
        .end(1, 4);

    new Generator(expectedMethod)
        .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)
        .end(1, 4);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testIfElseInFinally() {
    new Generator(inlinedMethod)
        .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)
        .end(1, 4);

    new Generator(expectedMethod)
        .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)
        .end(1, 4);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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"})
  public void testLookupOrTableSwitchInFinally(final boolean useTableSwitch) {
    new Generator(inlinedMethod)
        .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)
        .end(1, 4);

    new Generator(expectedMethod)
        .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)
        .end(1, 4);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testSimpleNestedFinally() {
    new Generator(inlinedMethod)
        .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)
        .end(2, 6);

    new Generator(expectedMethod)
        .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)
        .end(2, 6);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testSubroutineWithNoRet() {
    new Generator(inlinedMethod)
        .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)
        .end(1, 4);

    new Generator(expectedMethod)
        .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)
        .end(1, 4);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testSubroutineWithNoRet2() {
    new Generator(inlinedMethod)
        .jsr(label0)
        .go(label1)
        .label(label0)
        .astore(0)
        .vreturn()
        .label(label1)
        .aconst_null()
        .end(1, 1);

    new Generator(expectedMethod)
        .aconst_null()
        .go(expectedLabel2)
        .label(expectedLabel0)
        .go(expectedLabel1)
        .label(expectedLabel1)
        .aconst_null()
        .label(expectedLabel2) // First instantiation of subroutine.
        .astore(0)
        .vreturn()
        .label()
        .end(1, 1);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testImplicitExit() {
    new Generator(inlinedMethod)
        .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)
        .end(1, 4);

    new Generator(expectedMethod)
        .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)
        .end(1, 4);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testImplicitExitToAnotherSubroutine() {
    new Generator(inlinedMethod)
        .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)
        .end(1, 6);

    new Generator(expectedMethod)
        // --- 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)
        .end(1, 6);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testCommonCodeWhichMustBeDuplicated() {
    new Generator(inlinedMethod)
        .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()
        .end(1, 2);

    new Generator(expectedMethod)
        .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()
        .end(1, 2);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testInterleavedCode() {
    new Generator(inlinedMethod)
        .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()
        .end(1, 3);

    new Generator(expectedMethod)
        // 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()
        .end(1, 3);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testImplicitExitInTryCatch() {
    new Generator(inlinedMethod)
        .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)
        .end(1, 6);

    new Generator(expectedMethod)
        // --- 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)
        .end(1, 6);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * Tests an example coming from distilled down version of
   * com/sun/corba/ee/impl/protocol/CorbaClientDelegateImpl from GlassFish 2. See issue #317823.
   */
  @Test
  public void testGlassFish2CorbaClientDelegateImplExample() {
    new Generator(inlinedMethod)
        .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)
        .end(3, 3);

    new Generator(expectedMethod)
        // --- 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)
        .end(3, 3);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  /**
   * 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
  public void testBasicLineNumberAndLocalVars() {
    new Generator(inlinedMethod)
        .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)
        .end(1, 4);

    new Generator(expectedMethod)
        .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)
        .end(1, 4);

    assertMethodEquals(expectedMethod, inlinedMethod);
  }

  private void assertMethodEquals(final MethodNode expected, final MethodNode actual) {
    String expectedText = getText(expected);
    String actualText = getText(actual);
    assertEquals(expectedText, actualText);
  }

  private String getText(final MethodNode methodNode) {
    Textifier textifier = new Textifier();
    methodNode.accept(new TraceMethodVisitor(textifier));

    StringBuilder stringBuilder = new StringBuilder();
    for (Object o : textifier.text) {
      stringBuilder.append(o);
    }
    return stringBuilder.toString();
  }

  /** Tests that classes transformed with JSRInlinerAdapter can be loaded and instantiated. */
  @ParameterizedTest
  @MethodSource(ALL_CLASSES_AND_LATEST_API)
  public void testInlineJsrAndInstantiate(
      final PrecompiledClass classParameter, final Api apiParameter) {
    ClassReader classReader = new ClassReader(classParameter.getBytes());
    ClassWriter classWriter = new ClassWriter(0);
    classReader.accept(
        new ClassVisitor(apiParameter.value(), classWriter) {
          @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);
          }
        },
        0);
    assertThat(() -> loadAndInstantiate(classParameter.getName(), classWriter.toByteArray()))
        .succeedsOrThrows(UnsupportedClassVersionError.class)
        .when(classParameter.isMoreRecentThanCurrentJdk());
  }

  private static class Generator {

    private final MethodNode methodNode;

    Generator(final MethodNode methodNode) {
      this.methodNode = methodNode;
    }

    Generator iconst_0() {
      methodNode.visitInsn(Opcodes.ICONST_0);
      return this;
    }

    Generator pop() {
      methodNode.visitInsn(Opcodes.POP);
      return this;
    }

    Generator istore(final int var) {
      methodNode.visitVarInsn(Opcodes.ISTORE, var);
      return this;
    }

    Generator aload(final int var) {
      methodNode.visitVarInsn(Opcodes.ALOAD, var);
      return this;
    }

    Generator iload(final int var) {
      methodNode.visitVarInsn(Opcodes.ILOAD, var);
      return this;
    }

    Generator astore(final int var) {
      methodNode.visitVarInsn(Opcodes.ASTORE, var);
      return this;
    }

    Generator ret(final int var) {
      methodNode.visitVarInsn(Opcodes.RET, var);
      return this;
    }

    Generator athrow() {
      methodNode.visitInsn(Opcodes.ATHROW);
      return this;
    }

    Generator aconst_null() {
      methodNode.visitInsn(Opcodes.ACONST_NULL);
      return this;
    }

    Generator vreturn() {
      methodNode.visitInsn(Opcodes.RETURN);
      return this;
    }

    Generator label() {
      methodNode.visitLabel(new Label());
      return this;
    }

    Generator label(final Label label) {
      methodNode.visitLabel(label);
      return this;
    }

    Generator iinc(final int var, final int increment) {
      methodNode.visitIincInsn(var, increment);
      return this;
    }

    Generator go(final Label label) {
      methodNode.visitJumpInsn(Opcodes.GOTO, label);
      return this;
    }

    Generator jsr(final Label label) {
      methodNode.visitJumpInsn(Opcodes.JSR, label);
      return this;
    }

    Generator ifnonnull(final Label label) {
      methodNode.visitJumpInsn(Opcodes.IFNONNULL, label);
      return this;
    }

    Generator ifne(final Label label) {
      methodNode.visitJumpInsn(Opcodes.IFNE, label);
      return this;
    }

    Generator switchto(
        final Label defaultLabel, final int key, final Label target, final boolean useTableSwitch) {
      if (useTableSwitch) {
        methodNode.visitTableSwitchInsn(key, key, defaultLabel, new Label[] {target});
      } else {
        methodNode.visitLookupSwitchInsn(defaultLabel, new int[] {key}, new Label[] {target});
      }
      return this;
    }

    Generator trycatch(final Label start, final Label end, final Label handler) {
      methodNode.visitTryCatchBlock(start, end, handler, null);
      return this;
    }

    Generator line(final int line, final Label start) {
      methodNode.visitLineNumber(line, start);
      return this;
    }

    Generator localvar(
        final String name,
        final String descriptor,
        final int index,
        final Label start,
        final Label end) {
      methodNode.visitLocalVariable(name, descriptor, null, start, end, index);
      return this;
    }

    void end(final int maxStack, final int maxLocals) {
      methodNode.visitMaxs(maxStack, maxLocals);
      methodNode.visitEnd();

      ClassWriter classWriter = new ClassWriter(0);
      classWriter.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null);
      MethodVisitor methodVisitor =
          classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
      methodVisitor.visitCode();
      methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
      methodVisitor.visitMethodInsn(
          Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
      methodVisitor.visitInsn(Opcodes.RETURN);
      methodVisitor.visitMaxs(1, 1);
      methodVisitor.visitEnd();
      methodNode.accept(classWriter);
      classWriter.visitEnd();

      assertTrue(loadAndInstantiate("C", classWriter.toByteArray()));
    }
  }
}
