// 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} 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} or {@link Frame#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(Opcodes.ASM7);
		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.getArgumentTypes(descriptor).length];
			}
			return lastRuntimeVisibleParameterAnnotations[parameter] = AnnotationWriter.create(symbolTable,
					annotationDescriptor, lastRuntimeVisibleParameterAnnotations[parameter]);
		} else {
			if (lastRuntimeInvisibleParameterAnnotations == null) {
				lastRuntimeInvisibleParameterAnnotations = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
			}
			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 {
			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 var) {
		lastBytecodeOffset = code.length;
		// Add the instruction to the bytecode of the method.
		if (var < 4 && opcode != Opcodes.RET) {
			int optimizedOpcode;
			if (opcode < Opcodes.ISTORE) {
				optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var;
			} else {
				optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var;
			}
			code.putByte(optimizedOpcode);
		} else if (var >= 256) {
			code.putByte(Constants.WIDE).put12(opcode, var);
		} else {
			code.put11(opcode, var);
		}
		// 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, var, 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 = var + 2;
			} else {
				currentMaxLocals = var + 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, 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 |= (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 |= (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 var, final int increment) {
		lastBytecodeOffset = code.length;
		// Add the instruction to the bytecode of the method.
		if ((var > 255) || (increment > 127) || (increment < -128)) {
			code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment);
		} else {
			code.putByte(Opcodes.IINC).put11(var, 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, var, null, null);
		}
		if (compute != COMPUTE_NOTHING) {
			int currentMaxLocals = var + 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, 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).putShort(((Label) type).bytecodeOffset);
		}
	}

	// -----------------------------------------------------------------------------------------------
	// 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);
	}
}
