/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * ASM: a very small 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;

/**
 * A {@link MethodVisitor} that generates a corresponding 'method_info' structure, as defined in the
 * Java Virtual Machine Specification (JVMS).
 *
 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.6">JVMS
 *     4.6</a>
 * @author Eric Bruneton
 * @author Eugene Kuleshov
 */
final class MethodWriter extends MethodVisitor {

    /** Indicates that nothing must be computed. */
    static final int COMPUTE_NOTHING = 0;

    /**
      * Indicates that the maximum stack size and the maximum number of local variables must be
      * computed, from scratch.
      */
    static final int COMPUTE_MAX_STACK_AND_LOCAL = 1;

    /**
      * Indicates that the maximum stack size and the maximum number of local variables must be
      * computed, from the existing stack map frames. This can be done more efficiently than with the
      * control flow graph algorithm used for {@link #COMPUTE_MAX_STACK_AND_LOCAL}, by using a linear
      * scan of the bytecode instructions.
      */
    static final int COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES = 2;

    /**
      * Indicates that the stack map frames of type F_INSERT must be computed. The other frames are not
      * computed. They should all be of type F_NEW and should be sufficient to compute the content of
      * the F_INSERT frames, together with the bytecode instructions between a F_NEW and a F_INSERT
      * frame - and without any knowledge of the type hierarchy (by definition of F_INSERT).
      */
    static final int COMPUTE_INSERTED_FRAMES = 3;

    /**
      * Indicates that all the stack map frames must be computed. In this case the maximum stack size
      * and the maximum number of local variables is also computed.
      */
    static final int COMPUTE_ALL_FRAMES = 4;

    /** Indicates that {@link #STACK_SIZE_DELTA} is not applicable (not constant or never used). */
    private static final int NA = 0;

    /**
      * The stack size variation corresponding to each JVM opcode. The stack size variation for opcode
      * 'o' is given by the array element at index 'o'.
      *
      * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html">JVMS 6</a>
      */
    private static final int[] STACK_SIZE_DELTA = {
        0, // nop = 0 (0x0)
        1, // aconst_null = 1 (0x1)
        1, // iconst_m1 = 2 (0x2)
        1, // iconst_0 = 3 (0x3)
        1, // iconst_1 = 4 (0x4)
        1, // iconst_2 = 5 (0x5)
        1, // iconst_3 = 6 (0x6)
        1, // iconst_4 = 7 (0x7)
        1, // iconst_5 = 8 (0x8)
        2, // lconst_0 = 9 (0x9)
        2, // lconst_1 = 10 (0xa)
        1, // fconst_0 = 11 (0xb)
        1, // fconst_1 = 12 (0xc)
        1, // fconst_2 = 13 (0xd)
        2, // dconst_0 = 14 (0xe)
        2, // dconst_1 = 15 (0xf)
        1, // bipush = 16 (0x10)
        1, // sipush = 17 (0x11)
        1, // ldc = 18 (0x12)
        NA, // ldc_w = 19 (0x13)
        NA, // ldc2_w = 20 (0x14)
        1, // iload = 21 (0x15)
        2, // lload = 22 (0x16)
        1, // fload = 23 (0x17)
        2, // dload = 24 (0x18)
        1, // aload = 25 (0x19)
        NA, // iload_0 = 26 (0x1a)
        NA, // iload_1 = 27 (0x1b)
        NA, // iload_2 = 28 (0x1c)
        NA, // iload_3 = 29 (0x1d)
        NA, // lload_0 = 30 (0x1e)
        NA, // lload_1 = 31 (0x1f)
        NA, // lload_2 = 32 (0x20)
        NA, // lload_3 = 33 (0x21)
        NA, // fload_0 = 34 (0x22)
        NA, // fload_1 = 35 (0x23)
        NA, // fload_2 = 36 (0x24)
        NA, // fload_3 = 37 (0x25)
        NA, // dload_0 = 38 (0x26)
        NA, // dload_1 = 39 (0x27)
        NA, // dload_2 = 40 (0x28)
        NA, // dload_3 = 41 (0x29)
        NA, // aload_0 = 42 (0x2a)
        NA, // aload_1 = 43 (0x2b)
        NA, // aload_2 = 44 (0x2c)
        NA, // aload_3 = 45 (0x2d)
        -1, // iaload = 46 (0x2e)
        0, // laload = 47 (0x2f)
        -1, // faload = 48 (0x30)
        0, // daload = 49 (0x31)
        -1, // aaload = 50 (0x32)
        -1, // baload = 51 (0x33)
        -1, // caload = 52 (0x34)
        -1, // saload = 53 (0x35)
        -1, // istore = 54 (0x36)
        -2, // lstore = 55 (0x37)
        -1, // fstore = 56 (0x38)
        -2, // dstore = 57 (0x39)
        -1, // astore = 58 (0x3a)
        NA, // istore_0 = 59 (0x3b)
        NA, // istore_1 = 60 (0x3c)
        NA, // istore_2 = 61 (0x3d)
        NA, // istore_3 = 62 (0x3e)
        NA, // lstore_0 = 63 (0x3f)
        NA, // lstore_1 = 64 (0x40)
        NA, // lstore_2 = 65 (0x41)
        NA, // lstore_3 = 66 (0x42)
        NA, // fstore_0 = 67 (0x43)
        NA, // fstore_1 = 68 (0x44)
        NA, // fstore_2 = 69 (0x45)
        NA, // fstore_3 = 70 (0x46)
        NA, // dstore_0 = 71 (0x47)
        NA, // dstore_1 = 72 (0x48)
        NA, // dstore_2 = 73 (0x49)
        NA, // dstore_3 = 74 (0x4a)
        NA, // astore_0 = 75 (0x4b)
        NA, // astore_1 = 76 (0x4c)
        NA, // astore_2 = 77 (0x4d)
        NA, // astore_3 = 78 (0x4e)
        -3, // iastore = 79 (0x4f)
        -4, // lastore = 80 (0x50)
        -3, // fastore = 81 (0x51)
        -4, // dastore = 82 (0x52)
        -3, // aastore = 83 (0x53)
        -3, // bastore = 84 (0x54)
        -3, // castore = 85 (0x55)
        -3, // sastore = 86 (0x56)
        -1, // pop = 87 (0x57)
        -2, // pop2 = 88 (0x58)
        1, // dup = 89 (0x59)
        1, // dup_x1 = 90 (0x5a)
        1, // dup_x2 = 91 (0x5b)
        2, // dup2 = 92 (0x5c)
        2, // dup2_x1 = 93 (0x5d)
        2, // dup2_x2 = 94 (0x5e)
        0, // swap = 95 (0x5f)
        -1, // iadd = 96 (0x60)
        -2, // ladd = 97 (0x61)
        -1, // fadd = 98 (0x62)
        -2, // dadd = 99 (0x63)
        -1, // isub = 100 (0x64)
        -2, // lsub = 101 (0x65)
        -1, // fsub = 102 (0x66)
        -2, // dsub = 103 (0x67)
        -1, // imul = 104 (0x68)
        -2, // lmul = 105 (0x69)
        -1, // fmul = 106 (0x6a)
        -2, // dmul = 107 (0x6b)
        -1, // idiv = 108 (0x6c)
        -2, // ldiv = 109 (0x6d)
        -1, // fdiv = 110 (0x6e)
        -2, // ddiv = 111 (0x6f)
        -1, // irem = 112 (0x70)
        -2, // lrem = 113 (0x71)
        -1, // frem = 114 (0x72)
        -2, // drem = 115 (0x73)
        0, // ineg = 116 (0x74)
        0, // lneg = 117 (0x75)
        0, // fneg = 118 (0x76)
        0, // dneg = 119 (0x77)
        -1, // ishl = 120 (0x78)
        -1, // lshl = 121 (0x79)
        -1, // ishr = 122 (0x7a)
        -1, // lshr = 123 (0x7b)
        -1, // iushr = 124 (0x7c)
        -1, // lushr = 125 (0x7d)
        -1, // iand = 126 (0x7e)
        -2, // land = 127 (0x7f)
        -1, // ior = 128 (0x80)
        -2, // lor = 129 (0x81)
        -1, // ixor = 130 (0x82)
        -2, // lxor = 131 (0x83)
        0, // iinc = 132 (0x84)
        1, // i2l = 133 (0x85)
        0, // i2f = 134 (0x86)
        1, // i2d = 135 (0x87)
        -1, // l2i = 136 (0x88)
        -1, // l2f = 137 (0x89)
        0, // l2d = 138 (0x8a)
        0, // f2i = 139 (0x8b)
        1, // f2l = 140 (0x8c)
        1, // f2d = 141 (0x8d)
        -1, // d2i = 142 (0x8e)
        0, // d2l = 143 (0x8f)
        -1, // d2f = 144 (0x90)
        0, // i2b = 145 (0x91)
        0, // i2c = 146 (0x92)
        0, // i2s = 147 (0x93)
        -3, // lcmp = 148 (0x94)
        -1, // fcmpl = 149 (0x95)
        -1, // fcmpg = 150 (0x96)
        -3, // dcmpl = 151 (0x97)
        -3, // dcmpg = 152 (0x98)
        -1, // ifeq = 153 (0x99)
        -1, // ifne = 154 (0x9a)
        -1, // iflt = 155 (0x9b)
        -1, // ifge = 156 (0x9c)
        -1, // ifgt = 157 (0x9d)
        -1, // ifle = 158 (0x9e)
        -2, // if_icmpeq = 159 (0x9f)
        -2, // if_icmpne = 160 (0xa0)
        -2, // if_icmplt = 161 (0xa1)
        -2, // if_icmpge = 162 (0xa2)
        -2, // if_icmpgt = 163 (0xa3)
        -2, // if_icmple = 164 (0xa4)
        -2, // if_acmpeq = 165 (0xa5)
        -2, // if_acmpne = 166 (0xa6)
        0, // goto = 167 (0xa7)
        1, // jsr = 168 (0xa8)
        0, // ret = 169 (0xa9)
        -1, // tableswitch = 170 (0xaa)
        -1, // lookupswitch = 171 (0xab)
        -1, // ireturn = 172 (0xac)
        -2, // lreturn = 173 (0xad)
        -1, // freturn = 174 (0xae)
        -2, // dreturn = 175 (0xaf)
        -1, // areturn = 176 (0xb0)
        0, // return = 177 (0xb1)
        NA, // getstatic = 178 (0xb2)
        NA, // putstatic = 179 (0xb3)
        NA, // getfield = 180 (0xb4)
        NA, // putfield = 181 (0xb5)
        NA, // invokevirtual = 182 (0xb6)
        NA, // invokespecial = 183 (0xb7)
        NA, // invokestatic = 184 (0xb8)
        NA, // invokeinterface = 185 (0xb9)
        NA, // invokedynamic = 186 (0xba)
        1, // new = 187 (0xbb)
        0, // newarray = 188 (0xbc)
        0, // anewarray = 189 (0xbd)
        0, // arraylength = 190 (0xbe)
        NA, // athrow = 191 (0xbf)
        0, // checkcast = 192 (0xc0)
        0, // instanceof = 193 (0xc1)
        -1, // monitorenter = 194 (0xc2)
        -1, // monitorexit = 195 (0xc3)
        NA, // wide = 196 (0xc4)
        NA, // multianewarray = 197 (0xc5)
        -1, // ifnull = 198 (0xc6)
        -1, // ifnonnull = 199 (0xc7)
        NA, // goto_w = 200 (0xc8)
        NA // jsr_w = 201 (0xc9)
    };

    /** Where the constants used in this MethodWriter must be stored. */
    private final SymbolTable symbolTable;

    // Note: fields are ordered as in the method_info structure, and those related to attributes are
    // ordered as in Section 4.7 of the JVMS.

    /**
      * The access_flags field of the method_info JVMS structure. This field can contain ASM specific
      * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
      * ClassFile structure.
      */
    private final int accessFlags;

    /** The name_index field of the method_info JVMS structure. */
    private final int nameIndex;

    /** The name of this method. */
    private final String name;

    /** The descriptor_index field of the method_info JVMS structure. */
    private final int descriptorIndex;

    /** The descriptor of this method. */
    private final String descriptor;

    // Code attribute fields and sub attributes:

    /** The max_stack field of the Code attribute. */
    private int maxStack;

    /** The max_locals field of the Code attribute. */
    private int maxLocals;

    /** The 'code' field of the Code attribute. */
    private final ByteVector code = new ByteVector();

    /**
      * The first element in the exception handler list (used to generate the exception_table of the
      * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May
      * be {@literal null}.
      */
    private Handler firstHandler;

    /**
      * The last element in the exception handler list (used to generate the exception_table of the
      * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May
      * be {@literal null}.
      */
    private Handler lastHandler;

    /** The line_number_table_length field of the LineNumberTable code attribute. */
    private int lineNumberTableLength;

    /** The line_number_table array of the LineNumberTable code attribute, or {@literal null}. */
    private ByteVector lineNumberTable;

    /** The local_variable_table_length field of the LocalVariableTable code attribute. */
    private int localVariableTableLength;

    /**
      * The local_variable_table array of the LocalVariableTable code attribute, or {@literal null}.
      */
    private ByteVector localVariableTable;

    /** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */
    private int localVariableTypeTableLength;

    /**
      * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or {@literal
      * null}.
      */
    private ByteVector localVariableTypeTable;

    /** The number_of_entries field of the StackMapTable code attribute. */
    private int stackMapTableNumberOfEntries;

    /** The 'entries' array of the StackMapTable code attribute. */
    private ByteVector stackMapTableEntries;

    /**
      * The last runtime visible type annotation of the Code attribute. The previous ones can be
      * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
      */
    private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation;

    /**
      * The last runtime invisible type annotation of the Code attribute. The previous ones can be
      * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
      */
    private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation;

    /**
      * The first non standard attribute of the Code attribute. The next ones can be accessed with the
      * {@link Attribute#nextAttribute} field. May be {@literal null}.
      *
      * <p><b>WARNING</b>: this list stores the attributes in the <i>reverse</i> order of their visit.
      * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
      * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the
      * reverse order specified by the user.
      */
    private Attribute firstCodeAttribute;

    // Other method_info attributes:

    /** The number_of_exceptions field of the Exceptions attribute. */
    private final int numberOfExceptions;

    /** The exception_index_table array of the Exceptions attribute, or {@literal null}. */
    private final int[] exceptionIndexTable;

    /** The signature_index field of the Signature attribute. */
    private final int signatureIndex;

    /**
      * The last runtime visible annotation of this method. The previous ones can be accessed with the
      * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
      */
    private AnnotationWriter lastRuntimeVisibleAnnotation;

    /**
      * The last runtime invisible annotation of this method. The previous ones can be accessed with
      * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
      */
    private AnnotationWriter lastRuntimeInvisibleAnnotation;

    /** The number of method parameters that can have runtime visible annotations, or 0. */
    private int visibleAnnotableParameterCount;

    /**
      * The runtime visible parameter annotations of this method. Each array element contains the last
      * annotation of a parameter (which can be {@literal null} - the previous ones can be accessed
      * with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}.
      */
    private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations;

    /** The number of method parameters that can have runtime visible annotations, or 0. */
    private int invisibleAnnotableParameterCount;

    /**
      * The runtime invisible parameter annotations of this method. Each array element contains the
      * last annotation of a parameter (which can be {@literal null} - the previous ones can be
      * accessed with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}.
      */
    private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations;

    /**
      * The last runtime visible type annotation of this method. The previous ones can be accessed with
      * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
      */
    private AnnotationWriter lastRuntimeVisibleTypeAnnotation;

    /**
      * The last runtime invisible type annotation of this method. The previous ones can be accessed
      * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
      */
    private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;

    /** The default_value field of the AnnotationDefault attribute, or {@literal null}. */
    private ByteVector defaultValue;

    /** The parameters_count field of the MethodParameters attribute. */
    private int parametersCount;

    /** The 'parameters' array of the MethodParameters attribute, or {@literal null}. */
    private ByteVector parameters;

    /**
      * The first non standard attribute of this method. The next ones can be accessed with the {@link
      * Attribute#nextAttribute} field. May be {@literal null}.
      *
      * <p><b>WARNING</b>: this list stores the attributes in the <i>reverse</i> order of their visit.
      * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
      * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the
      * reverse order specified by the user.
      */
    private Attribute firstAttribute;

    // -----------------------------------------------------------------------------------------------
    // Fields used to compute the maximum stack size and number of locals, and the stack map frames
    // -----------------------------------------------------------------------------------------------

    /**
      * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link
      * #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link
      * #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
      */
    private final int compute;

    /**
      * The first basic block of the method. The next ones (in bytecode offset order) can be accessed
      * with the {@link Label#nextBasicBlock} field.
      */
    private Label firstBasicBlock;

    /**
      * The last basic block of the method (in bytecode offset order). This field is updated each time
      * a basic block is encountered, and is used to append it at the end of the basic block list.
      */
    private Label lastBasicBlock;

    /**
      * The current basic block, i.e. the basic block of the last visited instruction. When {@link
      * #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this
      * field is {@literal null} for unreachable code. When {@link #compute} is equal to {@link
      * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays
      * unchanged throughout the whole method (i.e. the whole code is seen as a single basic block;
      * indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame -
      * and the maximum stack size as well - without using any control flow graph).
      */
    private Label currentBasicBlock;

    /**
      * The relative stack size after the last visited instruction. This size is relative to the
      * beginning of {@link #currentBasicBlock}, i.e. the true stack size after the last visited
      * instruction is equal to the {@link Label#inputStackSize} of the current basic block plus {@link
      * #relativeStackSize}. When {@link #compute} is equal to {@link
      * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
      * the method, so this relative size is also equal to the absolute stack size after the last
      * visited instruction.
      */
    private int relativeStackSize;

    /**
      * The maximum relative stack size after the last visited instruction. This size is relative to
      * the beginning of {@link #currentBasicBlock}, i.e. the true maximum stack size after the last
      * visited instruction is equal to the {@link Label#inputStackSize} of the current basic block
      * plus {@link #maxRelativeStackSize}.When {@link #compute} is equal to {@link
      * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
      * the method, so this relative size is also equal to the absolute maximum stack size after the
      * last visited instruction.
      */
    private int maxRelativeStackSize;

    /** The number of local variables in the last visited stack map frame. */
    private int currentLocals;

    /** The bytecode offset of the last frame that was written in {@link #stackMapTableEntries}. */
    private int previousFrameOffset;

    /**
      * The last frame that was written in {@link #stackMapTableEntries}. This field has the same
      * format as {@link #currentFrame}.
      */
    private int[] previousFrame;

    /**
      * The current stack map frame. The first element contains the bytecode offset of the instruction
      * to which the frame corresponds, the second element is the number of locals and the third one is
      * the number of stack elements. The local variables start at index 3 and are followed by the
      * operand stack elements. In summary frame[0] = offset, frame[1] = numLocal, frame[2] = numStack.
      * Local variables and operand stack entries contain abstract types, as defined in {@link Frame},
      * but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND}, {@link
      * Frame#UNINITIALIZED_KIND} or {@link Frame#FORWARD_UNINITIALIZED_KIND} abstract types. Long and
      * double types use only one array entry.
      */
    private int[] currentFrame;

    /** Whether this method contains subroutines. */
    private boolean hasSubroutines;

    // -----------------------------------------------------------------------------------------------
    // Other miscellaneous status fields
    // -----------------------------------------------------------------------------------------------

    /** Whether the bytecode of this method contains ASM specific instructions. */
    private boolean hasAsmInstructions;

    /**
      * The start offset of the last visited instruction. Used to set the offset field of type
      * annotations of type 'offset_target' (see <a
      * href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.1">JVMS
      * 4.7.20.1</a>).
      */
    private int lastBytecodeOffset;

    /**
      * The offset in bytes in {@link SymbolTable#getSource} from which the method_info for this method
      * (excluding its first 6 bytes) must be copied, or 0.
      */
    private int sourceOffset;

    /**
      * The length in bytes in {@link SymbolTable#getSource} which must be copied to get the
      * method_info for this method (excluding its first 6 bytes for access_flags, name_index and
      * descriptor_index).
      */
    private int sourceLength;

    // -----------------------------------------------------------------------------------------------
    // Constructor and accessors
    // -----------------------------------------------------------------------------------------------

    /**
      * Constructs a new {@link MethodWriter}.
      *
      * @param symbolTable where the constants used in this AnnotationWriter must be stored.
      * @param access the method's access flags (see {@link Opcodes}).
      * @param name the method's name.
      * @param descriptor the method's descriptor (see {@link Type}).
      * @param signature the method's signature. May be {@literal null}.
      * @param exceptions the internal names of the method's exceptions. May be {@literal null}.
      * @param compute indicates what must be computed (see #compute).
      */
    MethodWriter(
            final SymbolTable symbolTable,
            final int access,
            final String name,
            final String descriptor,
            final String signature,
            final String[] exceptions,
            final int compute) {
        super(/* latest api = */ Opcodes.ASM9);
        this.symbolTable = symbolTable;
        this.accessFlags = "<init>".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
        this.nameIndex = symbolTable.addConstantUtf8(name);
        this.name = name;
        this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
        this.descriptor = descriptor;
        this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature);
        if (exceptions != null && exceptions.length > 0) {
            numberOfExceptions = exceptions.length;
            this.exceptionIndexTable = new int[numberOfExceptions];
            for (int i = 0; i < numberOfExceptions; ++i) {
                this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index;
            }
        } else {
            numberOfExceptions = 0;
            this.exceptionIndexTable = null;
        }
        this.compute = compute;
        if (compute != COMPUTE_NOTHING) {
            // Update maxLocals and currentLocals.
            int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
            if ((access & Opcodes.ACC_STATIC) != 0) {
                --argumentsSize;
            }
            maxLocals = argumentsSize;
            currentLocals = argumentsSize;
            // Create and visit the label for the first basic block.
            firstBasicBlock = new Label();
            visitLabel(firstBasicBlock);
        }
    }

    boolean hasFrames() {
        return stackMapTableNumberOfEntries > 0;
    }

    boolean hasAsmInstructions() {
        return hasAsmInstructions;
    }

    // -----------------------------------------------------------------------------------------------
    // Implementation of the MethodVisitor abstract class
    // -----------------------------------------------------------------------------------------------

    @Override
    public void visitParameter(final String name, final int access) {
        if (parameters == null) {
            parameters = new ByteVector();
        }
        ++parametersCount;
        parameters.putShort((name == null) ? 0 : symbolTable.addConstantUtf8(name)).putShort(access);
    }

    @Override
    public AnnotationVisitor visitAnnotationDefault() {
        defaultValue = new ByteVector();
        return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null);
    }

    @Override
    public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
        if (visible) {
            return lastRuntimeVisibleAnnotation =
                    AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
        } else {
            return lastRuntimeInvisibleAnnotation =
                    AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
        }
    }

    @Override
    public AnnotationVisitor visitTypeAnnotation(
            final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
        if (visible) {
            return lastRuntimeVisibleTypeAnnotation =
                    AnnotationWriter.create(
                            symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
        } else {
            return lastRuntimeInvisibleTypeAnnotation =
                    AnnotationWriter.create(
                            symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
        }
    }

    @Override
    public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
        if (visible) {
            visibleAnnotableParameterCount = parameterCount;
        } else {
            invisibleAnnotableParameterCount = parameterCount;
        }
    }

    @Override
    public AnnotationVisitor visitParameterAnnotation(
            final int parameter, final String annotationDescriptor, final boolean visible) {
        if (visible) {
            if (lastRuntimeVisibleParameterAnnotations == null) {
                lastRuntimeVisibleParameterAnnotations =
                        new AnnotationWriter[Type.getArgumentCount(descriptor)];
            }
            return lastRuntimeVisibleParameterAnnotations[parameter] =
                    AnnotationWriter.create(
                            symbolTable, annotationDescriptor, lastRuntimeVisibleParameterAnnotations[parameter]);
        } else {
            if (lastRuntimeInvisibleParameterAnnotations == null) {
                lastRuntimeInvisibleParameterAnnotations =
                        new AnnotationWriter[Type.getArgumentCount(descriptor)];
            }
            return lastRuntimeInvisibleParameterAnnotations[parameter] =
                    AnnotationWriter.create(
                            symbolTable,
                            annotationDescriptor,
                            lastRuntimeInvisibleParameterAnnotations[parameter]);
        }
    }

    @Override
    public void visitAttribute(final Attribute attribute) {
        // Store the attributes in the <i>reverse</i> order of their visit by this method.
        if (attribute.isCodeAttribute()) {
            attribute.nextAttribute = firstCodeAttribute;
            firstCodeAttribute = attribute;
        } else {
            attribute.nextAttribute = firstAttribute;
            firstAttribute = attribute;
        }
    }

    @Override
    public void visitCode() {
        // Nothing to do.
    }

    @Override
    public void visitFrame(
            final int type,
            final int numLocal,
            final Object[] local,
            final int numStack,
            final Object[] stack) {
        if (compute == COMPUTE_ALL_FRAMES) {
            return;
        }

        if (compute == COMPUTE_INSERTED_FRAMES) {
            if (currentBasicBlock.frame == null) {
                // This should happen only once, for the implicit first frame (which is explicitly visited
                // in ClassReader if the EXPAND_ASM_INSNS option is used - and COMPUTE_INSERTED_FRAMES
                // can't be set if EXPAND_ASM_INSNS is not used).
                currentBasicBlock.frame = new CurrentFrame(currentBasicBlock);
                currentBasicBlock.frame.setInputFrameFromDescriptor(
                        symbolTable, accessFlags, descriptor, numLocal);
                currentBasicBlock.frame.accept(this);
            } else {
                if (type == Opcodes.F_NEW) {
                    currentBasicBlock.frame.setInputFrameFromApiFormat(
                            symbolTable, numLocal, local, numStack, stack);
                }
                // If type is not F_NEW then it is F_INSERT by hypothesis, and currentBlock.frame contains
                // the stack map frame at the current instruction, computed from the last F_NEW frame and
                // the bytecode instructions in between (via calls to CurrentFrame#execute).
                currentBasicBlock.frame.accept(this);
            }
        } else if (type == Opcodes.F_NEW) {
            if (previousFrame == null) {
                int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
                Frame implicitFirstFrame = new Frame(new Label());
                implicitFirstFrame.setInputFrameFromDescriptor(
                        symbolTable, accessFlags, descriptor, argumentsSize);
                implicitFirstFrame.accept(this);
            }
            currentLocals = numLocal;
            int frameIndex = visitFrameStart(code.length, numLocal, numStack);
            for (int i = 0; i < numLocal; ++i) {
                currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]);
            }
            for (int i = 0; i < numStack; ++i) {
                currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]);
            }
            visitFrameEnd();
        } else {
            if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
                throw new IllegalArgumentException("Class versions V1_5 or less must use F_NEW frames.");
            }
            int offsetDelta;
            if (stackMapTableEntries == null) {
                stackMapTableEntries = new ByteVector();
                offsetDelta = code.length;
            } else {
                offsetDelta = code.length - previousFrameOffset - 1;
                if (offsetDelta < 0) {
                    if (type == Opcodes.F_SAME) {
                        return;
                    } else {
                        throw new IllegalStateException();
                    }
                }
            }

            switch (type) {
                case Opcodes.F_FULL:
                    currentLocals = numLocal;
                    stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal);
                    for (int i = 0; i < numLocal; ++i) {
                        putFrameType(local[i]);
                    }
                    stackMapTableEntries.putShort(numStack);
                    for (int i = 0; i < numStack; ++i) {
                        putFrameType(stack[i]);
                    }
                    break;
                case Opcodes.F_APPEND:
                    currentLocals += numLocal;
                    stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + numLocal).putShort(offsetDelta);
                    for (int i = 0; i < numLocal; ++i) {
                        putFrameType(local[i]);
                    }
                    break;
                case Opcodes.F_CHOP:
                    currentLocals -= numLocal;
                    stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - numLocal).putShort(offsetDelta);
                    break;
                case Opcodes.F_SAME:
                    if (offsetDelta < 64) {
                        stackMapTableEntries.putByte(offsetDelta);
                    } else {
                        stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
                    }
                    break;
                case Opcodes.F_SAME1:
                    if (offsetDelta < 64) {
                        stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
                    } else {
                        stackMapTableEntries
                                .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
                                .putShort(offsetDelta);
                    }
                    putFrameType(stack[0]);
                    break;
                default:
                    throw new IllegalArgumentException();
            }

            previousFrameOffset = code.length;
            ++stackMapTableNumberOfEntries;
        }

        if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
            relativeStackSize = numStack;
            for (int i = 0; i < numStack; ++i) {
                if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
                    relativeStackSize++;
                }
            }
            if (relativeStackSize > maxRelativeStackSize) {
                maxRelativeStackSize = relativeStackSize;
            }
        }

        maxStack = Math.max(maxStack, numStack);
        maxLocals = Math.max(maxLocals, currentLocals);
    }

    @Override
    public void visitInsn(final int opcode) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        code.putByte(opcode);
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(opcode, 0, null, null);
            } else {
                int size = relativeStackSize + STACK_SIZE_DELTA[opcode];
                if (size > maxRelativeStackSize) {
                    maxRelativeStackSize = size;
                }
                relativeStackSize = size;
            }
            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
                endCurrentBasicBlockWithNoSuccessor();
            }
        }
    }

    @Override
    public void visitIntInsn(final int opcode, final int operand) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        if (opcode == Opcodes.SIPUSH) {
            code.put12(opcode, operand);
        } else { // BIPUSH or NEWARRAY
            code.put11(opcode, operand);
        }
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(opcode, operand, null, null);
            } else if (opcode != Opcodes.NEWARRAY) {
                // The stack size delta is 1 for BIPUSH or SIPUSH, and 0 for NEWARRAY.
                int size = relativeStackSize + 1;
                if (size > maxRelativeStackSize) {
                    maxRelativeStackSize = size;
                }
                relativeStackSize = size;
            }
        }
    }

    @Override
    public void visitVarInsn(final int opcode, final int varIndex) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        if (varIndex < 4 && opcode != Opcodes.RET) {
            int optimizedOpcode;
            if (opcode < Opcodes.ISTORE) {
                optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex;
            } else {
                optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex;
            }
            code.putByte(optimizedOpcode);
        } else if (varIndex >= 256) {
            code.putByte(Constants.WIDE).put12(opcode, varIndex);
        } else {
            code.put11(opcode, varIndex);
        }
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(opcode, varIndex, null, null);
            } else {
                if (opcode == Opcodes.RET) {
                    // No stack size delta.
                    currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_END;
                    currentBasicBlock.outputStackSize = (short) relativeStackSize;
                    endCurrentBasicBlockWithNoSuccessor();
                } else { // xLOAD or xSTORE
                    int size = relativeStackSize + STACK_SIZE_DELTA[opcode];
                    if (size > maxRelativeStackSize) {
                        maxRelativeStackSize = size;
                    }
                    relativeStackSize = size;
                }
            }
        }
        if (compute != COMPUTE_NOTHING) {
            int currentMaxLocals;
            if (opcode == Opcodes.LLOAD
                    || opcode == Opcodes.DLOAD
                    || opcode == Opcodes.LSTORE
                    || opcode == Opcodes.DSTORE) {
                currentMaxLocals = varIndex + 2;
            } else {
                currentMaxLocals = varIndex + 1;
            }
            if (currentMaxLocals > maxLocals) {
                maxLocals = currentMaxLocals;
            }
        }
        if (opcode >= Opcodes.ISTORE && compute == COMPUTE_ALL_FRAMES && firstHandler != null) {
            // If there are exception handler blocks, each instruction within a handler range is, in
            // theory, a basic block (since execution can jump from this instruction to the exception
            // handler). As a consequence, the local variable types at the beginning of the handler
            // block should be the merge of the local variable types at all the instructions within the
            // handler range. However, instead of creating a basic block for each instruction, we can
            // get the same result in a more efficient way. Namely, by starting a new basic block after
            // each xSTORE instruction, which is what we do here.
            visitLabel(new Label());
        }
    }

    @Override
    public void visitTypeInsn(final int opcode, final String type) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        Symbol typeSymbol = symbolTable.addConstantClass(type);
        code.put12(opcode, typeSymbol.index);
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(opcode, lastBytecodeOffset, typeSymbol, symbolTable);
            } else if (opcode == Opcodes.NEW) {
                // The stack size delta is 1 for NEW, and 0 for ANEWARRAY, CHECKCAST, or INSTANCEOF.
                int size = relativeStackSize + 1;
                if (size > maxRelativeStackSize) {
                    maxRelativeStackSize = size;
                }
                relativeStackSize = size;
            }
        }
    }

    @Override
    public void visitFieldInsn(
            final int opcode, final String owner, final String name, final String descriptor) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        Symbol fieldrefSymbol = symbolTable.addConstantFieldref(owner, name, descriptor);
        code.put12(opcode, fieldrefSymbol.index);
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(opcode, 0, fieldrefSymbol, symbolTable);
            } else {
                int size;
                char firstDescChar = descriptor.charAt(0);
                switch (opcode) {
                    case Opcodes.GETSTATIC:
                        size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 2 : 1);
                        break;
                    case Opcodes.PUTSTATIC:
                        size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -2 : -1);
                        break;
                    case Opcodes.GETFIELD:
                        size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 1 : 0);
                        break;
                    case Opcodes.PUTFIELD:
                    default:
                        size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -3 : -2);
                        break;
                }
                if (size > maxRelativeStackSize) {
                    maxRelativeStackSize = size;
                }
                relativeStackSize = size;
            }
        }
    }

    @Override
    public void visitMethodInsn(
            final int opcode,
            final String owner,
            final String name,
            final String descriptor,
            final boolean isInterface) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        Symbol methodrefSymbol = symbolTable.addConstantMethodref(owner, name, descriptor, isInterface);
        if (opcode == Opcodes.INVOKEINTERFACE) {
            code.put12(Opcodes.INVOKEINTERFACE, methodrefSymbol.index)
                    .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0);
        } else {
            code.put12(opcode, methodrefSymbol.index);
        }
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(opcode, 0, methodrefSymbol, symbolTable);
            } else {
                int argumentsAndReturnSize = methodrefSymbol.getArgumentsAndReturnSizes();
                int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2);
                int size;
                if (opcode == Opcodes.INVOKESTATIC) {
                    size = relativeStackSize + stackSizeDelta + 1;
                } else {
                    size = relativeStackSize + stackSizeDelta;
                }
                if (size > maxRelativeStackSize) {
                    maxRelativeStackSize = size;
                }
                relativeStackSize = size;
            }
        }
    }

    @Override
    public void visitInvokeDynamicInsn(
            final String name,
            final String descriptor,
            final Handle bootstrapMethodHandle,
            final Object... bootstrapMethodArguments) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        Symbol invokeDynamicSymbol =
                symbolTable.addConstantInvokeDynamic(
                        name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
        code.put12(Opcodes.INVOKEDYNAMIC, invokeDynamicSymbol.index);
        code.putShort(0);
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, invokeDynamicSymbol, symbolTable);
            } else {
                int argumentsAndReturnSize = invokeDynamicSymbol.getArgumentsAndReturnSizes();
                int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2) + 1;
                int size = relativeStackSize + stackSizeDelta;
                if (size > maxRelativeStackSize) {
                    maxRelativeStackSize = size;
                }
                relativeStackSize = size;
            }
        }
    }

    @Override
    public void visitJumpInsn(final int opcode, final Label label) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        // Compute the 'base' opcode, i.e. GOTO or JSR if opcode is GOTO_W or JSR_W, otherwise opcode.
        int baseOpcode =
                opcode >= Constants.GOTO_W ? opcode - Constants.WIDE_JUMP_OPCODE_DELTA : opcode;
        boolean nextInsnIsJumpTarget = false;
        if ((label.flags & Label.FLAG_RESOLVED) != 0
                && label.bytecodeOffset - code.length < Short.MIN_VALUE) {
            // Case of a backward jump with an offset < -32768. In this case we automatically replace GOTO
            // with GOTO_W, JSR with JSR_W and IFxxx <l> with IFNOTxxx <L> GOTO_W <l> L:..., where
            // IFNOTxxx is the "opposite" opcode of IFxxx (e.g. IFNE for IFEQ) and where <L> designates
            // the instruction just after the GOTO_W.
            if (baseOpcode == Opcodes.GOTO) {
                code.putByte(Constants.GOTO_W);
            } else if (baseOpcode == Opcodes.JSR) {
                code.putByte(Constants.JSR_W);
            } else {
                // Put the "opposite" opcode of baseOpcode. This can be done by flipping the least
                // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ (with a
                // pre and post offset by 1). The jump offset is 8 bytes (3 for IFNOTxxx, 5 for GOTO_W).
                code.putByte(baseOpcode >= Opcodes.IFNULL ? baseOpcode ^ 1 : ((baseOpcode + 1) ^ 1) - 1);
                code.putShort(8);
                // Here we could put a GOTO_W in theory, but if ASM specific instructions are used in this
                // method or another one, and if the class has frames, we will need to insert a frame after
                // this GOTO_W during the additional ClassReader -> ClassWriter round trip to remove the ASM
                // specific instructions. To not miss this additional frame, we need to use an ASM_GOTO_W
                // here, which has the unfortunate effect of forcing this additional round trip (which in
                // some case would not have been really necessary, but we can't know this at this point).
                code.putByte(Constants.ASM_GOTO_W);
                hasAsmInstructions = true;
                // The instruction after the GOTO_W becomes the target of the IFNOT instruction.
                nextInsnIsJumpTarget = true;
            }
            label.put(code, code.length - 1, true);
        } else if (baseOpcode != opcode) {
            // Case of a GOTO_W or JSR_W specified by the user (normally ClassReader when used to remove
            // ASM specific instructions). In this case we keep the original instruction.
            code.putByte(opcode);
            label.put(code, code.length - 1, true);
        } else {
            // Case of a jump with an offset >= -32768, or of a jump with an unknown offset. In these
            // cases we store the offset in 2 bytes (which will be increased via a ClassReader ->
            // ClassWriter round trip if it turns out that 2 bytes are not sufficient).
            code.putByte(baseOpcode);
            label.put(code, code.length - 1, false);
        }

        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            Label nextBasicBlock = null;
            if (compute == COMPUTE_ALL_FRAMES) {
                currentBasicBlock.frame.execute(baseOpcode, 0, null, null);
                // Record the fact that 'label' is the target of a jump instruction.
                label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
                // Add 'label' as a successor of the current basic block.
                addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
                if (baseOpcode != Opcodes.GOTO) {
                    // The next instruction starts a new basic block (except for GOTO: by default the code
                    // following a goto is unreachable - unless there is an explicit label for it - and we
                    // should not compute stack frame types for its instructions).
                    nextBasicBlock = new Label();
                }
            } else if (compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(baseOpcode, 0, null, null);
            } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
                // No need to update maxRelativeStackSize (the stack size delta is always negative).
                relativeStackSize += STACK_SIZE_DELTA[baseOpcode];
            } else {
                if (baseOpcode == Opcodes.JSR) {
                    // Record the fact that 'label' designates a subroutine, if not already done.
                    if ((label.flags & Label.FLAG_SUBROUTINE_START) == 0) {
                        label.flags |= Label.FLAG_SUBROUTINE_START;
                        hasSubroutines = true;
                    }
                    currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_CALLER;
                    // Note that, by construction in this method, a block which calls a subroutine has at
                    // least two successors in the control flow graph: the first one (added below) leads to
                    // the instruction after the JSR, while the second one (added here) leads to the JSR
                    // target. Note that the first successor is virtual (it does not correspond to a possible
                    // execution path): it is only used to compute the successors of the basic blocks ending
                    // with a ret, in {@link Label#addSubroutineRetSuccessors}.
                    addSuccessorToCurrentBasicBlock(relativeStackSize + 1, label);
                    // The instruction after the JSR starts a new basic block.
                    nextBasicBlock = new Label();
                } else {
                    // No need to update maxRelativeStackSize (the stack size delta is always negative).
                    relativeStackSize += STACK_SIZE_DELTA[baseOpcode];
                    addSuccessorToCurrentBasicBlock(relativeStackSize, label);
                }
            }
            // If the next instruction starts a new basic block, call visitLabel to add the label of this
            // instruction as a successor of the current block, and to start a new basic block.
            if (nextBasicBlock != null) {
                if (nextInsnIsJumpTarget) {
                    nextBasicBlock.flags |= Label.FLAG_JUMP_TARGET;
                }
                visitLabel(nextBasicBlock);
            }
            if (baseOpcode == Opcodes.GOTO) {
                endCurrentBasicBlockWithNoSuccessor();
            }
        }
    }

    @Override
    public void visitLabel(final Label label) {
        // Resolve the forward references to this label, if any.
        hasAsmInstructions |= label.resolve(code.data, stackMapTableEntries, code.length);
        // visitLabel starts a new basic block (except for debug only labels), so we need to update the
        // previous and current block references and list of successors.
        if ((label.flags & Label.FLAG_DEBUG_ONLY) != 0) {
            return;
        }
        if (compute == COMPUTE_ALL_FRAMES) {
            if (currentBasicBlock != null) {
                if (label.bytecodeOffset == currentBasicBlock.bytecodeOffset) {
                    // We use {@link Label#getCanonicalInstance} to store the state of a basic block in only
                    // one place, but this does not work for labels which have not been visited yet.
                    // Therefore, when we detect here two labels having the same bytecode offset, we need to
                    // - consolidate the state scattered in these two instances into the canonical instance:
                    currentBasicBlock.flags |= (short)(label.flags & Label.FLAG_JUMP_TARGET);
                    // - make sure the two instances share the same Frame instance (the implementation of
                    // {@link Label#getCanonicalInstance} relies on this property; here label.frame should be
                    // null):
                    label.frame = currentBasicBlock.frame;
                    // - and make sure to NOT assign 'label' into 'currentBasicBlock' or 'lastBasicBlock', so
                    // that they still refer to the canonical instance for this bytecode offset.
                    return;
                }
                // End the current basic block (with one new successor).
                addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
            }
            // Append 'label' at the end of the basic block list.
            if (lastBasicBlock != null) {
                if (label.bytecodeOffset == lastBasicBlock.bytecodeOffset) {
                    // Same comment as above.
                    lastBasicBlock.flags |= (short)(label.flags & Label.FLAG_JUMP_TARGET);
                    // Here label.frame should be null.
                    label.frame = lastBasicBlock.frame;
                    currentBasicBlock = lastBasicBlock;
                    return;
                }
                lastBasicBlock.nextBasicBlock = label;
            }
            lastBasicBlock = label;
            // Make it the new current basic block.
            currentBasicBlock = label;
            // Here label.frame should be null.
            label.frame = new Frame(label);
        } else if (compute == COMPUTE_INSERTED_FRAMES) {
            if (currentBasicBlock == null) {
                // This case should happen only once, for the visitLabel call in the constructor. Indeed, if
                // compute is equal to COMPUTE_INSERTED_FRAMES, currentBasicBlock stays unchanged.
                currentBasicBlock = label;
            } else {
                // Update the frame owner so that a correct frame offset is computed in Frame.accept().
                currentBasicBlock.frame.owner = label;
            }
        } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
            if (currentBasicBlock != null) {
                // End the current basic block (with one new successor).
                currentBasicBlock.outputStackMax = (short) maxRelativeStackSize;
                addSuccessorToCurrentBasicBlock(relativeStackSize, label);
            }
            // Start a new current basic block, and reset the current and maximum relative stack sizes.
            currentBasicBlock = label;
            relativeStackSize = 0;
            maxRelativeStackSize = 0;
            // Append the new basic block at the end of the basic block list.
            if (lastBasicBlock != null) {
                lastBasicBlock.nextBasicBlock = label;
            }
            lastBasicBlock = label;
        } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES && currentBasicBlock == null) {
            // This case should happen only once, for the visitLabel call in the constructor. Indeed, if
            // compute is equal to COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES, currentBasicBlock stays
            // unchanged.
            currentBasicBlock = label;
        }
    }

    @Override
    public void visitLdcInsn(final Object value) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        Symbol constantSymbol = symbolTable.addConstant(value);
        int constantIndex = constantSymbol.index;
        char firstDescriptorChar;
        boolean isLongOrDouble =
                constantSymbol.tag == Symbol.CONSTANT_LONG_TAG
                        || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG
                        || (constantSymbol.tag == Symbol.CONSTANT_DYNAMIC_TAG
                                && ((firstDescriptorChar = constantSymbol.value.charAt(0)) == 'J'
                                        || firstDescriptorChar == 'D'));
        if (isLongOrDouble) {
            code.put12(Constants.LDC2_W, constantIndex);
        } else if (constantIndex >= 256) {
            code.put12(Constants.LDC_W, constantIndex);
        } else {
            code.put11(Opcodes.LDC, constantIndex);
        }
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(Opcodes.LDC, 0, constantSymbol, symbolTable);
            } else {
                int size = relativeStackSize + (isLongOrDouble ? 2 : 1);
                if (size > maxRelativeStackSize) {
                    maxRelativeStackSize = size;
                }
                relativeStackSize = size;
            }
        }
    }

    @Override
    public void visitIincInsn(final int varIndex, final int increment) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        if ((varIndex > 255) || (increment > 127) || (increment < -128)) {
            code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment);
        } else {
            code.putByte(Opcodes.IINC).put11(varIndex, increment);
        }
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null
                && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) {
            currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null);
        }
        if (compute != COMPUTE_NOTHING) {
            int currentMaxLocals = varIndex + 1;
            if (currentMaxLocals > maxLocals) {
                maxLocals = currentMaxLocals;
            }
        }
    }

    @Override
    public void visitTableSwitchInsn(
            final int min, final int max, final Label dflt, final Label... labels) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        code.putByte(Opcodes.TABLESWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4);
        dflt.put(code, lastBytecodeOffset, true);
        code.putInt(min).putInt(max);
        for (Label label : labels) {
            label.put(code, lastBytecodeOffset, true);
        }
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        visitSwitchInsn(dflt, labels);
    }

    @Override
    public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        code.putByte(Opcodes.LOOKUPSWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4);
        dflt.put(code, lastBytecodeOffset, true);
        code.putInt(labels.length);
        for (int i = 0; i < labels.length; ++i) {
            code.putInt(keys[i]);
            labels[i].put(code, lastBytecodeOffset, true);
        }
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        visitSwitchInsn(dflt, labels);
    }

    private void visitSwitchInsn(final Label dflt, final Label[] labels) {
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES) {
                currentBasicBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
                // Add all the labels as successors of the current basic block.
                addSuccessorToCurrentBasicBlock(Edge.JUMP, dflt);
                dflt.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
                for (Label label : labels) {
                    addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
                    label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
                }
            } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
                // No need to update maxRelativeStackSize (the stack size delta is always negative).
                --relativeStackSize;
                // Add all the labels as successors of the current basic block.
                addSuccessorToCurrentBasicBlock(relativeStackSize, dflt);
                for (Label label : labels) {
                    addSuccessorToCurrentBasicBlock(relativeStackSize, label);
                }
            }
            // End the current basic block.
            endCurrentBasicBlockWithNoSuccessor();
        }
    }

    @Override
    public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
        lastBytecodeOffset = code.length;
        // Add the instruction to the bytecode of the method.
        Symbol descSymbol = symbolTable.addConstantClass(descriptor);
        code.put12(Opcodes.MULTIANEWARRAY, descSymbol.index).putByte(numDimensions);
        // If needed, update the maximum stack size and number of locals, and stack map frames.
        if (currentBasicBlock != null) {
            if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
                currentBasicBlock.frame.execute(
                        Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable);
            } else {
                // No need to update maxRelativeStackSize (the stack size delta is always negative).
                relativeStackSize += 1 - numDimensions;
            }
        }
    }

    @Override
    public AnnotationVisitor visitInsnAnnotation(
            final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
        if (visible) {
            return lastCodeRuntimeVisibleTypeAnnotation =
                    AnnotationWriter.create(
                            symbolTable,
                            (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8),
                            typePath,
                            descriptor,
                            lastCodeRuntimeVisibleTypeAnnotation);
        } else {
            return lastCodeRuntimeInvisibleTypeAnnotation =
                    AnnotationWriter.create(
                            symbolTable,
                            (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8),
                            typePath,
                            descriptor,
                            lastCodeRuntimeInvisibleTypeAnnotation);
        }
    }

    @Override
    public void visitTryCatchBlock(
            final Label start, final Label end, final Label handler, final String type) {
        Handler newHandler =
                new Handler(
                        start, end, handler, type != null ? symbolTable.addConstantClass(type).index : 0, type);
        if (firstHandler == null) {
            firstHandler = newHandler;
        } else {
            lastHandler.nextHandler = newHandler;
        }
        lastHandler = newHandler;
    }

    @Override
    public AnnotationVisitor visitTryCatchAnnotation(
            final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
        if (visible) {
            return lastCodeRuntimeVisibleTypeAnnotation =
                    AnnotationWriter.create(
                            symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeVisibleTypeAnnotation);
        } else {
            return lastCodeRuntimeInvisibleTypeAnnotation =
                    AnnotationWriter.create(
                            symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeInvisibleTypeAnnotation);
        }
    }

    @Override
    public void visitLocalVariable(
            final String name,
            final String descriptor,
            final String signature,
            final Label start,
            final Label end,
            final int index) {
        if (signature != null) {
            if (localVariableTypeTable == null) {
                localVariableTypeTable = new ByteVector();
            }
            ++localVariableTypeTableLength;
            localVariableTypeTable
                    .putShort(start.bytecodeOffset)
                    .putShort(end.bytecodeOffset - start.bytecodeOffset)
                    .putShort(symbolTable.addConstantUtf8(name))
                    .putShort(symbolTable.addConstantUtf8(signature))
                    .putShort(index);
        }
        if (localVariableTable == null) {
            localVariableTable = new ByteVector();
        }
        ++localVariableTableLength;
        localVariableTable
                .putShort(start.bytecodeOffset)
                .putShort(end.bytecodeOffset - start.bytecodeOffset)
                .putShort(symbolTable.addConstantUtf8(name))
                .putShort(symbolTable.addConstantUtf8(descriptor))
                .putShort(index);
        if (compute != COMPUTE_NOTHING) {
            char firstDescChar = descriptor.charAt(0);
            int currentMaxLocals = index + (firstDescChar == 'J' || firstDescChar == 'D' ? 2 : 1);
            if (currentMaxLocals > maxLocals) {
                maxLocals = currentMaxLocals;
            }
        }
    }

    @Override
    public AnnotationVisitor visitLocalVariableAnnotation(
            final int typeRef,
            final TypePath typePath,
            final Label[] start,
            final Label[] end,
            final int[] index,
            final String descriptor,
            final boolean visible) {
        // Create a ByteVector to hold a 'type_annotation' JVMS structure.
        // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
        ByteVector typeAnnotation = new ByteVector();
        // Write target_type, target_info, and target_path.
        typeAnnotation.putByte(typeRef >>> 24).putShort(start.length);
        for (int i = 0; i < start.length; ++i) {
            typeAnnotation
                    .putShort(start[i].bytecodeOffset)
                    .putShort(end[i].bytecodeOffset - start[i].bytecodeOffset)
                    .putShort(index[i]);
        }
        TypePath.put(typePath, typeAnnotation);
        // Write type_index and reserve space for num_element_value_pairs.
        typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
        if (visible) {
            return lastCodeRuntimeVisibleTypeAnnotation =
                    new AnnotationWriter(
                            symbolTable,
                            /* useNamedValues = */ true,
                            typeAnnotation,
                            lastCodeRuntimeVisibleTypeAnnotation);
        } else {
            return lastCodeRuntimeInvisibleTypeAnnotation =
                    new AnnotationWriter(
                            symbolTable,
                            /* useNamedValues = */ true,
                            typeAnnotation,
                            lastCodeRuntimeInvisibleTypeAnnotation);
        }
    }

    @Override
    public void visitLineNumber(final int line, final Label start) {
        if (lineNumberTable == null) {
            lineNumberTable = new ByteVector();
        }
        ++lineNumberTableLength;
        lineNumberTable.putShort(start.bytecodeOffset);
        lineNumberTable.putShort(line);
    }

    @Override
    public void visitMaxs(final int maxStack, final int maxLocals) {
        if (compute == COMPUTE_ALL_FRAMES) {
            computeAllFrames();
        } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
            computeMaxStackAndLocal();
        } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
            this.maxStack = maxRelativeStackSize;
        } else {
            this.maxStack = maxStack;
            this.maxLocals = maxLocals;
        }
    }

    /** Computes all the stack map frames of the method, from scratch. */
    private void computeAllFrames() {
        // Complete the control flow graph with exception handler blocks.
        Handler handler = firstHandler;
        while (handler != null) {
            String catchTypeDescriptor =
                    handler.catchTypeDescriptor == null ? "java/lang/Throwable" : handler.catchTypeDescriptor;
            int catchType = Frame.getAbstractTypeFromInternalName(symbolTable, catchTypeDescriptor);
            // Mark handlerBlock as an exception handler.
            Label handlerBlock = handler.handlerPc.getCanonicalInstance();
            handlerBlock.flags |= Label.FLAG_JUMP_TARGET;
            // Add handlerBlock as a successor of all the basic blocks in the exception handler range.
            Label handlerRangeBlock = handler.startPc.getCanonicalInstance();
            Label handlerRangeEnd = handler.endPc.getCanonicalInstance();
            while (handlerRangeBlock != handlerRangeEnd) {
                handlerRangeBlock.outgoingEdges =
                        new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges);
                handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
            }
            handler = handler.nextHandler;
        }

        // Create and visit the first (implicit) frame.
        Frame firstFrame = firstBasicBlock.frame;
        firstFrame.setInputFrameFromDescriptor(symbolTable, accessFlags, descriptor, this.maxLocals);
        firstFrame.accept(this);

        // Fix point algorithm: add the first basic block to a list of blocks to process (i.e. blocks
        // whose stack map frame has changed) and, while there are blocks to process, remove one from
        // the list and update the stack map frames of its successor blocks in the control flow graph
        // (which might change them, in which case these blocks must be processed too, and are thus
        // added to the list of blocks to process). Also compute the maximum stack size of the method,
        // as a by-product.
        Label listOfBlocksToProcess = firstBasicBlock;
        listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST;
        int maxStackSize = 0;
        while (listOfBlocksToProcess != Label.EMPTY_LIST) {
            // Remove a basic block from the list of blocks to process.
            Label basicBlock = listOfBlocksToProcess;
            listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
            basicBlock.nextListElement = null;
            // By definition, basicBlock is reachable.
            basicBlock.flags |= Label.FLAG_REACHABLE;
            // Update the (absolute) maximum stack size.
            int maxBlockStackSize = basicBlock.frame.getInputStackSize() + basicBlock.outputStackMax;
            if (maxBlockStackSize > maxStackSize) {
                maxStackSize = maxBlockStackSize;
            }
            // Update the successor blocks of basicBlock in the control flow graph.
            Edge outgoingEdge = basicBlock.outgoingEdges;
            while (outgoingEdge != null) {
                Label successorBlock = outgoingEdge.successor.getCanonicalInstance();
                boolean successorBlockChanged =
                        basicBlock.frame.merge(symbolTable, successorBlock.frame, outgoingEdge.info);
                if (successorBlockChanged && successorBlock.nextListElement == null) {
                    // If successorBlock has changed it must be processed. Thus, if it is not already in the
                    // list of blocks to process, add it to this list.
                    successorBlock.nextListElement = listOfBlocksToProcess;
                    listOfBlocksToProcess = successorBlock;
                }
                outgoingEdge = outgoingEdge.nextEdge;
            }
        }

        // Loop over all the basic blocks and visit the stack map frames that must be stored in the
        // StackMapTable attribute. Also replace unreachable code with NOP* ATHROW, and remove it from
        // exception handler ranges.
        Label basicBlock = firstBasicBlock;
        while (basicBlock != null) {
            if ((basicBlock.flags & (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE))
                    == (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) {
                basicBlock.frame.accept(this);
            }
            if ((basicBlock.flags & Label.FLAG_REACHABLE) == 0) {
                // Find the start and end bytecode offsets of this unreachable block.
                Label nextBasicBlock = basicBlock.nextBasicBlock;
                int startOffset = basicBlock.bytecodeOffset;
                int endOffset = (nextBasicBlock == null ? code.length : nextBasicBlock.bytecodeOffset) - 1;
                if (endOffset >= startOffset) {
                    // Replace its instructions with NOP ... NOP ATHROW.
                    for (int i = startOffset; i < endOffset; ++i) {
                        code.data[i] = Opcodes.NOP;
                    }
                    code.data[endOffset] = (byte) Opcodes.ATHROW;
                    // Emit a frame for this unreachable block, with no local and a Throwable on the stack
                    // (so that the ATHROW could consume this Throwable if it were reachable).
                    int frameIndex = visitFrameStart(startOffset, /* numLocal = */ 0, /* numStack = */ 1);
                    currentFrame[frameIndex] =
                            Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable");
                    visitFrameEnd();
                    // Remove this unreachable basic block from the exception handler ranges.
                    firstHandler = Handler.removeRange(firstHandler, basicBlock, nextBasicBlock);
                    // The maximum stack size is now at least one, because of the Throwable declared above.
                    maxStackSize = Math.max(maxStackSize, 1);
                }
            }
            basicBlock = basicBlock.nextBasicBlock;
        }

        this.maxStack = maxStackSize;
    }

    /** Computes the maximum stack size of the method. */
    private void computeMaxStackAndLocal() {
        // Complete the control flow graph with exception handler blocks.
        Handler handler = firstHandler;
        while (handler != null) {
            Label handlerBlock = handler.handlerPc;
            Label handlerRangeBlock = handler.startPc;
            Label handlerRangeEnd = handler.endPc;
            // Add handlerBlock as a successor of all the basic blocks in the exception handler range.
            while (handlerRangeBlock != handlerRangeEnd) {
                if ((handlerRangeBlock.flags & Label.FLAG_SUBROUTINE_CALLER) == 0) {
                    handlerRangeBlock.outgoingEdges =
                            new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges);
                } else {
                    // If handlerRangeBlock is a JSR block, add handlerBlock after the first two outgoing
                    // edges to preserve the hypothesis about JSR block successors order (see
                    // {@link #visitJumpInsn}).
                    handlerRangeBlock.outgoingEdges.nextEdge.nextEdge =
                            new Edge(
                                    Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge);
                }
                handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
            }
            handler = handler.nextHandler;
        }

        // Complete the control flow graph with the successor blocks of subroutines, if needed.
        if (hasSubroutines) {
            // First step: find the subroutines. This step determines, for each basic block, to which
            // subroutine(s) it belongs. Start with the main "subroutine":
            short numSubroutines = 1;
            firstBasicBlock.markSubroutine(numSubroutines);
            // Then, mark the subroutines called by the main subroutine, then the subroutines called by
            // those called by the main subroutine, etc.
            for (short currentSubroutine = 1; currentSubroutine <= numSubroutines; ++currentSubroutine) {
                Label basicBlock = firstBasicBlock;
                while (basicBlock != null) {
                    if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0
                            && basicBlock.subroutineId == currentSubroutine) {
                        Label jsrTarget = basicBlock.outgoingEdges.nextEdge.successor;
                        if (jsrTarget.subroutineId == 0) {
                            // If this subroutine has not been marked yet, find its basic blocks.
                            jsrTarget.markSubroutine(++numSubroutines);
                        }
                    }
                    basicBlock = basicBlock.nextBasicBlock;
                }
            }
            // Second step: find the successors in the control flow graph of each subroutine basic block
            // 'r' ending with a RET instruction. These successors are the virtual successors of the basic
            // blocks ending with JSR instructions (see {@link #visitJumpInsn)} that can reach 'r'.
            Label basicBlock = firstBasicBlock;
            while (basicBlock != null) {
                if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) {
                    // By construction, jsr targets are stored in the second outgoing edge of basic blocks
                    // that ends with a jsr instruction (see {@link #FLAG_SUBROUTINE_CALLER}).
                    Label subroutine = basicBlock.outgoingEdges.nextEdge.successor;
                    subroutine.addSubroutineRetSuccessors(basicBlock);
                }
                basicBlock = basicBlock.nextBasicBlock;
            }
        }

        // Data flow algorithm: put the first basic block in a list of blocks to process (i.e. blocks
        // whose input stack size has changed) and, while there are blocks to process, remove one
        // from the list, update the input stack size of its successor blocks in the control flow
        // graph, and add these blocks to the list of blocks to process (if not already done).
        Label listOfBlocksToProcess = firstBasicBlock;
        listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST;
        int maxStackSize = maxStack;
        while (listOfBlocksToProcess != Label.EMPTY_LIST) {
            // Remove a basic block from the list of blocks to process. Note that we don't reset
            // basicBlock.nextListElement to null on purpose, to make sure we don't reprocess already
            // processed basic blocks.
            Label basicBlock = listOfBlocksToProcess;
            listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
            // Compute the (absolute) input stack size and maximum stack size of this block.
            int inputStackTop = basicBlock.inputStackSize;
            int maxBlockStackSize = inputStackTop + basicBlock.outputStackMax;
            // Update the absolute maximum stack size of the method.
            if (maxBlockStackSize > maxStackSize) {
                maxStackSize = maxBlockStackSize;
            }
            // Update the input stack size of the successor blocks of basicBlock in the control flow
            // graph, and add these blocks to the list of blocks to process, if not already done.
            Edge outgoingEdge = basicBlock.outgoingEdges;
            if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) {
                // Ignore the first outgoing edge of the basic blocks ending with a jsr: these are virtual
                // edges which lead to the instruction just after the jsr, and do not correspond to a
                // possible execution path (see {@link #visitJumpInsn} and
                // {@link Label#FLAG_SUBROUTINE_CALLER}).
                outgoingEdge = outgoingEdge.nextEdge;
            }
            while (outgoingEdge != null) {
                Label successorBlock = outgoingEdge.successor;
                if (successorBlock.nextListElement == null) {
                    successorBlock.inputStackSize =
                            (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info);
                    successorBlock.nextListElement = listOfBlocksToProcess;
                    listOfBlocksToProcess = successorBlock;
                }
                outgoingEdge = outgoingEdge.nextEdge;
            }
        }
        this.maxStack = maxStackSize;
    }

    @Override
    public void visitEnd() {
        // Nothing to do.
    }

    // -----------------------------------------------------------------------------------------------
    // Utility methods: control flow analysis algorithm
    // -----------------------------------------------------------------------------------------------

    /**
      * Adds a successor to {@link #currentBasicBlock} in the control flow graph.
      *
      * @param info information about the control flow edge to be added.
      * @param successor the successor block to be added to the current basic block.
      */
    private void addSuccessorToCurrentBasicBlock(final int info, final Label successor) {
        currentBasicBlock.outgoingEdges = new Edge(info, successor, currentBasicBlock.outgoingEdges);
    }

    /**
      * Ends the current basic block. This method must be used in the case where the current basic
      * block does not have any successor.
      *
      * <p>WARNING: this method must be called after the currently visited instruction has been put in
      * {@link #code} (if frames are computed, this method inserts a new Label to start a new basic
      * block after the current instruction).
      */
    private void endCurrentBasicBlockWithNoSuccessor() {
        if (compute == COMPUTE_ALL_FRAMES) {
            Label nextBasicBlock = new Label();
            nextBasicBlock.frame = new Frame(nextBasicBlock);
            nextBasicBlock.resolve(code.data, stackMapTableEntries, code.length);
            lastBasicBlock.nextBasicBlock = nextBasicBlock;
            lastBasicBlock = nextBasicBlock;
            currentBasicBlock = null;
        } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
            currentBasicBlock.outputStackMax = (short) maxRelativeStackSize;
            currentBasicBlock = null;
        }
    }

    // -----------------------------------------------------------------------------------------------
    // Utility methods: stack map frames
    // -----------------------------------------------------------------------------------------------

    /**
      * Starts the visit of a new stack map frame, stored in {@link #currentFrame}.
      *
      * @param offset the bytecode offset of the instruction to which the frame corresponds.
      * @param numLocal the number of local variables in the frame.
      * @param numStack the number of stack elements in the frame.
      * @return the index of the next element to be written in this frame.
      */
    int visitFrameStart(final int offset, final int numLocal, final int numStack) {
        int frameLength = 3 + numLocal + numStack;
        if (currentFrame == null || currentFrame.length < frameLength) {
            currentFrame = new int[frameLength];
        }
        currentFrame[0] = offset;
        currentFrame[1] = numLocal;
        currentFrame[2] = numStack;
        return 3;
    }

    /**
      * Sets an abstract type in {@link #currentFrame}.
      *
      * @param frameIndex the index of the element to be set in {@link #currentFrame}.
      * @param abstractType an abstract type.
      */
    void visitAbstractType(final int frameIndex, final int abstractType) {
        currentFrame[frameIndex] = abstractType;
    }

    /**
      * Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by
      * updating the StackMapTable number_of_entries (except if the current frame is the first one,
      * which is implicit in StackMapTable). Then resets {@link #currentFrame} to {@literal null}.
      */
    void visitFrameEnd() {
        if (previousFrame != null) {
            if (stackMapTableEntries == null) {
                stackMapTableEntries = new ByteVector();
            }
            putFrame();
            ++stackMapTableNumberOfEntries;
        }
        previousFrame = currentFrame;
        currentFrame = null;
    }

    /** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */
    private void putFrame() {
        final int numLocal = currentFrame[1];
        final int numStack = currentFrame[2];
        if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
            // Generate a StackMap attribute entry, which are always uncompressed.
            stackMapTableEntries.putShort(currentFrame[0]).putShort(numLocal);
            putAbstractTypes(3, 3 + numLocal);
            stackMapTableEntries.putShort(numStack);
            putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
            return;
        }
        final int offsetDelta =
                stackMapTableNumberOfEntries == 0
                        ? currentFrame[0]
                        : currentFrame[0] - previousFrame[0] - 1;
        final int previousNumlocal = previousFrame[1];
        final int numLocalDelta = numLocal - previousNumlocal;
        int type = Frame.FULL_FRAME;
        if (numStack == 0) {
            switch (numLocalDelta) {
                case -3:
                case -2:
                case -1:
                    type = Frame.CHOP_FRAME;
                    break;
                case 0:
                    type = offsetDelta < 64 ? Frame.SAME_FRAME : Frame.SAME_FRAME_EXTENDED;
                    break;
                case 1:
                case 2:
                case 3:
                    type = Frame.APPEND_FRAME;
                    break;
                default:
                    // Keep the FULL_FRAME type.
                    break;
            }
        } else if (numLocalDelta == 0 && numStack == 1) {
            type =
                    offsetDelta < 63
                            ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME
                            : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
        }
        if (type != Frame.FULL_FRAME) {
            // Verify if locals are the same as in the previous frame.
            int frameIndex = 3;
            for (int i = 0; i < previousNumlocal && i < numLocal; i++) {
                if (currentFrame[frameIndex] != previousFrame[frameIndex]) {
                    type = Frame.FULL_FRAME;
                    break;
                }
                frameIndex++;
            }
        }
        switch (type) {
            case Frame.SAME_FRAME:
                stackMapTableEntries.putByte(offsetDelta);
                break;
            case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME:
                stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
                putAbstractTypes(3 + numLocal, 4 + numLocal);
                break;
            case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
                stackMapTableEntries
                        .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
                        .putShort(offsetDelta);
                putAbstractTypes(3 + numLocal, 4 + numLocal);
                break;
            case Frame.SAME_FRAME_EXTENDED:
                stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
                break;
            case Frame.CHOP_FRAME:
                stackMapTableEntries
                        .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
                        .putShort(offsetDelta);
                break;
            case Frame.APPEND_FRAME:
                stackMapTableEntries
                        .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
                        .putShort(offsetDelta);
                putAbstractTypes(3 + previousNumlocal, 3 + numLocal);
                break;
            case Frame.FULL_FRAME:
            default:
                stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal);
                putAbstractTypes(3, 3 + numLocal);
                stackMapTableEntries.putShort(numStack);
                putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
                break;
        }
    }

    /**
      * Puts some abstract types of {@link #currentFrame} in {@link #stackMapTableEntries} , using the
      * JVMS verification_type_info format used in StackMapTable attributes.
      *
      * @param start index of the first type in {@link #currentFrame} to write.
      * @param end index of last type in {@link #currentFrame} to write (exclusive).
      */
    private void putAbstractTypes(final int start, final int end) {
        for (int i = start; i < end; ++i) {
            Frame.putAbstractType(symbolTable, currentFrame[i], stackMapTableEntries);
        }
    }

    /**
      * Puts the given public API frame element type in {@link #stackMapTableEntries} , using the JVMS
      * verification_type_info format used in StackMapTable attributes.
      *
      * @param type a frame element type described using the same format as in {@link
      *     MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
      *     Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or
      *     {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating
      *     a NEW instruction (for uninitialized types).
      */
    private void putFrameType(final Object type) {
        if (type instanceof Integer) {
            stackMapTableEntries.putByte(((Integer) type).intValue());
        } else if (type instanceof String) {
            stackMapTableEntries
                    .putByte(Frame.ITEM_OBJECT)
                    .putShort(symbolTable.addConstantClass((String) type).index);
        } else {
            stackMapTableEntries.putByte(Frame.ITEM_UNINITIALIZED);
            ((Label) type).put(stackMapTableEntries);
        }
    }

    // -----------------------------------------------------------------------------------------------
    // Utility methods
    // -----------------------------------------------------------------------------------------------

    /**
      * Returns whether the attributes of this method can be copied from the attributes of the given
      * method (assuming there is no method visitor between the given ClassReader and this
      * MethodWriter). This method should only be called just after this MethodWriter has been created,
      * and before any content is visited. It returns true if the attributes corresponding to the
      * constructor arguments (at most a Signature, an Exception, a Deprecated and a Synthetic
      * attribute) are the same as the corresponding attributes in the given method.
      *
      * @param source the source ClassReader from which the attributes of this method might be copied.
      * @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes
      *     of this method might be copied contains a Synthetic attribute.
      * @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes
      *     of this method might be copied contains a Deprecated attribute.
      * @param descriptorIndex the descriptor_index field of the method_info JVMS structure from which
      *     the attributes of this method might be copied.
      * @param signatureIndex the constant pool index contained in the Signature attribute of the
      *     method_info JVMS structure from which the attributes of this method might be copied, or 0.
      * @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info
      *     JVMS structure from which the attributes of this method might be copied, or 0.
      * @return whether the attributes of this method can be copied from the attributes of the
      *     method_info JVMS structure in 'source.b', between 'methodInfoOffset' and 'methodInfoOffset'
      *     + 'methodInfoLength'.
      */
    boolean canCopyMethodAttributes(
            final ClassReader source,
            final boolean hasSyntheticAttribute,
            final boolean hasDeprecatedAttribute,
            final int descriptorIndex,
            final int signatureIndex,
            final int exceptionsOffset) {
        // If the method descriptor has changed, with more locals than the max_locals field of the
        // original Code attribute, if any, then the original method attributes can't be copied. A
        // conservative check on the descriptor changes alone ensures this (being more precise is not
        // worth the additional complexity, because these cases should be rare -- if a transform changes
        // a method descriptor, most of the time it needs to change the method's code too).
        if (source != symbolTable.getSource()
                || descriptorIndex != this.descriptorIndex
                || signatureIndex != this.signatureIndex
                || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) {
            return false;
        }
        boolean needSyntheticAttribute =
                symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0;
        if (hasSyntheticAttribute != needSyntheticAttribute) {
            return false;
        }
        if (exceptionsOffset == 0) {
            if (numberOfExceptions != 0) {
                return false;
            }
        } else if (source.readUnsignedShort(exceptionsOffset) == numberOfExceptions) {
            int currentExceptionOffset = exceptionsOffset + 2;
            for (int i = 0; i < numberOfExceptions; ++i) {
                if (source.readUnsignedShort(currentExceptionOffset) != exceptionIndexTable[i]) {
                    return false;
                }
                currentExceptionOffset += 2;
            }
        }
        return true;
    }

    /**
      * Sets the source from which the attributes of this method will be copied.
      *
      * @param methodInfoOffset the offset in 'symbolTable.getSource()' of the method_info JVMS
      *     structure from which the attributes of this method will be copied.
      * @param methodInfoLength the length in 'symbolTable.getSource()' of the method_info JVMS
      *     structure from which the attributes of this method will be copied.
      */
    void setMethodAttributesSource(final int methodInfoOffset, final int methodInfoLength) {
        // Don't copy the attributes yet, instead store their location in the source class reader so
        // they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes
        // of the method_info JVMS structure.
        this.sourceOffset = methodInfoOffset + 6;
        this.sourceLength = methodInfoLength - 6;
    }

    /**
      * Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the
      * names of the attributes of this method in the constant pool.
      *
      * @return the size in bytes of the method_info JVMS structure.
      */
    int computeMethodInfoSize() {
        // If this method_info must be copied from an existing one, the size computation is trivial.
        if (sourceOffset != 0) {
            // sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index.
            return 6 + sourceLength;
        }
        // 2 bytes each for access_flags, name_index, descriptor_index and attributes_count.
        int size = 8;
        // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
        if (code.length > 0) {
            if (code.length > 65535) {
                throw new MethodTooLargeException(
                        symbolTable.getClassName(), name, descriptor, code.length);
            }
            symbolTable.addConstantUtf8(Constants.CODE);
            // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
            // max_locals, code_length and attributes_count, plus the bytecode and the exception table.
            size += 16 + code.length + Handler.getExceptionTableSize(firstHandler);
            if (stackMapTableEntries != null) {
                boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
                symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap");
                // 6 header bytes and 2 bytes for number_of_entries.
                size += 8 + stackMapTableEntries.length;
            }
            if (lineNumberTable != null) {
                symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE);
                // 6 header bytes and 2 bytes for line_number_table_length.
                size += 8 + lineNumberTable.length;
            }
            if (localVariableTable != null) {
                symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE);
                // 6 header bytes and 2 bytes for local_variable_table_length.
                size += 8 + localVariableTable.length;
            }
            if (localVariableTypeTable != null) {
                symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE);
                // 6 header bytes and 2 bytes for local_variable_type_table_length.
                size += 8 + localVariableTypeTable.length;
            }
            if (lastCodeRuntimeVisibleTypeAnnotation != null) {
                size +=
                        lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
                                Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
            }
            if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
                size +=
                        lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
                                Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
            }
            if (firstCodeAttribute != null) {
                size +=
                        firstCodeAttribute.computeAttributesSize(
                                symbolTable, code.data, code.length, maxStack, maxLocals);
            }
        }
        if (numberOfExceptions > 0) {
            symbolTable.addConstantUtf8(Constants.EXCEPTIONS);
            size += 8 + 2 * numberOfExceptions;
        }
        size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex);
        size +=
                AnnotationWriter.computeAnnotationsSize(
                        lastRuntimeVisibleAnnotation,
                        lastRuntimeInvisibleAnnotation,
                        lastRuntimeVisibleTypeAnnotation,
                        lastRuntimeInvisibleTypeAnnotation);
        if (lastRuntimeVisibleParameterAnnotations != null) {
            size +=
                    AnnotationWriter.computeParameterAnnotationsSize(
                            Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
                            lastRuntimeVisibleParameterAnnotations,
                            visibleAnnotableParameterCount == 0
                                    ? lastRuntimeVisibleParameterAnnotations.length
                                    : visibleAnnotableParameterCount);
        }
        if (lastRuntimeInvisibleParameterAnnotations != null) {
            size +=
                    AnnotationWriter.computeParameterAnnotationsSize(
                            Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS,
                            lastRuntimeInvisibleParameterAnnotations,
                            invisibleAnnotableParameterCount == 0
                                    ? lastRuntimeInvisibleParameterAnnotations.length
                                    : invisibleAnnotableParameterCount);
        }
        if (defaultValue != null) {
            symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT);
            size += 6 + defaultValue.length;
        }
        if (parameters != null) {
            symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS);
            // 6 header bytes and 1 byte for parameters_count.
            size += 7 + parameters.length;
        }
        if (firstAttribute != null) {
            size += firstAttribute.computeAttributesSize(symbolTable);
        }
        return size;
    }

    /**
      * Puts the content of the method_info JVMS structure generated by this MethodWriter into the
      * given ByteVector.
      *
      * @param output where the method_info structure must be put.
      */
    void putMethodInfo(final ByteVector output) {
        boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
        int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
        output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
        // If this method_info must be copied from an existing one, copy it now and return early.
        if (sourceOffset != 0) {
            output.putByteArray(symbolTable.getSource().classFileBuffer, sourceOffset, sourceLength);
            return;
        }
        // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
        int attributeCount = 0;
        if (code.length > 0) {
            ++attributeCount;
        }
        if (numberOfExceptions > 0) {
            ++attributeCount;
        }
        if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
            ++attributeCount;
        }
        if (signatureIndex != 0) {
            ++attributeCount;
        }
        if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
            ++attributeCount;
        }
        if (lastRuntimeVisibleAnnotation != null) {
            ++attributeCount;
        }
        if (lastRuntimeInvisibleAnnotation != null) {
            ++attributeCount;
        }
        if (lastRuntimeVisibleParameterAnnotations != null) {
            ++attributeCount;
        }
        if (lastRuntimeInvisibleParameterAnnotations != null) {
            ++attributeCount;
        }
        if (lastRuntimeVisibleTypeAnnotation != null) {
            ++attributeCount;
        }
        if (lastRuntimeInvisibleTypeAnnotation != null) {
            ++attributeCount;
        }
        if (defaultValue != null) {
            ++attributeCount;
        }
        if (parameters != null) {
            ++attributeCount;
        }
        if (firstAttribute != null) {
            attributeCount += firstAttribute.getAttributeCount();
        }
        // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
        output.putShort(attributeCount);
        if (code.length > 0) {
            // 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and
            // attributes_count, plus the bytecode and the exception table.
            int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler);
            int codeAttributeCount = 0;
            if (stackMapTableEntries != null) {
                // 6 header bytes and 2 bytes for number_of_entries.
                size += 8 + stackMapTableEntries.length;
                ++codeAttributeCount;
            }
            if (lineNumberTable != null) {
                // 6 header bytes and 2 bytes for line_number_table_length.
                size += 8 + lineNumberTable.length;
                ++codeAttributeCount;
            }
            if (localVariableTable != null) {
                // 6 header bytes and 2 bytes for local_variable_table_length.
                size += 8 + localVariableTable.length;
                ++codeAttributeCount;
            }
            if (localVariableTypeTable != null) {
                // 6 header bytes and 2 bytes for local_variable_type_table_length.
                size += 8 + localVariableTypeTable.length;
                ++codeAttributeCount;
            }
            if (lastCodeRuntimeVisibleTypeAnnotation != null) {
                size +=
                        lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
                                Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
                ++codeAttributeCount;
            }
            if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
                size +=
                        lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
                                Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
                ++codeAttributeCount;
            }
            if (firstCodeAttribute != null) {
                size +=
                        firstCodeAttribute.computeAttributesSize(
                                symbolTable, code.data, code.length, maxStack, maxLocals);
                codeAttributeCount += firstCodeAttribute.getAttributeCount();
            }
            output
                    .putShort(symbolTable.addConstantUtf8(Constants.CODE))
                    .putInt(size)
                    .putShort(maxStack)
                    .putShort(maxLocals)
                    .putInt(code.length)
                    .putByteArray(code.data, 0, code.length);
            Handler.putExceptionTable(firstHandler, output);
            output.putShort(codeAttributeCount);
            if (stackMapTableEntries != null) {
                boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
                output
                        .putShort(
                                symbolTable.addConstantUtf8(
                                        useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap"))
                        .putInt(2 + stackMapTableEntries.length)
                        .putShort(stackMapTableNumberOfEntries)
                        .putByteArray(stackMapTableEntries.data, 0, stackMapTableEntries.length);
            }
            if (lineNumberTable != null) {
                output
                        .putShort(symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE))
                        .putInt(2 + lineNumberTable.length)
                        .putShort(lineNumberTableLength)
                        .putByteArray(lineNumberTable.data, 0, lineNumberTable.length);
            }
            if (localVariableTable != null) {
                output
                        .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE))
                        .putInt(2 + localVariableTable.length)
                        .putShort(localVariableTableLength)
                        .putByteArray(localVariableTable.data, 0, localVariableTable.length);
            }
            if (localVariableTypeTable != null) {
                output
                        .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE))
                        .putInt(2 + localVariableTypeTable.length)
                        .putShort(localVariableTypeTableLength)
                        .putByteArray(localVariableTypeTable.data, 0, localVariableTypeTable.length);
            }
            if (lastCodeRuntimeVisibleTypeAnnotation != null) {
                lastCodeRuntimeVisibleTypeAnnotation.putAnnotations(
                        symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
            }
            if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
                lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations(
                        symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
            }
            if (firstCodeAttribute != null) {
                firstCodeAttribute.putAttributes(
                        symbolTable, code.data, code.length, maxStack, maxLocals, output);
            }
        }
        if (numberOfExceptions > 0) {
            output
                    .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS))
                    .putInt(2 + 2 * numberOfExceptions)
                    .putShort(numberOfExceptions);
            for (int exceptionIndex : exceptionIndexTable) {
                output.putShort(exceptionIndex);
            }
        }
        Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output);
        AnnotationWriter.putAnnotations(
                symbolTable,
                lastRuntimeVisibleAnnotation,
                lastRuntimeInvisibleAnnotation,
                lastRuntimeVisibleTypeAnnotation,
                lastRuntimeInvisibleTypeAnnotation,
                output);
        if (lastRuntimeVisibleParameterAnnotations != null) {
            AnnotationWriter.putParameterAnnotations(
                    symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS),
                    lastRuntimeVisibleParameterAnnotations,
                    visibleAnnotableParameterCount == 0
                            ? lastRuntimeVisibleParameterAnnotations.length
                            : visibleAnnotableParameterCount,
                    output);
        }
        if (lastRuntimeInvisibleParameterAnnotations != null) {
            AnnotationWriter.putParameterAnnotations(
                    symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS),
                    lastRuntimeInvisibleParameterAnnotations,
                    invisibleAnnotableParameterCount == 0
                            ? lastRuntimeInvisibleParameterAnnotations.length
                            : invisibleAnnotableParameterCount,
                    output);
        }
        if (defaultValue != null) {
            output
                    .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT))
                    .putInt(defaultValue.length)
                    .putByteArray(defaultValue.data, 0, defaultValue.length);
        }
        if (parameters != null) {
            output
                    .putShort(symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS))
                    .putInt(1 + parameters.length)
                    .putByte(parametersCount)
                    .putByteArray(parameters.data, 0, parameters.length);
        }
        if (firstAttribute != null) {
            firstAttribute.putAttributes(symbolTable, output);
        }
    }

    /**
      * Collects the attributes of this method into the given set of attribute prototypes.
      *
      * @param attributePrototypes a set of attribute prototypes.
      */
    final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
        attributePrototypes.addAttributes(firstAttribute);
        attributePrototypes.addAttributes(firstCodeAttribute);
    }
}
