/**
* ASM: a very small and fast Java bytecode manipulation framework
* Copyright (c) 2000-2005 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.attrs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ByteVector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* The stack map attribute is used during the process of verification by
* typechecking (§4.11.1).
A stack map attribute consists of zero or
* more stack map frames. Each stack map frame specifies (either explicitly or
* implicitly) a bytecode offset, the verification types (§4.11.1) for the local
* variables, and the verification types for the operand stack.
The
* type checker deals with and manipulates the expected types of a method's
* local variables and operand stack. Throughout this section, a location refers
* to either a single local variable or to a single operand stack entry.
*
We will use the terms stack frame map and type state interchangeably to
* describe a mapping from locations in the operand stack and local variables of
* a method to verification types. We will usually use the term stack frame map
* when such a mapping is provided in the class file, and the term type state
* when the mapping is inferred by the type checker.
If a method's
* Code attribute does not have a StackMapTable attribute, it has an implicit
* stack map attribute. This implicit stack map attribute is equivalent to a
* StackMapTable attribute with number_of_entries equal to zero. A method's Code
* attribute may have at most one StackMapTable attribute, otherwise a
* java.lang.ClassFormatError is thrown.
The format of the stack map
* in the class file is given below. In the following, if the length of the
* method's byte code is 65535 or less, then uoffset represents the type u2;
* otherwise uoffset represents the type u4. If the maximum number of local
* variables for the method is 65535 or less, then ulocalvar
* represents the type u2; otherwise ulocalvar represents the type u4. If the
* maximum size of the operand stack is 65535 or less, then ustack
* represents the type u2; otherwise ustack represents the type u4.
*
*
* stack_map { // attribute StackMapTable * u2 attribute_name_index; * u4 attribute_length * uoffset number_of_entries; * stack_map_frame entries[number_of_entries]; * } ** * Each stack_map_frame structure specifies the type state at a particular byte * code offset. Each frame type specifies (explicitly or implicitly) a value, * offset_delta, that is used to calulate the actual byte code offset at which * it applies. The byte code offset at which the frame applies is given by * adding
1 + offset_delta
to the offset
of the
* previous frame, unless the previous frame is the initial frame of the method,
* in which case the byte code offset is offset_delta
. offset_delta + 1
for all explicit frames, we guarantee
* the absence of duplicates. * union stack_map_frame { * same_frame; * same_locals_1_stack_item_frame; * chop_frame; * same_frame_extended; * append_frame; * full_frame; * } ** * The frame type same_frame is represented by tags in the range [0-63]. If the * frame type is same_frame, it means the frame has exactly the same locals as * the previous stack map frame and that the number of stack items is zero. The * offset_delta value for the frame is the value of the tag field, frame_type. * The form of such a frame is then: * *
* same_frame { * u1 frame_type = SAME; // 0-63 * } ** * The frame type same_locals_1_stack_item_frame is represented by tags in the * range [64, 127]. If the frame_type is same_locals_1_stack_item_frame, it * means the frame has exactly the same locals as the previous stack map frame * and that the number of stack items is 1. The offset_delta value for the frame * is the value (frame_type - 64). There is a verification_type_info following * the frame_type for the one stack item. The form of such a frame is then: * *
* same_locals_1_stack_item_frame { * u1 frame_type = SAME_LOCALS_1_STACK_ITEM; // 64-127 * verification_type_info stack[1]; * } ** * Tags in the range [128-247] are reserved for future use.
* chop_frame { * u1 frame_type=CHOP; // 248-250 * uoffset offset_delta; * } ** * The frame type same_frame_extended is represented by the tag value 251. If * the frame type is same_frame_extended, it means the frame has exactly the * same locals as the previous stack map frame and that the number of stack * items is zero. The form of such a frame is then: * *
* same_frame_extended { * u1 frame_type = SAME_FRAME_EXTENDED; // 251 * uoffset offset_delta; * } ** * The frame type append_frame is represented by tags in the range [252-254]. If * the frame_type is append_frame, it means that the current locals are the same * as the locals in the previous frame, except that k additional locals are * defined. The value of k is given by the formula frame_type-251.
* append_frame { * u1 frame_type =APPEND; // 252-254 * uoffset offset_delta; * verification_type_info locals[frame_type -251]; * } ** * The 0th entry in locals represents the type of the first additional local * variable. If locals[M] represents local variable N, then locals[M+1] * represents local variable N+1 if locals[M] is one of Top_variable_info, * Integer_variable_info, Float_variable_info, Null_variable_info, * UninitializedThis_variable_info, Object_variable_info, or * Uninitialized_variable_info, otherwise locals[M+1] represents local variable * N+2. It is an error if, for any index i, locals[i] represents a local * variable whose index is greater than the maximum number of local variables * for the method.
* full_frame { * u1 frame_type = FULL_FRAME; // 255 * uoffset offset_delta; * ulocalvar number_of_locals; * verification_type_info locals[number_of_locals]; * ustack number_of_stack_items; * verification_type_info stack[number_of_stack_items]; * } ** * The 0th entry in locals represents the type of local variable 0. If locals[M] * represents local variable N, then locals[M+1] represents local variable N+1 * if locals[M] is one of Top_variable_info, Integer_variable_info, * Float_variable_info, Null_variable_info, UninitializedThis_variable_info, * Object_variable_info, or Uninitialized_variable_info, otherwise locals[M+1] * represents local variable N+2. It is an error if, for any index i, locals[i] * represents a local variable whose index is greater than the maximum number of * local variables for the method.
* union verification_type_info { * Top_variable_info; * Integer_variable_info; * Float_variable_info; * Long_variable_info; * Double_variable_info; * Null_variable_info; * UninitializedThis_variable_info; * Object_variable_info; * Uninitialized_variable_info; * } ** * The Top_variable_info type indicates that the local variable has the * verification type top (T.) * *
* Top_variable_info { * u1 tag = ITEM_Top; // 0 * } ** * The Integer_variable_info type indicates that the location contains the * verification type int. * *
* Integer_variable_info { * u1 tag = ITEM_Integer; // 1 * } ** * The Float_variable_info type indicates that the location contains the * verification type float. * *
* Float_variable_info { * u1 tag = ITEM_Float; // 2 * } ** * The Long_variable_info type indicates that the location contains the * verification type long. If the location is a local variable, then: * *
* Long_variable_info { * u1 tag = ITEM_Long; // 4 * } ** * The Double_variable_info type indicates that the location contains the * verification type double. If the location is a local variable, then: * *
* Double_variable_info { * u1 tag = ITEM_Double; // 3 * } ** * The Null_variable_info type indicates that location contains the verification * type null. * *
* Null_variable_info { * u1 tag = ITEM_Null; // 5 * } ** * The UninitializedThis_variable_info type indicates that the location contains * the verification type uninitializedThis. * *
* UninitializedThis_variable_info { * u1 tag = ITEM_UninitializedThis; // 6 * } ** * The Object_variable_info type indicates that the location contains an * instance of the class referenced by the constant pool entry. * *
* Object_variable_info { * u1 tag = ITEM_Object; // 7 * u2 cpool_index; * } ** * The Uninitialized_variable_info indicates that the location contains the * verification type uninitialized(offset). The offset item indicates the offset * of the new instruction that created the object being stored in the location. * *
* Uninitialized_variable_info { * u1 tag = ITEM_Uninitialized // 8 * uoffset offset; * } ** * @see "ClassFileFormat-Java6.fm Page 138 Friday, April 15, 2005 3:22 PM" * * @author Eugene Kuleshov */ public class StackMapTableAttribute extends Attribute { /** * Frame has exactly the same locals as the previous stack map frame and * number of stack items is zero. */ public static final int SAME_FRAME = 0; // to 63 (0-3f) /** * Frame has exactly the same locals as the previous stack map frame and * number of stack items is 1 */ public static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 // (40-7f) /** * Reserved for future use */ public static final int RESERVED = 128; /** * Frame has exactly the same locals as the previous stack map frame and * number of stack items is 1. Offset is bigger then 63; */ public static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7 /** * Frame where current locals are the same as the locals in the previous * frame, except that the k last locals are absent. The value of k is given * by the formula 251-frame_type. */ public static final int CHOP_FRAME = 248; // to 250 (f8-fA) /** * Frame has exactly the same locals as the previous stack map frame and * number of stack items is zero. Offset is bigger then 63; */ public static final int SAME_FRAME_EXTENDED = 251; // fb /** * Frame where current locals are the same as the locals in the previous * frame, except that k additional locals are defined. The value of k is * given by the formula frame_type-251. */ public static final int APPEND_FRAME = 252; // to 254 // fc-fe /** * Full frame */ public static final int FULL_FRAME = 255; // ff private static final int MAX_SHORT = 65535; /** * A
List
of StackMapFrame
instances.
*/
private List frames;
public StackMapTableAttribute() {
super("StackMapTable");
}
public StackMapTableAttribute(List frames) {
this();
this.frames = frames;
}
public List getFrames() {
return frames;
}
public StackMapFrame getFrame(Label label) {
for (int i = 0; i < frames.size(); i++) {
StackMapFrame frame = (StackMapFrame) frames.get(i);
if (frame.label == label) {
return frame;
}
}
return null;
}
public boolean isUnknown() {
return false;
}
public boolean isCodeAttribute() {
return true;
}
protected Attribute read(
ClassReader cr,
int off,
int len,
char[] buf,
int codeOff,
Label[] labels)
{
ArrayList frames = new ArrayList();
// note that this is not the size of Code attribute
boolean isExtCodeSize = cr.readInt(codeOff + 4) > MAX_SHORT;
boolean isExtLocals = cr.readUnsignedShort(codeOff + 2) > MAX_SHORT;
boolean isExtStack = cr.readUnsignedShort(codeOff) > MAX_SHORT;
int offset = 0;
int methodOff = getMethodOff(cr, codeOff, buf);
StackMapFrame frame = new StackMapFrame(getLabel(offset, labels),
calculateLocals(cr.readClass(cr.header + 2, buf), // owner
cr.readUnsignedShort(methodOff), // method access
cr.readUTF8(methodOff + 2, buf), // method name
cr.readUTF8(methodOff + 4, buf)), // method desc
Collections.EMPTY_LIST);
frames.add(frame);
// System.err.println( cr.readUTF8( methodOff + 2, buf));
// System.err.println( offset +" delta:" + 0 +" : "+ frame);
int size;
if (isExtCodeSize) {
size = cr.readInt(off);
off += 4;
} else {
size = cr.readUnsignedShort(off);
off += 2;
}
for (; size > 0; size--) {
int tag = cr.readByte(off); // & 0xff;
off++;
List stack;
List locals;
int offsetDelta;
if (tag < SAME_LOCALS_1_STACK_ITEM_FRAME) { // SAME_FRAME
offsetDelta = tag;
locals = new ArrayList(frame.locals);
stack = Collections.EMPTY_LIST;
} else if (tag < RESERVED) { // SAME_LOCALS_1_STACK_ITEM_FRAME
offsetDelta = tag - SAME_LOCALS_1_STACK_ITEM_FRAME;
locals = new ArrayList(frame.locals);
stack = new ArrayList();
// read verification_type_info stack[1];
off = readType(stack, isExtCodeSize, cr, off, labels, buf);
} else {
if (isExtCodeSize) {
offsetDelta = cr.readInt(off);
off += 4;
} else {
offsetDelta = cr.readUnsignedShort(off);
off += 2;
}
if (tag == SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { // SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED
locals = new ArrayList(frame.locals);
stack = new ArrayList();
// read verification_type_info stack[1];
off = readType(stack, isExtCodeSize, cr, off, labels, buf);
} else if (tag >= CHOP_FRAME && tag < SAME_FRAME_EXTENDED) { // CHOP_FRAME
stack = Collections.EMPTY_LIST;
int k = SAME_FRAME_EXTENDED - tag;
// copy locals from prev frame and chop last k
locals = new ArrayList(frame.locals.subList(0,
frame.locals.size() - k));
} else if (tag == SAME_FRAME_EXTENDED) { // SAME_FRAME_EXTENDED
stack = Collections.EMPTY_LIST;
locals = new ArrayList(frame.locals);
} else if ( /* tag>=APPEND && */tag < FULL_FRAME) { // APPEND_FRAME
stack = Collections.EMPTY_LIST;
// copy locals from prev frame and append new k
locals = new ArrayList(frame.locals);
for (int k = tag - SAME_FRAME_EXTENDED; k > 0; k--) {
off = readType(locals,
isExtCodeSize,
cr,
off,
labels,
buf);
}
} else if (tag == FULL_FRAME) { // FULL_FRAME
// read verification_type_info locals[number_of_locals];
locals = new ArrayList();
off = readTypes(locals,
isExtLocals,
isExtCodeSize,
cr,
off,
labels,
buf);
// read verification_type_info stack[number_of_stack_items];
stack = new ArrayList();
off = readTypes(stack,
isExtStack,
isExtCodeSize,
cr,
off,
labels,
buf);
} else {
throw new RuntimeException("Unknown frame type " + tag
+ " after offset " + offset);
}
}
offset += offsetDelta;
Label offsetLabel = getLabel(offset, labels);
frame = new StackMapFrame(offsetLabel, locals, stack);
frames.add(frame);
// System.err.println( tag +" " + offset +" delta:" + offsetDelta +
// " frameType:"+ frameType+" : "+ frame);
offset++;
}
return new StackMapTableAttribute(frames);
}
protected ByteVector write(
ClassWriter cw,
byte[] code,
int len,
int maxStack,
int maxLocals)
{
ByteVector bv = new ByteVector();
// TODO verify this value (MAX_SHORT)
boolean isExtCodeSize = code != null && code.length > MAX_SHORT;
writeSize(frames.size() - 1, bv, isExtCodeSize);
if (frames.size() < 2) {
return bv;
}
boolean isExtLocals = maxLocals > MAX_SHORT;
boolean isExtStack = maxStack > MAX_SHORT;
// skip the first frame
StackMapFrame frame = (StackMapFrame) frames.get(0);
List locals = frame.locals;
int offset = frame.label.getOffset();
for (int i = 1; i < frames.size(); i++) {
frame = (StackMapFrame) frames.get(i);
List clocals = frame.locals;
List cstack = frame.stack;
int coffset = frame.label.getOffset();
int clocalsSize = clocals.size();
int cstackSize = cstack.size();
int localsSize = locals.size();
int delta = coffset - offset;
int type = FULL_FRAME;
int k = 0;
if (cstackSize == 0) {
k = clocalsSize - localsSize;
switch (k) {
case -3:
case -2:
case -1:
type = CHOP_FRAME; // CHOP or FULL
localsSize = clocalsSize; // for full_frame check
break;
case 0:
// SAME, SAME_EXTENDED or FULL
type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
break;
case 1:
case 2:
case 3:
type = APPEND_FRAME; // APPEND or FULL
break;
}
} else if (localsSize == clocalsSize && cstackSize == 1) {
// SAME_LOCAL_1_STACK or FULL
type = delta < 63
? SAME_LOCALS_1_STACK_ITEM_FRAME
: SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
}
if (type != FULL_FRAME) {
// verify if stack and locals are the same
for (int j = 0; j < localsSize && type != FULL_FRAME; j++) {
if (!locals.get(j).equals(clocals.get(j)))
type = FULL_FRAME;
}
}
switch (type) {
case SAME_FRAME:
bv.putByte(delta);
break;
case SAME_LOCALS_1_STACK_ITEM_FRAME:
bv.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
writeTypeInfos(bv, cw, cstack, 0, 1);
break;
case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
bv.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED);
writeSize(delta, bv, isExtCodeSize);
writeTypeInfos(bv, cw, cstack, 0, 1);
break;
case SAME_FRAME_EXTENDED:
bv.putByte(SAME_FRAME_EXTENDED);
writeSize(delta, bv, isExtCodeSize);
break;
case CHOP_FRAME:
bv.putByte(SAME_FRAME_EXTENDED + k); // negative k
writeSize(delta, bv, isExtCodeSize);
break;
case APPEND_FRAME:
bv.putByte(SAME_FRAME_EXTENDED + k); // positive k
writeSize(delta, bv, isExtCodeSize);
writeTypeInfos(bv,
cw,
clocals,
clocalsSize - 1,
clocalsSize);
break;
case FULL_FRAME:
bv.putByte(FULL_FRAME);
writeSize(delta, bv, isExtCodeSize);
writeSize(clocalsSize, bv, isExtLocals);
writeTypeInfos(bv, cw, clocals, 0, clocalsSize);
writeSize(cstackSize, bv, isExtStack);
writeTypeInfos(bv, cw, cstack, 0, cstackSize);
break;
default:
throw new RuntimeException();
}
offset = coffset + 1; // compensating non first offset
locals = clocals;
}
return bv;
}
private void writeSize(int delta, ByteVector bv, boolean isExt) {
if (isExt) {
bv.putInt(delta);
} else {
bv.putShort(delta);
}
}
private void writeTypeInfos(
ByteVector bv,
ClassWriter cw,
List info,
int start,
int end)
{
for (int j = start; j < end; j++) {
StackMapType typeInfo = (StackMapType) info.get(j);
bv.putByte(typeInfo.getType());
switch (typeInfo.getType()) {
case StackMapType.ITEM_Object: //
bv.putShort(cw.newClass(typeInfo.getObject()));
break;
case StackMapType.ITEM_Uninitialized: //
bv.putShort(typeInfo.getLabel().getOffset());
break;
}
}
}
public static int getMethodOff(ClassReader cr, int codeOff, char[] buf) {
int off = cr.header + 6;
int interfacesCount = cr.readUnsignedShort(off);
off += 2 + interfacesCount * 2;
int fieldsCount = cr.readUnsignedShort(off);
off += 2;
for (; fieldsCount > 0; --fieldsCount) {
int attrCount = cr.readUnsignedShort(off + 6); // field attributes
off += 8;
for (; attrCount > 0; --attrCount) {
off += 6 + cr.readInt(off + 2);
}
}
int methodsCount = cr.readUnsignedShort(off);
off += 2;
for (; methodsCount > 0; --methodsCount) {
int methodOff = off;
int attrCount = cr.readUnsignedShort(off + 6); // method attributes
off += 8;
for (; attrCount > 0; --attrCount) {
String attrName = cr.readUTF8(off, buf);
off += 6;
if (attrName.equals("Code")) {
if (codeOff == off) {
return methodOff;
}
}
off += cr.readInt(off - 4);
}
}
return -1;
}
/**
* Use method signature and access flags to resolve initial locals state.
*
* @param className name of the method's owner class.
* @param access access flags of the method.
* @param methodName name of the method.
* @param methodDesc descriptor of the method.
* @return list of StackMapType
instances representing locals
* for an initial frame.
*/
public static List calculateLocals(
String className,
int access,
String methodName,
String methodDesc)
{
List locals = new ArrayList();
// TODO
if ("