/* * 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.util.ArrayList; /** * An iterator for editing a code attribute. * *
If there are multiple CodeIterator
s referring to the
* same Code_attribute
, then inserting a gap by one
* CodeIterator
will break the other
* CodeIterator
.
*
*
This iterator does not provide remove()
.
* If a piece of code in a Code_attribute
is unnecessary,
* it should be overwritten with NOP
.
*
* @see CodeAttribute#iterator()
*/
public class CodeIterator implements Opcode {
protected CodeAttribute codeAttr;
protected byte[] bytecode;
protected int endPos;
protected int currentPos;
protected int mark;
protected CodeIterator(CodeAttribute ca) {
codeAttr = ca;
bytecode = ca.getCode();
begin();
}
/**
* Moves to the first instruction.
*/
public void begin() {
currentPos = mark = 0;
endPos = getCodeLength();
}
/**
* Moves to the given index.
*
*
The index of the next instruction is set to the given index.
* The successive call to next()
* returns the index that has been given to move()
.
*
*
Note that the index is into the byte array returned by
* get().getCode()
.
*
* @see CodeAttribute#getCode()
*/
public void move(int index) {
currentPos = index;
}
/**
* Sets a mark to the bytecode at the given index.
* The mark can be used to track the position of that bytecode
* when code blocks are inserted.
* If a code block is inclusively inserted at the position of the
* bytecode, the mark is set to the inserted code block.
*
* @see #getMark()
* @since 3.11
*/
public void setMark(int index) {
mark = index;
}
/**
* Gets the index of the position of the mark set by
* setMark
.
*
* @return the index of the position.
* @see #setMark(int)
* @since 3.11
*/
public int getMark() { return mark; }
/**
* Returns a Code attribute read with this iterator.
*/
public CodeAttribute get() {
return codeAttr;
}
/**
* Returns code_length
of Code_attribute
.
*/
public int getCodeLength() {
return bytecode.length;
}
/**
* Returns the unsigned 8bit value at the given index.
*/
public int byteAt(int index) { return bytecode[index] & 0xff; }
/**
* Writes an 8bit value at the given index.
*/
public void writeByte(int value, int index) {
bytecode[index] = (byte)value;
}
/**
* Returns the unsigned 16bit value at the given index.
*/
public int u16bitAt(int index) {
return ByteArray.readU16bit(bytecode, index);
}
/**
* Returns the signed 16bit value at the given index.
*/
public int s16bitAt(int index) {
return ByteArray.readS16bit(bytecode, index);
}
/**
* Writes a 16 bit integer at the index.
*/
public void write16bit(int value, int index) {
ByteArray.write16bit(value, bytecode, index);
}
/**
* Returns the signed 32bit value at the given index.
*/
public int s32bitAt(int index) {
return ByteArray.read32bit(bytecode, index);
}
/**
* Writes a 32bit integer at the index.
*/
public void write32bit(int value, int index) {
ByteArray.write32bit(value, bytecode, index);
}
/**
* Writes a byte array at the index.
*
* @param code may be a zero-length array.
*/
public void write(byte[] code, int index) {
int len = code.length;
for (int j = 0; j < len; ++j)
bytecode[index++] = code[j];
}
/**
* Returns true if there is more instructions.
*/
public boolean hasNext() { return currentPos < endPos; }
/**
* Returns the index of the next instruction
* (not the operand following the current opcode).
*
*
Note that the index is into the byte array returned by
* get().getCode()
.
*
* @see CodeAttribute#getCode()
* @see CodeIterator#byteAt(int)
*/
public int next() throws BadBytecode {
int pos = currentPos;
currentPos = nextOpcode(bytecode, pos);
return pos;
}
/**
* Obtains the value that the next call
* to next()
will return.
*
*
This method is side-effects free.
* Successive calls to lookAhead()
return the
* same value until next()
is called.
*/
public int lookAhead() {
return currentPos;
}
/**
* Moves to the instruction for
* either super()
or this()
.
*
*
This method skips all the instructions for computing arguments
* to super()
or this()
, which should be
* placed at the beginning of a constructor body.
*
*
This method returns the index of INVOKESPECIAL instruction
* executing super()
or this()
.
* A successive call to next()
returns the
* index of the next instruction following that INVOKESPECIAL.
*
*
This method works only for a constructor.
*
* @return the index of the INVOKESPECIAL instruction, or -1
* if a constructor invocation is not found.
*/
public int skipConstructor() throws BadBytecode {
return skipSuperConstructor0(-1);
}
/**
* Moves to the instruction for super()
.
*
*
This method skips all the instructions for computing arguments to
* super()
, which should be
* placed at the beginning of a constructor body.
*
*
This method returns the index of INVOKESPECIAL instruction
* executing super()
.
* A successive call to next()
returns the
* index of the next instruction following that INVOKESPECIAL.
*
*
This method works only for a constructor.
*
* @return the index of the INVOKESPECIAL instruction, or -1
* if a super constructor invocation is not found
* but this()
is found.
*/
public int skipSuperConstructor() throws BadBytecode {
return skipSuperConstructor0(0);
}
/**
* Moves to the instruction for this()
.
*
*
This method skips all the instructions for computing arguments to
* this()
, which should be
* placed at the beginning of a constructor body.
*
*
This method returns the index of INVOKESPECIAL instruction
* executing this()
.
* A successive call to next()
returns the
* index of the next instruction following that INVOKESPECIAL.
*
*
This method works only for a constructor.
*
* @return the index of the INVOKESPECIAL instruction, or -1
* if a explicit constructor invocation is not found
* but super()
is found.
*/
public int skipThisConstructor() throws BadBytecode {
return skipSuperConstructor0(1);
}
/* skipSuper 1: this(), 0: super(), -1: both.
*/
private int skipSuperConstructor0(int skipThis) throws BadBytecode {
begin();
ConstPool cp = codeAttr.getConstPool();
String thisClassName = codeAttr.getDeclaringClass();
int nested = 0;
while (hasNext()) {
int index = next();
int c = byteAt(index);
if (c == NEW)
++nested;
else if (c == INVOKESPECIAL) {
int mref = ByteArray.readU16bit(bytecode, index + 1);
if (cp.getMethodrefName(mref).equals(MethodInfo.nameInit))
if (--nested < 0) {
if (skipThis < 0)
return index;
String cname = cp.getMethodrefClassName(mref);
if (cname.equals(thisClassName) == (skipThis > 0))
return index;
else
break;
}
}
}
begin();
return -1;
}
/**
* Inserts the given bytecode sequence
* before the next instruction that would be returned by
* next()
(not before the instruction returned
* by the last call to next()
).
* Branch offsets and the exception table are also updated.
*
*
If the next instruction is at the beginning of a block statement, * then the bytecode is inserted within that block. * *
An extra gap may be inserted at the end of the inserted
* bytecode sequence for adjusting alignment if the code attribute
* includes LOOKUPSWITCH
or TABLESWITCH
.
*
* @param code inserted bytecode sequence.
* @return the index indicating the first byte of the
* inserted byte sequence.
*/
public int insert(byte[] code)
throws BadBytecode
{
return insert0(currentPos, code, false);
}
/**
* Inserts the given bytecode sequence
* before the instruction at the given index pos
.
* Branch offsets and the exception table are also updated.
*
*
If the instruction at the given index is at the beginning * of a block statement, * then the bytecode is inserted within that block. * *
An extra gap may be inserted at the end of the inserted
* bytecode sequence for adjusting alignment if the code attribute
* includes LOOKUPSWITCH
or TABLESWITCH
.
*
*
The index at which the byte sequence is actually inserted
* might be different from pos since some other bytes might be
* inserted at other positions (e.g. to change GOTO
* to GOTO_W
).
*
* @param pos the index at which a byte sequence is inserted.
* @param code inserted bytecode sequence.
*/
public void insert(int pos, byte[] code) throws BadBytecode {
insert0(pos, code, false);
}
/**
* Inserts the given bytecode sequence
* before the instruction at the given index pos
.
* Branch offsets and the exception table are also updated.
*
*
If the instruction at the given index is at the beginning * of a block statement, * then the bytecode is inserted within that block. * *
An extra gap may be inserted at the end of the inserted
* bytecode sequence for adjusting alignment if the code attribute
* includes LOOKUPSWITCH
or TABLESWITCH
.
*
* @param pos the index at which a byte sequence is inserted.
* @param code inserted bytecode sequence.
* @return the index indicating the first byte of the
* inserted byte sequence, which might be
* different from pos.
* @since 3.11
*/
public int insertAt(int pos, byte[] code) throws BadBytecode {
return insert0(pos, code, false);
}
/**
* Inserts the given bytecode sequence exclusively
* before the next instruction that would be returned by
* next()
(not before the instruction returned
* by tha last call to next()
).
* Branch offsets and the exception table are also updated.
*
*
If the next instruction is at the beginning of a block statement, * then the bytecode is excluded from that block. * *
An extra gap may be inserted at the end of the inserted
* bytecode sequence for adjusting alignment if the code attribute
* includes LOOKUPSWITCH
or TABLESWITCH
.
*
* @param code inserted bytecode sequence.
* @return the index indicating the first byte of the
* inserted byte sequence.
*/
public int insertEx(byte[] code)
throws BadBytecode
{
return insert0(currentPos, code, true);
}
/**
* Inserts the given bytecode sequence exclusively
* before the instruction at the given index pos
.
* Branch offsets and the exception table are also updated.
*
*
If the instruction at the given index is at the beginning * of a block statement, * then the bytecode is excluded from that block. * *
An extra gap may be inserted at the end of the inserted
* bytecode sequence for adjusting alignment if the code attribute
* includes LOOKUPSWITCH
or TABLESWITCH
.
*
*
The index at which the byte sequence is actually inserted
* might be different from pos since some other bytes might be
* inserted at other positions (e.g. to change GOTO
* to GOTO_W
).
*
* @param pos the index at which a byte sequence is inserted.
* @param code inserted bytecode sequence.
*/
public void insertEx(int pos, byte[] code) throws BadBytecode {
insert0(pos, code, true);
}
/**
* Inserts the given bytecode sequence exclusively
* before the instruction at the given index pos
.
* Branch offsets and the exception table are also updated.
*
*
If the instruction at the given index is at the beginning * of a block statement, * then the bytecode is excluded from that block. * *
An extra gap may be inserted at the end of the inserted
* bytecode sequence for adjusting alignment if the code attribute
* includes LOOKUPSWITCH
or TABLESWITCH
.
*
* @param pos the index at which a byte sequence is inserted.
* @param code inserted bytecode sequence.
* @return the index indicating the first byte of the
* inserted byte sequence, which might be
* different from pos.
* @since 3.11
*/
public int insertExAt(int pos, byte[] code) throws BadBytecode {
return insert0(pos, code, true);
}
/**
* @return the index indicating the first byte of the
* inserted byte sequence.
*/
private int insert0(int pos, byte[] code, boolean exclusive)
throws BadBytecode
{
int len = code.length;
if (len <= 0)
return pos;
// currentPos will change.
pos = insertGapAt(pos, len, exclusive).position;
int p = pos;
for (int j = 0; j < len; ++j)
bytecode[p++] = code[j];
return pos;
}
/**
* Inserts a gap
* before the next instruction that would be returned by
* next()
(not before the instruction returned
* by the last call to next()
).
* Branch offsets and the exception table are also updated.
* The inserted gap is filled with NOP. The gap length may be
* extended to a multiple of 4.
*
*
If the next instruction is at the beginning of a block statement,
* then the gap is inserted within that block.
*
* @param length gap length
* @return the index indicating the first byte of the inserted gap.
*/
public int insertGap(int length) throws BadBytecode {
return insertGapAt(currentPos, length, false).position;
}
/**
* Inserts a gap in front of the instruction at the given
* index pos
.
* Branch offsets and the exception table are also updated.
* The inserted gap is filled with NOP. The gap length may be
* extended to a multiple of 4.
*
*
If the instruction at the given index is at the beginning
* of a block statement,
* then the gap is inserted within that block.
*
* @param pos the index at which a gap is inserted.
* @param length gap length.
* @return the length of the inserted gap.
* It might be bigger than length
.
*/
public int insertGap(int pos, int length) throws BadBytecode {
return insertGapAt(pos, length, false).length;
}
/**
* Inserts an exclusive gap
* before the next instruction that would be returned by
* next()
(not before the instruction returned
* by the last call to next()
).
* Branch offsets and the exception table are also updated.
* The inserted gap is filled with NOP. The gap length may be
* extended to a multiple of 4.
*
*
If the next instruction is at the beginning of a block statement,
* then the gap is excluded from that block.
*
* @param length gap length
* @return the index indicating the first byte of the inserted gap.
*/
public int insertExGap(int length) throws BadBytecode {
return insertGapAt(currentPos, length, true).position;
}
/**
* Inserts an exclusive gap in front of the instruction at the given
* index pos
.
* Branch offsets and the exception table are also updated.
* The inserted gap is filled with NOP. The gap length may be
* extended to a multiple of 4.
*
*
If the instruction at the given index is at the beginning
* of a block statement,
* then the gap is excluded from that block.
*
* @param pos the index at which a gap is inserted.
* @param length gap length.
* @return the length of the inserted gap.
* It might be bigger than length
.
*/
public int insertExGap(int pos, int length) throws BadBytecode {
return insertGapAt(pos, length, true).length;
}
/**
* An inserted gap.
*
* @since 3.11
*/
public static class Gap {
/**
* The position of the gap.
*/
public int position;
/**
* The length of the gap.
*/
public int length;
}
/**
* Inserts an inclusive or exclusive gap in front of the instruction
* at the given index pos
.
* Branch offsets and the exception table in the method body
* are also updated. The inserted gap is filled with NOP.
* The gap length may be extended to a multiple of 4.
*
*
Suppose that the instruction at the given index is at the * beginning of a block statement. If the gap is inclusive, * then it is included within that block. If the gap is exclusive, * then it is excluded from that block. * *
The index at which the gap is actually inserted
* might be different from pos since some other bytes might be
* inserted at other positions (e.g. to change GOTO
* to GOTO_W
). The index is available from the Gap
* object returned by this method.
*
*
Suppose that the gap is inserted at the position of
* the next instruction that would be returned by
* next()
(not the last instruction returned
* by the last call to next()
). The next
* instruction returned by next()
after the gap is
* inserted is still the same instruction. It is not NOP
* at the first byte of the inserted gap.
*
* @param pos the index at which a gap is inserted.
* @param length gap length.
* @param exclusive true if exclusive, otherwise false.
* @return the position and the length of the inserted gap.
* @since 3.11
*/
public Gap insertGapAt(int pos, int length, boolean exclusive)
throws BadBytecode
{
/**
* cursorPos indicates the next bytecode whichever exclusive is
* true or false.
*/
Gap gap = new Gap();
if (length <= 0) {
gap.position = pos;
gap.length = 0;
return gap;
}
byte[] c;
int length2;
if (bytecode.length + length > Short.MAX_VALUE) {
// currentPos might change after calling insertGapCore0w().
c = insertGapCore0w(bytecode, pos, length, exclusive,
get().getExceptionTable(), codeAttr, gap);
pos = gap.position;
length2 = length; // == gap.length
}
else {
int cur = currentPos;
c = insertGapCore0(bytecode, pos, length, exclusive,
get().getExceptionTable(), codeAttr);
// insertGapCore0() never changes pos.
length2 = c.length - bytecode.length;
gap.position = pos;
gap.length = length2;
if (cur >= pos)
currentPos = cur + length2;
if (mark > pos || (mark == pos && exclusive))
mark += length2;
}
codeAttr.setCode(c);
bytecode = c;
endPos = getCodeLength();
updateCursors(pos, length2);
return gap;
}
/**
* Is called when a gap is inserted. The default implementation is empty.
* A subclass can override this method so that cursors will be updated.
*
* @param pos the position where a gap is inserted.
* @param length the length of the gap.
*/
protected void updateCursors(int pos, int length) {
// empty
}
/**
* Copies and inserts the entries in the given exception table
* at the beginning of the exception table in the code attribute
* edited by this object.
*
* @param offset the value added to the code positions included
* in the entries.
*/
public void insert(ExceptionTable et, int offset) {
codeAttr.getExceptionTable().add(0, et, offset);
}
/**
* Appends the given bytecode sequence at the end.
*
* @param code the bytecode appended.
* @return the position of the first byte of the appended bytecode.
*/
public int append(byte[] code) {
int size = getCodeLength();
int len = code.length;
if (len <= 0)
return size;
appendGap(len);
byte[] dest = bytecode;
for (int i = 0; i < len; ++i)
dest[i + size] = code[i];
return size;
}
/**
* Appends a gap at the end of the bytecode sequence.
*
* @param gapLength gap length
*/
public void appendGap(int gapLength) {
byte[] code = bytecode;
int codeLength = code.length;
byte[] newcode = new byte[codeLength + gapLength];
int i;
for (i = 0; i < codeLength; ++i)
newcode[i] = code[i];
for (i = codeLength; i < codeLength + gapLength; ++i)
newcode[i] = NOP;
codeAttr.setCode(newcode);
bytecode = newcode;
endPos = getCodeLength();
}
/**
* Copies and appends the entries in the given exception table
* at the end of the exception table in the code attribute
* edited by this object.
*
* @param offset the value added to the code positions included
* in the entries.
*/
public void append(ExceptionTable et, int offset) {
ExceptionTable table = codeAttr.getExceptionTable();
table.add(table.size(), et, offset);
}
/* opcodeLegth is used for implementing nextOpcode().
*/
private static final int opcodeLength[] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3,
3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3,
3, 3, 3, 3, 3, 5, 0, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3,
5, 5
};
// 0 .. UNUSED (186), LOOKUPSWITCH, TABLESWITCH, WIDE
/**
* Calculates the index of the next opcode.
*/
static int nextOpcode(byte[] code, int index)
throws BadBytecode
{
int opcode;
try {
opcode = code[index] & 0xff;
}
catch (IndexOutOfBoundsException e) {
throw new BadBytecode("invalid opcode address");
}
try {
int len = opcodeLength[opcode];
if (len > 0)
return index + len;
else if (opcode == WIDE)
if (code[index + 1] == (byte)IINC) // WIDE IINC
return index + 6;
else
return index + 4; // WIDE ...
else {
int index2 = (index & ~3) + 8;
if (opcode == LOOKUPSWITCH) {
int npairs = ByteArray.read32bit(code, index2);
return index2 + npairs * 8 + 4;
}
else if (opcode == TABLESWITCH) {
int low = ByteArray.read32bit(code, index2);
int high = ByteArray.read32bit(code, index2 + 4);
return index2 + (high - low + 1) * 4 + 8;
}
// else
// throw new BadBytecode(opcode);
}
}
catch (IndexOutOfBoundsException e) {
}
// opcode is UNUSED or an IndexOutOfBoundsException was thrown.
throw new BadBytecode(opcode);
}
// methods for implementing insertGap().
static class AlignmentException extends Exception {}
/**
* insertGapCore0() inserts a gap (some NOPs).
* It cannot handle a long code sequence more than 32K. All branch offsets must be
* signed 16bits.
*
* If "where" is the beginning of a block statement and exclusive is false,
* then the inserted gap is also included in the block statement.
* "where" must indicate the first byte of an opcode.
* The inserted gap is filled with NOP. gapLength may be extended to
* a multiple of 4.
*
* This method was also called from CodeAttribute.LdcEntry.doit().
*
* @param where It must indicate the first byte of an opcode.
*/
static byte[] insertGapCore0(byte[] code, int where, int gapLength,
boolean exclusive, ExceptionTable etable, CodeAttribute ca)
throws BadBytecode
{
if (gapLength <= 0)
return code;
try {
return insertGapCore1(code, where, gapLength, exclusive, etable, ca);
}
catch (AlignmentException e) {
try {
return insertGapCore1(code, where, (gapLength + 3) & ~3,
exclusive, etable, ca);
}
catch (AlignmentException e2) {
throw new RuntimeException("fatal error?");
}
}
}
private static byte[] insertGapCore1(byte[] code, int where, int gapLength,
boolean exclusive, ExceptionTable etable,
CodeAttribute ca)
throws BadBytecode, AlignmentException
{
int codeLength = code.length;
byte[] newcode = new byte[codeLength + gapLength];
insertGap2(code, where, gapLength, codeLength, newcode, exclusive);
etable.shiftPc(where, gapLength, exclusive);
LineNumberAttribute na
= (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag);
if (na != null)
na.shiftPc(where, gapLength, exclusive);
LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute(
LocalVariableAttribute.tag);
if (va != null)
va.shiftPc(where, gapLength, exclusive);
LocalVariableAttribute vta
= (LocalVariableAttribute)ca.getAttribute(
LocalVariableAttribute.typeTag);
if (vta != null)
vta.shiftPc(where, gapLength, exclusive);
StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag);
if (smt != null)
smt.shiftPc(where, gapLength, exclusive);
StackMap sm = (StackMap)ca.getAttribute(StackMap.tag);
if (sm != null)
sm.shiftPc(where, gapLength, exclusive);
return newcode;
}
private static void insertGap2(byte[] code, int where, int gapLength,
int endPos, byte[] newcode, boolean exclusive)
throws BadBytecode, AlignmentException
{
int nextPos;
int i = 0;
int j = 0;
for (; i < endPos; i = nextPos) {
if (i == where) {
int j2 = j + gapLength;
while (j < j2)
newcode[j++] = NOP;
}
nextPos = nextOpcode(code, i);
int inst = code[i] & 0xff;
// if