// 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.springframework.asm; /** * A {@link MethodVisitor} that generates a corresponding 'method_info' structure, as defined in the * Java Virtual Machine Specification (JVMS). * * @see JVMS * 4.6 * @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 JVMS 6 */ 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}. * *
WARNING: this list stores the attributes in the reverse 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}. * *
WARNING: this list stores the attributes in the reverse order of their visit.
* firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
* #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the
* reverse order specified by the user.
*/
private Attribute firstAttribute;
// -----------------------------------------------------------------------------------------------
// Fields used to compute the maximum stack size and number of locals, and the stack map frames
// -----------------------------------------------------------------------------------------------
/**
* Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link
* #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link
* #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
*/
private final int compute;
/**
* The first basic block of the method. The next ones (in bytecode offset order) can be accessed
* with the {@link Label#nextBasicBlock} field.
*/
private Label firstBasicBlock;
/**
* The last basic block of the method (in bytecode offset order). This field is updated each time
* a basic block is encountered, and is used to append it at the end of the basic block list.
*/
private Label lastBasicBlock;
/**
* The current basic block, i.e. the basic block of the last visited instruction. When {@link
* #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this
* field is {@literal null} for unreachable code. When {@link #compute} is equal to {@link
* #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays
* unchanged throughout the whole method (i.e. the whole code is seen as a single basic block;
* indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame -
* and the maximum stack size as well - without using any control flow graph).
*/
private Label currentBasicBlock;
/**
* The relative stack size after the last visited instruction. This size is relative to the
* beginning of {@link #currentBasicBlock}, i.e. the true stack size after the last visited
* instruction is equal to the {@link Label#inputStackSize} of the current basic block plus {@link
* #relativeStackSize}. When {@link #compute} is equal to {@link
* #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
* the method, so this relative size is also equal to the absolute stack size after the last
* visited instruction.
*/
private int relativeStackSize;
/**
* The maximum relative stack size after the last visited instruction. This size is relative to
* the beginning of {@link #currentBasicBlock}, i.e. the true maximum stack size after the last
* visited instruction is equal to the {@link Label#inputStackSize} of the current basic block
* plus {@link #maxRelativeStackSize}.When {@link #compute} is equal to {@link
* #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
* the method, so this relative size is also equal to the absolute maximum stack size after the
* last visited instruction.
*/
private int maxRelativeStackSize;
/** The number of local variables in the last visited stack map frame. */
private int currentLocals;
/** The bytecode offset of the last frame that was written in {@link #stackMapTableEntries}. */
private int previousFrameOffset;
/**
* The last frame that was written in {@link #stackMapTableEntries}. This field has the same
* format as {@link #currentFrame}.
*/
private int[] previousFrame;
/**
* The current stack map frame. The first element contains the bytecode offset of the instruction
* to which the frame corresponds, the second element is the number of locals and the third one is
* the number of stack elements. The local variables start at index 3 and are followed by the
* operand stack elements. In summary frame[0] = offset, frame[1] = numLocal, frame[2] = numStack.
* Local variables and operand stack entries contain abstract types, as defined in {@link Frame},
* but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND}, {@link
* Frame#UNINITIALIZED_KIND} or {@link Frame#FORWARD_UNINITIALIZED_KIND} abstract types. Long and
* double types use only one array entry.
*/
private int[] currentFrame;
/** Whether this method contains subroutines. */
private boolean hasSubroutines;
// -----------------------------------------------------------------------------------------------
// Other miscellaneous status fields
// -----------------------------------------------------------------------------------------------
/** Whether the bytecode of this method contains ASM specific instructions. */
private boolean hasAsmInstructions;
/**
* The start offset of the last visited instruction. Used to set the offset field of type
* annotations of type 'offset_target' (see JVMS
* 4.7.20.1).
*/
private int lastBytecodeOffset;
/**
* The offset in bytes in {@link SymbolTable#getSource} from which the method_info for this method
* (excluding its first 6 bytes) must be copied, or 0.
*/
private int sourceOffset;
/**
* The length in bytes in {@link SymbolTable#getSource} which must be copied to get the
* method_info for this method (excluding its first 6 bytes for access_flags, name_index and
* descriptor_index).
*/
private int sourceLength;
// -----------------------------------------------------------------------------------------------
// Constructor and accessors
// -----------------------------------------------------------------------------------------------
/**
* Constructs a new {@link MethodWriter}.
*
* @param symbolTable where the constants used in this AnnotationWriter must be stored.
* @param access the method's access flags (see {@link Opcodes}).
* @param name the method's name.
* @param descriptor the method's descriptor (see {@link Type}).
* @param signature the method's signature. May be {@literal null}.
* @param exceptions the internal names of the method's exceptions. May be {@literal null}.
* @param compute indicates what must be computed (see #compute).
*/
MethodWriter(
final SymbolTable symbolTable,
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions,
final int compute) {
super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.accessFlags = " WARNING: this method must be called after the currently visited instruction has been put in
* {@link #code} (if frames are computed, this method inserts a new Label to start a new basic
* block after the current instruction).
*/
private void endCurrentBasicBlockWithNoSuccessor() {
if (compute == COMPUTE_ALL_FRAMES) {
Label nextBasicBlock = new Label();
nextBasicBlock.frame = new Frame(nextBasicBlock);
nextBasicBlock.resolve(code.data, stackMapTableEntries, code.length);
lastBasicBlock.nextBasicBlock = nextBasicBlock;
lastBasicBlock = nextBasicBlock;
currentBasicBlock = null;
} else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
currentBasicBlock.outputStackMax = (short) maxRelativeStackSize;
currentBasicBlock = null;
}
}
// -----------------------------------------------------------------------------------------------
// Utility methods: stack map frames
// -----------------------------------------------------------------------------------------------
/**
* Starts the visit of a new stack map frame, stored in {@link #currentFrame}.
*
* @param offset the bytecode offset of the instruction to which the frame corresponds.
* @param numLocal the number of local variables in the frame.
* @param numStack the number of stack elements in the frame.
* @return the index of the next element to be written in this frame.
*/
int visitFrameStart(final int offset, final int numLocal, final int numStack) {
int frameLength = 3 + numLocal + numStack;
if (currentFrame == null || currentFrame.length < frameLength) {
currentFrame = new int[frameLength];
}
currentFrame[0] = offset;
currentFrame[1] = numLocal;
currentFrame[2] = numStack;
return 3;
}
/**
* Sets an abstract type in {@link #currentFrame}.
*
* @param frameIndex the index of the element to be set in {@link #currentFrame}.
* @param abstractType an abstract type.
*/
void visitAbstractType(final int frameIndex, final int abstractType) {
currentFrame[frameIndex] = abstractType;
}
/**
* Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by
* updating the StackMapTable number_of_entries (except if the current frame is the first one,
* which is implicit in StackMapTable). Then resets {@link #currentFrame} to {@literal null}.
*/
void visitFrameEnd() {
if (previousFrame != null) {
if (stackMapTableEntries == null) {
stackMapTableEntries = new ByteVector();
}
putFrame();
++stackMapTableNumberOfEntries;
}
previousFrame = currentFrame;
currentFrame = null;
}
/** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */
private void putFrame() {
final int numLocal = currentFrame[1];
final int numStack = currentFrame[2];
if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
// Generate a StackMap attribute entry, which are always uncompressed.
stackMapTableEntries.putShort(currentFrame[0]).putShort(numLocal);
putAbstractTypes(3, 3 + numLocal);
stackMapTableEntries.putShort(numStack);
putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
return;
}
final int offsetDelta =
stackMapTableNumberOfEntries == 0
? currentFrame[0]
: currentFrame[0] - previousFrame[0] - 1;
final int previousNumlocal = previousFrame[1];
final int numLocalDelta = numLocal - previousNumlocal;
int type = Frame.FULL_FRAME;
if (numStack == 0) {
switch (numLocalDelta) {
case -3:
case -2:
case -1:
type = Frame.CHOP_FRAME;
break;
case 0:
type = offsetDelta < 64 ? Frame.SAME_FRAME : Frame.SAME_FRAME_EXTENDED;
break;
case 1:
case 2:
case 3:
type = Frame.APPEND_FRAME;
break;
default:
// Keep the FULL_FRAME type.
break;
}
} else if (numLocalDelta == 0 && numStack == 1) {
type =
offsetDelta < 63
? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME
: Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
}
if (type != Frame.FULL_FRAME) {
// Verify if locals are the same as in the previous frame.
int frameIndex = 3;
for (int i = 0; i < previousNumlocal && i < numLocal; i++) {
if (currentFrame[frameIndex] != previousFrame[frameIndex]) {
type = Frame.FULL_FRAME;
break;
}
frameIndex++;
}
}
switch (type) {
case Frame.SAME_FRAME:
stackMapTableEntries.putByte(offsetDelta);
break;
case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME:
stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
putAbstractTypes(3 + numLocal, 4 + numLocal);
break;
case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
stackMapTableEntries
.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
.putShort(offsetDelta);
putAbstractTypes(3 + numLocal, 4 + numLocal);
break;
case Frame.SAME_FRAME_EXTENDED:
stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
break;
case Frame.CHOP_FRAME:
stackMapTableEntries
.putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
.putShort(offsetDelta);
break;
case Frame.APPEND_FRAME:
stackMapTableEntries
.putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
.putShort(offsetDelta);
putAbstractTypes(3 + previousNumlocal, 3 + numLocal);
break;
case Frame.FULL_FRAME:
default:
stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal);
putAbstractTypes(3, 3 + numLocal);
stackMapTableEntries.putShort(numStack);
putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
break;
}
}
/**
* Puts some abstract types of {@link #currentFrame} in {@link #stackMapTableEntries} , using the
* JVMS verification_type_info format used in StackMapTable attributes.
*
* @param start index of the first type in {@link #currentFrame} to write.
* @param end index of last type in {@link #currentFrame} to write (exclusive).
*/
private void putAbstractTypes(final int start, final int end) {
for (int i = start; i < end; ++i) {
Frame.putAbstractType(symbolTable, currentFrame[i], stackMapTableEntries);
}
}
/**
* Puts the given public API frame element type in {@link #stackMapTableEntries} , using the JVMS
* verification_type_info format used in StackMapTable attributes.
*
* @param type a frame element type described using the same format as in {@link
* MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
* Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or
* {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating
* a NEW instruction (for uninitialized types).
*/
private void putFrameType(final Object type) {
if (type instanceof Integer) {
stackMapTableEntries.putByte(((Integer) type).intValue());
} else if (type instanceof String) {
stackMapTableEntries
.putByte(Frame.ITEM_OBJECT)
.putShort(symbolTable.addConstantClass((String) type).index);
} else {
stackMapTableEntries.putByte(Frame.ITEM_UNINITIALIZED);
((Label) type).put(stackMapTableEntries);
}
}
// -----------------------------------------------------------------------------------------------
// Utility methods
// -----------------------------------------------------------------------------------------------
/**
* Returns whether the attributes of this method can be copied from the attributes of the given
* method (assuming there is no method visitor between the given ClassReader and this
* MethodWriter). This method should only be called just after this MethodWriter has been created,
* and before any content is visited. It returns true if the attributes corresponding to the
* constructor arguments (at most a Signature, an Exception, a Deprecated and a Synthetic
* attribute) are the same as the corresponding attributes in the given method.
*
* @param source the source ClassReader from which the attributes of this method might be copied.
* @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes
* of this method might be copied contains a Synthetic attribute.
* @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes
* of this method might be copied contains a Deprecated attribute.
* @param descriptorIndex the descriptor_index field of the method_info JVMS structure from which
* the attributes of this method might be copied.
* @param signatureIndex the constant pool index contained in the Signature attribute of the
* method_info JVMS structure from which the attributes of this method might be copied, or 0.
* @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info
* JVMS structure from which the attributes of this method might be copied, or 0.
* @return whether the attributes of this method can be copied from the attributes of the
* method_info JVMS structure in 'source.b', between 'methodInfoOffset' and 'methodInfoOffset'
* + 'methodInfoLength'.
*/
boolean canCopyMethodAttributes(
final ClassReader source,
final boolean hasSyntheticAttribute,
final boolean hasDeprecatedAttribute,
final int descriptorIndex,
final int signatureIndex,
final int exceptionsOffset) {
// If the method descriptor has changed, with more locals than the max_locals field of the
// original Code attribute, if any, then the original method attributes can't be copied. A
// conservative check on the descriptor changes alone ensures this (being more precise is not
// worth the additional complexity, because these cases should be rare -- if a transform changes
// a method descriptor, most of the time it needs to change the method's code too).
if (source != symbolTable.getSource()
|| descriptorIndex != this.descriptorIndex
|| signatureIndex != this.signatureIndex
|| hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) {
return false;
}
boolean needSyntheticAttribute =
symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0;
if (hasSyntheticAttribute != needSyntheticAttribute) {
return false;
}
if (exceptionsOffset == 0) {
if (numberOfExceptions != 0) {
return false;
}
} else if (source.readUnsignedShort(exceptionsOffset) == numberOfExceptions) {
int currentExceptionOffset = exceptionsOffset + 2;
for (int i = 0; i < numberOfExceptions; ++i) {
if (source.readUnsignedShort(currentExceptionOffset) != exceptionIndexTable[i]) {
return false;
}
currentExceptionOffset += 2;
}
}
return true;
}
/**
* Sets the source from which the attributes of this method will be copied.
*
* @param methodInfoOffset the offset in 'symbolTable.getSource()' of the method_info JVMS
* structure from which the attributes of this method will be copied.
* @param methodInfoLength the length in 'symbolTable.getSource()' of the method_info JVMS
* structure from which the attributes of this method will be copied.
*/
void setMethodAttributesSource(final int methodInfoOffset, final int methodInfoLength) {
// Don't copy the attributes yet, instead store their location in the source class reader so
// they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes
// of the method_info JVMS structure.
this.sourceOffset = methodInfoOffset + 6;
this.sourceLength = methodInfoLength - 6;
}
/**
* Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the
* names of the attributes of this method in the constant pool.
*
* @return the size in bytes of the method_info JVMS structure.
*/
int computeMethodInfoSize() {
// If this method_info must be copied from an existing one, the size computation is trivial.
if (sourceOffset != 0) {
// sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index.
return 6 + sourceLength;
}
// 2 bytes each for access_flags, name_index, descriptor_index and attributes_count.
int size = 8;
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
if (code.length > 0) {
if (code.length > 65535) {
throw new MethodTooLargeException(
symbolTable.getClassName(), name, descriptor, code.length);
}
symbolTable.addConstantUtf8(Constants.CODE);
// The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
// max_locals, code_length and attributes_count, plus the bytecode and the exception table.
size += 16 + code.length + Handler.getExceptionTableSize(firstHandler);
if (stackMapTableEntries != null) {
boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap");
// 6 header bytes and 2 bytes for number_of_entries.
size += 8 + stackMapTableEntries.length;
}
if (lineNumberTable != null) {
symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE);
// 6 header bytes and 2 bytes for line_number_table_length.
size += 8 + lineNumberTable.length;
}
if (localVariableTable != null) {
symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE);
// 6 header bytes and 2 bytes for local_variable_table_length.
size += 8 + localVariableTable.length;
}
if (localVariableTypeTable != null) {
symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE);
// 6 header bytes and 2 bytes for local_variable_type_table_length.
size += 8 + localVariableTypeTable.length;
}
if (lastCodeRuntimeVisibleTypeAnnotation != null) {
size +=
lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
}
if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
size +=
lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
}
if (firstCodeAttribute != null) {
size +=
firstCodeAttribute.computeAttributesSize(
symbolTable, code.data, code.length, maxStack, maxLocals);
}
}
if (numberOfExceptions > 0) {
symbolTable.addConstantUtf8(Constants.EXCEPTIONS);
size += 8 + 2 * numberOfExceptions;
}
size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex);
size +=
AnnotationWriter.computeAnnotationsSize(
lastRuntimeVisibleAnnotation,
lastRuntimeInvisibleAnnotation,
lastRuntimeVisibleTypeAnnotation,
lastRuntimeInvisibleTypeAnnotation);
if (lastRuntimeVisibleParameterAnnotations != null) {
size +=
AnnotationWriter.computeParameterAnnotationsSize(
Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
lastRuntimeVisibleParameterAnnotations,
visibleAnnotableParameterCount == 0
? lastRuntimeVisibleParameterAnnotations.length
: visibleAnnotableParameterCount);
}
if (lastRuntimeInvisibleParameterAnnotations != null) {
size +=
AnnotationWriter.computeParameterAnnotationsSize(
Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS,
lastRuntimeInvisibleParameterAnnotations,
invisibleAnnotableParameterCount == 0
? lastRuntimeInvisibleParameterAnnotations.length
: invisibleAnnotableParameterCount);
}
if (defaultValue != null) {
symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT);
size += 6 + defaultValue.length;
}
if (parameters != null) {
symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS);
// 6 header bytes and 1 byte for parameters_count.
size += 7 + parameters.length;
}
if (firstAttribute != null) {
size += firstAttribute.computeAttributesSize(symbolTable);
}
return size;
}
/**
* Puts the content of the method_info JVMS structure generated by this MethodWriter into the
* given ByteVector.
*
* @param output where the method_info structure must be put.
*/
void putMethodInfo(final ByteVector output) {
boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
// If this method_info must be copied from an existing one, copy it now and return early.
if (sourceOffset != 0) {
output.putByteArray(symbolTable.getSource().classFileBuffer, sourceOffset, sourceLength);
return;
}
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
int attributeCount = 0;
if (code.length > 0) {
++attributeCount;
}
if (numberOfExceptions > 0) {
++attributeCount;
}
if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
++attributeCount;
}
if (signatureIndex != 0) {
++attributeCount;
}
if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
++attributeCount;
}
if (lastRuntimeVisibleAnnotation != null) {
++attributeCount;
}
if (lastRuntimeInvisibleAnnotation != null) {
++attributeCount;
}
if (lastRuntimeVisibleParameterAnnotations != null) {
++attributeCount;
}
if (lastRuntimeInvisibleParameterAnnotations != null) {
++attributeCount;
}
if (lastRuntimeVisibleTypeAnnotation != null) {
++attributeCount;
}
if (lastRuntimeInvisibleTypeAnnotation != null) {
++attributeCount;
}
if (defaultValue != null) {
++attributeCount;
}
if (parameters != null) {
++attributeCount;
}
if (firstAttribute != null) {
attributeCount += firstAttribute.getAttributeCount();
}
// For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
output.putShort(attributeCount);
if (code.length > 0) {
// 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and
// attributes_count, plus the bytecode and the exception table.
int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler);
int codeAttributeCount = 0;
if (stackMapTableEntries != null) {
// 6 header bytes and 2 bytes for number_of_entries.
size += 8 + stackMapTableEntries.length;
++codeAttributeCount;
}
if (lineNumberTable != null) {
// 6 header bytes and 2 bytes for line_number_table_length.
size += 8 + lineNumberTable.length;
++codeAttributeCount;
}
if (localVariableTable != null) {
// 6 header bytes and 2 bytes for local_variable_table_length.
size += 8 + localVariableTable.length;
++codeAttributeCount;
}
if (localVariableTypeTable != null) {
// 6 header bytes and 2 bytes for local_variable_type_table_length.
size += 8 + localVariableTypeTable.length;
++codeAttributeCount;
}
if (lastCodeRuntimeVisibleTypeAnnotation != null) {
size +=
lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
++codeAttributeCount;
}
if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
size +=
lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
++codeAttributeCount;
}
if (firstCodeAttribute != null) {
size +=
firstCodeAttribute.computeAttributesSize(
symbolTable, code.data, code.length, maxStack, maxLocals);
codeAttributeCount += firstCodeAttribute.getAttributeCount();
}
output
.putShort(symbolTable.addConstantUtf8(Constants.CODE))
.putInt(size)
.putShort(maxStack)
.putShort(maxLocals)
.putInt(code.length)
.putByteArray(code.data, 0, code.length);
Handler.putExceptionTable(firstHandler, output);
output.putShort(codeAttributeCount);
if (stackMapTableEntries != null) {
boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
output
.putShort(
symbolTable.addConstantUtf8(
useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap"))
.putInt(2 + stackMapTableEntries.length)
.putShort(stackMapTableNumberOfEntries)
.putByteArray(stackMapTableEntries.data, 0, stackMapTableEntries.length);
}
if (lineNumberTable != null) {
output
.putShort(symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE))
.putInt(2 + lineNumberTable.length)
.putShort(lineNumberTableLength)
.putByteArray(lineNumberTable.data, 0, lineNumberTable.length);
}
if (localVariableTable != null) {
output
.putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE))
.putInt(2 + localVariableTable.length)
.putShort(localVariableTableLength)
.putByteArray(localVariableTable.data, 0, localVariableTable.length);
}
if (localVariableTypeTable != null) {
output
.putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE))
.putInt(2 + localVariableTypeTable.length)
.putShort(localVariableTypeTableLength)
.putByteArray(localVariableTypeTable.data, 0, localVariableTypeTable.length);
}
if (lastCodeRuntimeVisibleTypeAnnotation != null) {
lastCodeRuntimeVisibleTypeAnnotation.putAnnotations(
symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
}
if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations(
symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
}
if (firstCodeAttribute != null) {
firstCodeAttribute.putAttributes(
symbolTable, code.data, code.length, maxStack, maxLocals, output);
}
}
if (numberOfExceptions > 0) {
output
.putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS))
.putInt(2 + 2 * numberOfExceptions)
.putShort(numberOfExceptions);
for (int exceptionIndex : exceptionIndexTable) {
output.putShort(exceptionIndex);
}
}
Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output);
AnnotationWriter.putAnnotations(
symbolTable,
lastRuntimeVisibleAnnotation,
lastRuntimeInvisibleAnnotation,
lastRuntimeVisibleTypeAnnotation,
lastRuntimeInvisibleTypeAnnotation,
output);
if (lastRuntimeVisibleParameterAnnotations != null) {
AnnotationWriter.putParameterAnnotations(
symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS),
lastRuntimeVisibleParameterAnnotations,
visibleAnnotableParameterCount == 0
? lastRuntimeVisibleParameterAnnotations.length
: visibleAnnotableParameterCount,
output);
}
if (lastRuntimeInvisibleParameterAnnotations != null) {
AnnotationWriter.putParameterAnnotations(
symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS),
lastRuntimeInvisibleParameterAnnotations,
invisibleAnnotableParameterCount == 0
? lastRuntimeInvisibleParameterAnnotations.length
: invisibleAnnotableParameterCount,
output);
}
if (defaultValue != null) {
output
.putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT))
.putInt(defaultValue.length)
.putByteArray(defaultValue.data, 0, defaultValue.length);
}
if (parameters != null) {
output
.putShort(symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS))
.putInt(1 + parameters.length)
.putByte(parametersCount)
.putByteArray(parameters.data, 0, parameters.length);
}
if (firstAttribute != null) {
firstAttribute.putAttributes(symbolTable, output);
}
}
/**
* Collects the attributes of this method into the given set of attribute prototypes.
*
* @param attributePrototypes a set of attribute prototypes.
*/
final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
attributePrototypes.addAttributes(firstAttribute);
attributePrototypes.addAttributes(firstCodeAttribute);
}
}