/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package javassist.bytecode;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.Map;
import javassist.CannotCompileException;
/**
* stack_map
attribute.
*
*
This is an entry in the attributes table of a Code attribute.
* It was introduced by J2SE 6 for the process of verification by
* typechecking.
*
* @see StackMap
* @since 3.4
*/
public class StackMapTable extends AttributeInfo {
/**
* The name of this attribute This method is for javassist.convert.TransformNew.
* It is called to update the stack map table when
* the NEW opcode (and the following DUP) is removed.
*
* @param where the position of the removed NEW opcode.
*/
public void removeNew(int where) throws CannotCompileException {
try {
byte[] data = new NewRemover(this.get(), where).doit();
this.set(data);
}
catch (BadBytecode e) {
throw new CannotCompileException("bad stack map table", e);
}
}
static class NewRemover extends SimpleCopy {
int posOfNew;
public NewRemover(byte[] data, int pos) {
super(data);
posOfNew = pos;
}
public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
if (stackTag == UNINIT && stackData == posOfNew)
super.sameFrame(pos, offsetDelta);
else
super.sameLocals(pos, offsetDelta, stackTag, stackData);
}
public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
int[] stackTags, int[] stackData) {
int n = stackTags.length - 1;
for (int i = 0; i < n; i++)
if (stackTags[i] == UNINIT && stackData[i] == posOfNew
&& stackTags[i + 1] == UNINIT && stackData[i + 1] == posOfNew) {
n++;
int[] stackTags2 = new int[n - 2];
int[] stackData2 = new int[n - 2];
int k = 0;
for (int j = 0; j < n; j++)
if (j == i)
j++;
else {
stackTags2[k] = stackTags[j];
stackData2[k++] = stackData[j];
}
stackTags = stackTags2;
stackData = stackData2;
break;
}
super.fullFrame(pos, offsetDelta, localTags, localData, stackTags, stackData);
}
}
}
"StackMapTable"
.
*/
public static final String tag = "StackMapTable";
/**
* Constructs a stack_map
attribute.
*/
StackMapTable(ConstPool cp, byte[] newInfo) {
super(cp, tag, newInfo);
}
StackMapTable(ConstPool cp, int name_id, DataInputStream in)
throws IOException
{
super(cp, name_id, in);
}
/**
* Makes a copy.
*
* @exception RuntimeCopyException if a BadBytecode
* exception is thrown while copying,
* it is converted into
* RuntimeCopyException
.
*
*/
public AttributeInfo copy(ConstPool newCp, Map classnames)
throws RuntimeCopyException
{
try {
return new StackMapTable(newCp,
new Copier(this.constPool, info, newCp).doit());
}
catch (BadBytecode e) {
throw new RuntimeCopyException("bad bytecode. fatal?");
}
}
/**
* An exception that may be thrown by copy()
* in StackMapTable
.
*/
public static class RuntimeCopyException extends RuntimeException {
/**
* Constructs an exception.
*/
public RuntimeCopyException(String s) {
super(s);
}
}
void write(DataOutputStream out) throws IOException {
super.write(out);
}
/**
* Top_variable_info.tag
.
*/
public static final int TOP = 0;
/**
* Integer_variable_info.tag
.
*/
public static final int INTEGER = 1;
/**
* Float_variable_info.tag
.
*/
public static final int FLOAT = 2;
/**
* Double_variable_info.tag
.
*/
public static final int DOUBLE = 3;
/**
* Long_variable_info.tag
.
*/
public static final int LONG = 4;
/**
* Null_variable_info.tag
.
*/
public static final int NULL = 5;
/**
* UninitializedThis_variable_info.tag
.
*/
public static final int THIS = 6;
/**
* Object_variable_info.tag
.
*/
public static final int OBJECT = 7;
/**
* Uninitialized_variable_info.tag
.
*/
public static final int UNINIT = 8;
/**
* A code walker for a StackMapTable attribute.
*/
public static class Walker {
byte[] info;
int numOfEntries;
/**
* Constructs a walker.
*
* @param smt the StackMapTable that this walker
* walks around.
*/
public Walker(StackMapTable smt) {
this(smt.get());
}
/**
* Constructs a walker.
*
* @param data the info
field of the
* attribute_info
structure.
* It can be obtained by get()
* in the AttributeInfo
class.
*/
public Walker(byte[] data) {
info = data;
numOfEntries = ByteArray.readU16bit(data, 0);
}
/**
* Returns the number of the entries.
*/
public final int size() { return numOfEntries; }
/**
* Visits each entry of the stack map frames.
*/
public void parse() throws BadBytecode {
int n = numOfEntries;
int pos = 2;
for (int i = 0; i < n; i++)
pos = stackMapFrames(pos, i);
}
/**
* Invoked when the next entry of the stack map frames is visited.
*
* @param pos the position of the frame in the info
* field of attribute_info
structure.
* @param nth the frame is the N-th
* (0, 1st, 2nd, 3rd, 4th, ...) entry.
* @return the position of the next frame.
*/
int stackMapFrames(int pos, int nth) throws BadBytecode {
int type = info[pos] & 0xff;
if (type < 64) {
sameFrame(pos, type);
pos++;
}
else if (type < 128)
pos = sameLocals(pos, type);
else if (type < 247)
throw new BadBytecode("bad frame_type in StackMapTable");
else if (type == 247) // SAME_LOCALS_1_STACK_ITEM_EXTENDED
pos = sameLocals(pos, type);
else if (type < 251) {
int offset = ByteArray.readU16bit(info, pos + 1);
chopFrame(pos, offset, 251 - type);
pos += 3;
}
else if (type == 251) { // SAME_FRAME_EXTENDED
int offset = ByteArray.readU16bit(info, pos + 1);
sameFrame(pos, offset);
pos += 3;
}
else if (type < 255)
pos = appendFrame(pos, type);
else // FULL_FRAME
pos = fullFrame(pos);
return pos;
}
/**
* Invoked if the visited frame is a same_frame
or
* a same_frame_extended
.
*
* @param pos the position of this frame in the info
* field of attribute_info
structure.
* @param offsetDelta
*/
public void sameFrame(int pos, int offsetDelta) throws BadBytecode {}
private int sameLocals(int pos, int type) throws BadBytecode {
int top = pos;
int offset;
if (type < 128)
offset = type - 64;
else { // type == 247
offset = ByteArray.readU16bit(info, pos + 1);
pos += 2;
}
int tag = info[pos + 1] & 0xff;
int data = 0;
if (tag == OBJECT || tag == UNINIT) {
data = ByteArray.readU16bit(info, pos + 2);
pos += 2;
}
sameLocals(top, offset, tag, data);
return pos + 2;
}
/**
* Invoked if the visited frame is a same_locals_1_stack_item_frame
* or a same_locals_1_stack_item_frame_extended
.
*
* @param pos the position.
* @param offsetDelta
* @param stackTag stack[0].tag
.
* @param stackData stack[0].cpool_index
* if the tag is OBJECT
,
* or stack[0].offset
* if the tag is UNINIT
.
*/
public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData)
throws BadBytecode {}
/**
* Invoked if the visited frame is a chop_frame
.
*
* @param pos the position.
* @param offsetDelta
* @param k the append_frame
.
*
* @param pos the position.
* @param offsetDelta
* @param tags locals[i].tag
.
* @param data locals[i].cpool_index
* or full_frame
.
*
* @param pos the position.
* @param offsetDelta
* @param localTags locals[i].tag
* @param localData locals[i].cpool_index
* or locals[i].offset
* @param stackTags stack[i].tag
* @param stackData stack[i].cpool_index
* or stack[i].offset
*/
public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
int[] stackTags, int[] stackData)
throws BadBytecode {}
private int verifyTypeInfo(int pos, int n, int[] tags, int[] data) {
for (int i = 0; i < n; i++) {
int tag = info[pos++] & 0xff;
tags[i] = tag;
if (tag == OBJECT || tag == UNINIT) {
data[i] = ByteArray.readU16bit(info, pos);
pos += 2;
}
}
return pos;
}
}
static class SimpleCopy extends Walker {
private Writer writer;
public SimpleCopy(byte[] data) {
super(data);
writer = new Writer(data.length);
}
public byte[] doit() throws BadBytecode {
parse();
return writer.toByteArray();
}
public void sameFrame(int pos, int offsetDelta) {
writer.sameFrame(offsetDelta);
}
public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
writer.sameLocals(offsetDelta, stackTag, copyData(stackTag, stackData));
}
public void chopFrame(int pos, int offsetDelta, int k) {
writer.chopFrame(offsetDelta, k);
}
public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) {
writer.appendFrame(offsetDelta, tags, copyData(tags, data));
}
public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
int[] stackTags, int[] stackData) {
writer.fullFrame(offsetDelta, localTags, copyData(localTags, localData),
stackTags, copyData(stackTags, stackData));
}
protected int copyData(int tag, int data) {
return data;
}
protected int[] copyData(int[] tags, int[] data) {
return data;
}
}
static class Copier extends SimpleCopy {
private ConstPool srcPool, destPool;
public Copier(ConstPool src, byte[] data, ConstPool dest) {
super(data);
srcPool = src;
destPool = dest;
}
protected int copyData(int tag, int data) {
if (tag == OBJECT)
return srcPool.copy(data, destPool, null);
else
return data;
}
protected int[] copyData(int[] tags, int[] data) {
int[] newData = new int[data.length];
for (int i = 0; i < data.length; i++)
if (tags[i] == OBJECT)
newData[i] = srcPool.copy(data[i], destPool, null);
else
newData[i] = data[i];
return newData;
}
}
/**
* Updates this stack map table when a new local variable is inserted
* for a new parameter.
*
* @param index the index of the added local variable.
* @param tag the type tag of that local variable.
* @param classInfo the index of the CONSTANT_Class_info
structure
* in a constant pool table. This should be zero unless the tag
* is ITEM_Object
.
*
* @see javassist.CtBehavior#addParameter(javassist.CtClass)
* @see #typeTagOf(char)
* @see ConstPool
*/
public void insertLocal(int index, int tag, int classInfo)
throws BadBytecode
{
byte[] data = new InsertLocal(this.get(), index, tag, classInfo).doit();
this.set(data);
}
/**
* Returns the tag of the type specified by the
* descriptor. This method returns INTEGER
* unless the descriptor is either D (double), F (float),
* J (long), L (class type), or [ (array).
*
* @param descriptor the type descriptor.
* @see Descriptor
*/
public static int typeTagOf(char descriptor) {
switch (descriptor) {
case 'D' :
return DOUBLE;
case 'F' :
return FLOAT;
case 'J' :
return LONG;
case 'L' :
case '[' :
return OBJECT;
// case 'V' :
default :
return INTEGER;
}
}
/* This implementation assumes that a local variable initially
* holding a parameter value is never changed to be a different
* type.
*
*/
static class InsertLocal extends SimpleCopy {
private int varIndex;
private int varTag, varData;
public InsertLocal(byte[] data, int varIndex, int varTag, int varData) {
super(data);
this.varIndex = varIndex;
this.varTag = varTag;
this.varData = varData;
}
public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
int[] stackTags, int[] stackData) {
int len = localTags.length;
if (len < varIndex) {
super.fullFrame(pos, offsetDelta, localTags, localData, stackTags, stackData);
return;
}
int typeSize = (varTag == LONG || varTag == DOUBLE) ? 2 : 1;
int[] localTags2 = new int[len + typeSize];
int[] localData2 = new int[len + typeSize];
int index = varIndex;
int j = 0;
for (int i = 0; i < len; i++) {
if (j == index)
j += typeSize;
localTags2[j] = localTags[i];
localData2[j++] = localData[i];
}
localTags2[index] = varTag;
localData2[index] = varData;
if (typeSize > 1) {
localTags2[index + 1] = TOP;
localData2[index + 1] = 0;
}
super.fullFrame(pos, offsetDelta, localTags2, localData2, stackTags, stackData);
}
}
/**
* A writer of stack map tables.
*/
public static class Writer {
ByteArrayOutputStream output;
int numOfEntries;
/**
* Constructs a writer.
* @param size the initial buffer size.
*/
public Writer(int size) {
output = new ByteArrayOutputStream(size);
numOfEntries = 0;
output.write(0); // u2 number_of_entries
output.write(0);
}
/**
* Returns the stack map table written out.
*/
public byte[] toByteArray() {
byte[] b = output.toByteArray();
ByteArray.write16bit(numOfEntries, b, 0);
return b;
}
/**
* Constructs and a return a stack map table containing
* the written stack map entries.
*
* @param cp the constant pool used to write
* the stack map entries.
*/
public StackMapTable toStackMapTable(ConstPool cp) {
return new StackMapTable(cp, toByteArray());
}
/**
* Writes a same_frame
or a same_frame_extended
.
*/
public void sameFrame(int offsetDelta) {
numOfEntries++;
if (offsetDelta < 64)
output.write(offsetDelta);
else {
output.write(251); // SAME_FRAME_EXTENDED
write16(offsetDelta);
}
}
/**
* Writes a same_locals_1_stack_item
* or a same_locals_1_stack_item_extended
.
*
* @param tag stack[0].tag
.
* @param data stack[0].cpool_index
* if the tag is OBJECT
,
* or UNINIT
.
* Otherwise, this parameter is not used.
*/
public void sameLocals(int offsetDelta, int tag, int data) {
numOfEntries++;
if (offsetDelta < 64)
output.write(offsetDelta + 64);
else {
output.write(247); // SAME_LOCALS_1_STACK_ITEM_EXTENDED
write16(offsetDelta);
}
writeTypeInfo(tag, data);
}
/**
* Writes a chop_frame
.
*
* @param k the number of absent locals. 1, 2, or 3.
*/
public void chopFrame(int offsetDelta, int k) {
numOfEntries++;
output.write(251 - k);
write16(offsetDelta);
}
/**
* Writes a append_frame
. The number of the appended
* locals is specified by the length of tags
.
*
* @param tags locals[].tag
.
* The length of this array must be
* either 1, 2, or 3.
* @param data locals[].cpool_index
* if the tag is OBJECT
,
* or UNINIT
.
* Otherwise, this parameter is not used.
*/
public void appendFrame(int offsetDelta, int[] tags, int[] data) {
numOfEntries++;
int k = tags.length; // k is 1, 2, or 3
output.write(k + 251);
write16(offsetDelta);
for (int i = 0; i < k; i++)
writeTypeInfo(tags[i], data[i]);
}
/**
* Writes a full_frame
.
* number_of_locals
and number_of_stack_items
* are specified by the the length of localTags
and
* stackTags
.
*
* @param localTags locals[].tag
.
* @param localData locals[].cpool_index
* if the tag is OBJECT
,
* or UNINIT
.
* Otherwise, this parameter is not used.
* @param stackTags stack[].tag
.
* @param stackData stack[].cpool_index
* if the tag is OBJECT
,
* or UNINIT
.
* Otherwise, this parameter is not used.
*/
public void fullFrame(int offsetDelta, int[] localTags, int[] localData,
int[] stackTags, int[] stackData) {
numOfEntries++;
output.write(255); // FULL_FRAME
write16(offsetDelta);
int n = localTags.length;
write16(n);
for (int i = 0; i < n; i++)
writeTypeInfo(localTags[i], localData[i]);
n = stackTags.length;
write16(n);
for (int i = 0; i < n; i++)
writeTypeInfo(stackTags[i], stackData[i]);
}
private void writeTypeInfo(int tag, int data) {
output.write(tag);
if (tag == OBJECT || tag == UNINIT)
write16(data);
}
private void write16(int value) {
output.write((value >>> 8) & 0xff);
output.write(value & 0xff);
}
}
/**
* Prints the stack table map.
*/
public void println(PrintWriter w) {
Printer.print(this, w);
}
/**
* Prints the stack table map.
*
* @param ps a print stream such as System.out
.
*/
public void println(java.io.PrintStream ps) {
Printer.print(this, new java.io.PrintWriter(ps, true));
}
static class Printer extends Walker {
private PrintWriter writer;
private int offset;
/**
* Prints the stack table map.
*/
public static void print(StackMapTable smt, PrintWriter writer) {
try {
new Printer(smt.get(), writer).parse();
}
catch (BadBytecode e) {
writer.println(e.getMessage());
}
}
Printer(byte[] data, PrintWriter pw) {
super(data);
writer = pw;
offset = -1;
}
public void sameFrame(int pos, int offsetDelta) {
offset += offsetDelta + 1;
writer.println(offset + " same frame: " + offsetDelta);
}
public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
offset += offsetDelta + 1;
writer.println(offset + " same locals: " + offsetDelta);
printTypeInfo(stackTag, stackData);
}
public void chopFrame(int pos, int offsetDelta, int k) {
offset += offsetDelta + 1;
writer.println(offset + " chop frame: " + offsetDelta + ", " + k + " last locals");
}
public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) {
offset += offsetDelta + 1;
writer.println(offset + " append frame: " + offsetDelta);
for (int i = 0; i < tags.length; i++)
printTypeInfo(tags[i], data[i]);
}
public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
int[] stackTags, int[] stackData) {
offset += offsetDelta + 1;
writer.println(offset + " full frame: " + offsetDelta);
writer.println("[locals]");
for (int i = 0; i < localTags.length; i++)
printTypeInfo(localTags[i], localData[i]);
writer.println("[stack]");
for (int i = 0; i < stackTags.length; i++)
printTypeInfo(stackTags[i], stackData[i]);
}
private void printTypeInfo(int tag, int data) {
String msg = null;
switch (tag) {
case TOP :
msg = "top";
break;
case INTEGER :
msg = "integer";
break;
case FLOAT :
msg = "float";
break;
case DOUBLE :
msg = "double";
break;
case LONG :
msg = "long";
break;
case NULL :
msg = "null";
break;
case THIS :
msg = "this";
break;
case OBJECT :
msg = "object (cpool_index " + data + ")";
break;
case UNINIT :
msg = "uninitialized (offset " + data + ")";
break;
}
writer.print(" ");
writer.println(msg);
}
}
void shiftPc(int where, int gapSize, boolean exclusive)
throws BadBytecode
{
new Shifter(this, where, gapSize, exclusive).doit();
}
static class Shifter extends Walker {
private StackMapTable stackMap;
private int where, gap;
private int position;
private byte[] updatedInfo;
private boolean exclusive;
public Shifter(StackMapTable smt, int where, int gap, boolean exclusive) {
super(smt);
stackMap = smt;
this.where = where;
this.gap = gap;
this.position = 0;
this.updatedInfo = null;
this.exclusive = exclusive;
}
public void doit() throws BadBytecode {
parse();
if (updatedInfo != null)
stackMap.set(updatedInfo);
}
public void sameFrame(int pos, int offsetDelta) {
update(pos, offsetDelta, 0, 251);
}
public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) {
update(pos, offsetDelta, 64, 247);
}
private void update(int pos, int offsetDelta, int base, int entry) {
int oldPos = position;
position = oldPos + offsetDelta + (oldPos == 0 ? 0 : 1);
boolean match;
if (exclusive)
match = oldPos < where && where <= position;
else
match = oldPos <= where && where < position;
if (match) {
int newDelta = offsetDelta + gap;
position += gap;
if (newDelta < 64)
info[pos] = (byte)(newDelta + base);
else if (offsetDelta < 64) {
byte[] newinfo = insertGap(info, pos, 2);
newinfo[pos] = (byte)entry;
ByteArray.write16bit(newDelta, newinfo, pos + 1);
updatedInfo = newinfo;
}
else
ByteArray.write16bit(newDelta, info, pos + 1);
}
}
private static byte[] insertGap(byte[] info, int where, int gap) {
int len = info.length;
byte[] newinfo = new byte[len + gap];
for (int i = 0; i < len; i++)
newinfo[i + (i < where ? 0 : gap)] = info[i];
return newinfo;
}
public void chopFrame(int pos, int offsetDelta, int k) {
update(pos, offsetDelta);
}
public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) {
update(pos, offsetDelta);
}
public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData,
int[] stackTags, int[] stackData) {
update(pos, offsetDelta);
}
private void update(int pos, int offsetDelta) {
int oldPos = position;
position = oldPos + offsetDelta + (oldPos == 0 ? 0 : 1);
boolean match;
if (exclusive)
match = oldPos < where && where <= position;
else
match = oldPos <= where && where < position;
if (match) {
int newDelta = offsetDelta + gap;
ByteArray.write16bit(newDelta, info, pos + 1);
position += gap;
}
}
}
/**
* Undocumented method. Do not use; internal-use only.
*
*