package alt.jiapi.reflect;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import alt.jiapi.file.ConstantPool;
import alt.jiapi.reflect.instruction.CPInstruction;
import alt.jiapi.reflect.instruction.Opcodes;
/**
* InstructionList is a place holder for Instructions.
*
* @author Mika Riekkinen
*/
public class InstructionList {
private JiapiMethod declaringMethod;
ConstantPool constantPool;
List instructions;
// modified indicates whether or not bytes are up-to-date
private boolean modified = false;
// private byte[] bytes;
public InstructionList() {
this.constantPool = new ConstantPool();
this.instructions = new LinkedList();
}
/**
* Constructor for InstructionList. Bytecode of hte method is given
* as an argument to this Constructor. This bytecode is parsed and
* Instructions are created for each byte level instruction(opcode).
*
* @param byteCode Bytecode of a method.
*/
InstructionList(byte[] byteCode, ConstantPool cp) {
this.constantPool = cp;
InstructionFactory factory = new InstructionFactory(cp);
instructions = factory.createInstructionList(byteCode);
}
/**
* Constructor for InstructionList.
*
* @param byteCode Bytecode of a method.
*/
private InstructionList(ConstantPool cp, List instructions) {
this.constantPool = cp;
this.instructions = instructions;
}
/**
* Constructor for InstructionList.
*
* @param factory InstructionFactory
*/
InstructionList(ConstantPool cp) {
this.constantPool = cp;
this.instructions = new LinkedList();
}
/**
* Create an empty list.
*/
public InstructionList createEmptyList() {
return new InstructionList(constantPool);
}
/**
* Gets this InstructionList as a byte array. This byte array
* may be put into alt.jiapi.file.Method as a code attribute.
* No checking is being made, whether this byte array
* makes any sense or not. It is thus the responsibility of
* the developer to keep this list in a correct form.
*
* @return a byte array representing this InstructionList
*/
public byte[] getBytes() {
// Calculate byte-size of list
Iterator i = instructions.iterator();
int bSize = 0;
while(i.hasNext()) {
bSize += ((Instruction)i.next()).length();
}
// Copy each instructions bytes to main bytes list
byte[] bytes = new byte[bSize];
int bIdx = 0;
i = instructions.iterator();
while(i.hasNext()) {
Instruction ins = (Instruction)i.next();
byte[] iBytes = ins.getBytes();
for (int j = 0; j < iBytes.length; j++) {
bytes[bIdx] = iBytes[j];
bIdx++;
}
}
// Turn this flag to false.
this.modified = false;
return bytes;
}
/**
* Adds an Instruction to this InstructionList
*/
public void add(Instruction i) {
modified = true;
i.setAttribute("synthetic");
if (i instanceof CPInstruction) {
CPInstruction cpIns = (CPInstruction)i;
ConstantPool cp = cpIns.getConstantPool();
if (!constantPool.equals(cp)) {
cpIns.replaceConstantPool(constantPool);
}
}
instructions.add(i);
}
/**
* Adds all the Instructions in given list to this list.
*
* @param il InstructionList to add
*/
public void add(InstructionList il) {
modified = true;
if (il.size() == 0) {
return;
}
for (int i = 0; i < il.size(); i++) {
add(il.get(i));
}
}
/**
* Insert an Instruction to this InstructionList
*
* @param idx index of the Instruction
* @param i Instruction to insert
*/
public boolean insert(int idx, Instruction i) {
modified = true;
i.setAttribute("synthetic");
if (i instanceof CPInstruction) {
CPInstruction cpIns = (CPInstruction)i;
ConstantPool cp = cpIns.getConstantPool();
if (!constantPool.equals(cp)) {
cpIns.replaceConstantPool(constantPool);
}
}
return instructions.subList(0, idx).add(i);
}
/**
* Insert an InstructionList to this InstructionList
*
* @param idx index of the InstructionList
* @param i Instruction to insert
*/
public boolean insert(int idx, InstructionList il) {
modified = true;
if (il.size() == 0) {
return true;
}
// if (constantPool.equals(il.constantPool)) {
// // If two lists share constantpool, just insert all of them
// return instructions.subList(0, idx).addAll(il.instructions);
// //instructions.addAll(il.instructions);
// }
// else {
// else insert instructions one by one
for (int i = 0; i < il.size(); i++) {
insert(idx + i, il.get(i));
}
// }
return true;
}
/**
* Gets an Instruction at given index.
* IndexOutOfBoundsException is thrown if index is not within
* this InstructionList
*
* @param i index
* @return Instruction
*/
public Instruction get(int i) {
Instruction ins = (Instruction)instructions.get(i);
if (ins instanceof TargetInstruction) {
return ((TargetInstruction)ins).getTarget();
}
return ins;
}
/**
* Replace an Instruction at given index
*
* @param idx Index of the Instruction to replace
* @param idx Index of the Instruction to replace
*/
public void replace(int idx, Instruction i) {
if (i == null) {
// fail fast
throw new NullPointerException("Instruction may not be null");
}
// NOTE: We should make sure, that branches, exception table,
// etc. remain valid.
instructions.set(idx, i);
}
public void replace(InstructionList il) {
replace(0, size(), il);
}
/**
* Replaces instructions with given range. Stack-usages are
* checked before replace. If stack usages do not match,
* an exception is thrown.
* Furthermore, if instructions to be replaced contain
* some sort of 'target', like with BranchInstruction,
* an exception is thrown. Only first instruction to be replaced
* is allowed to be a 'target'. If this is the case, first
* Instruction in new list becames that target.<p>
*
* Note, that exception table might get corrupted with this method.
*
* @param start start of old Instructions, inclusive
* @param end end of old Instructions, exclusive
* @param il InstructionList containing new Instructions.
* @exception IllegalArgumentException is thrown, if stack-usages
* do not match
* @exception IllegalArgumentException is thrown, if there is
* an instruction in the range, that is a target to some
* other entity.
*/
public void replace(int start, int end, InstructionList il) {
InstructionList __il = createView(start, end);
int su1 = __il.stackUsage();
int su2 = il.stackUsage();
if (su1 != su2) {
System.out.println("Replacing ");
print(__il);
System.out.println("with");
print(il);
throw new IllegalArgumentException("Cannot replace instruction list; stack usages differ: " + su1 + "(this list), " + su2 + " (other list)");
}
Instruction first = (Instruction)__il.instructions.get(0);
if (first instanceof TargetInstruction) {
// If first Instruction is a TargetInstruction,
// Move it to new list
TargetInstruction tIns = (TargetInstruction)first;
tIns.setTarget((Instruction)il.get(0));
il.instructions.set(0, tIns);
}
// Check, if view contains targets. If so, throw a n exception
// Only forst is allowed to be a target, so skip it
for (int i = 1; i < __il.size(); i++) {
if (__il.instructions.get(i) instanceof TargetInstruction) {
throw new IllegalArgumentException("Cannot replace instructions since there are at least one 'target' instruction in it");
}
}
// replace instructions
__il.clear();
__il.add(il);
}
/**
* Calculates stack-usage of this InstructionList
*
* @return stack usage
*/
public int stackUsage() {
int su = 0;
for (int i = 0; i < size(); i++) {
su += get(i).stackUsage();
}
return su;
}
/**
* Gets the size of this InstructionList.
*
* @return number of instructions in this InstructionList
*/
public int size() {
return instructions.size();
}
/**
* Scans this InstructionList for a opcode. Scanning is started from
* the beginning of the InstructionList.
*
* @param ins Instruction to look for
* @return index of the instruction that was found, or -1 if not found.
*/
public int indexOf(Instruction ins) {
return instructions.indexOf(ins);
}
/**
* Scans this InstructionList for a opcode. Scanning is started from
* the beginning of the InstructionList.
*
* @param opcode Opcode to look for
* @return index of the instruction that was found, or -1 if not found.
*/
public int indexOf(byte opcode) {
return indexOf(new byte[]{opcode}, 0);
}
/**
* Scans this InstructionList for a opcode.
*
* @param start Index to start scanning from
* @param opcode Opcode to look for
* @return index of the instruction that was found, or -1 if not found.
*/
public int indexOf(byte opcode, int fromIndex) {
return indexOf(new byte[]{opcode}, fromIndex);
}
/**
* Scans this InstructionList for opcodes.
*
* @param fromIndex Index to start scanning from
* @param opcodes opcode to look for
* @return index of the instruction that was found, or -1 if not found.
*/
public int indexOf(byte[] opcodes, int fromIndex) {
for (int i = fromIndex; i < instructions.size(); i++) {
Instruction ins = (Instruction)instructions.get(i);
short opCode = ins.getOpcode();
for (int j = 0; j < opcodes.length; j++) {
if (opcodes[j] == opCode) {
return i;
}
}
}
return -1;
}
int offset(Instruction i) {
return offset(0, i);
}
int offset(int start, Instruction ins) {
int offset = 0;
for (int i = start; i < instructions.size(); i++) {
if (instructions.get(i).equals(ins)) {
return offset;
}
offset += ((Instruction)instructions.get(i)).length();
}
throw new JiapiRuntimeException("Isntruction not found; " + ins);
}
/**
* Get the Instruction at given offset.
*
* @return Instruction at offset, or null if there is no instruction
* at given offset.
*/
Instruction instructionAtOffset(short offset) {
Iterator i = instructions.iterator();
while(i.hasNext()) {
Instruction ins = (Instruction)i.next();
if (ins.getOffset() == offset) {
return ins;
}
if (ins.getOffset() > offset) {
break;
}
}
return null;
}
public String toString() {
if (instructions.size() == 0) {
return "<empty>";
}
StringBuffer sb = new StringBuffer();
Iterator i = instructions.iterator();
int offset = 0;
int count = 0;
while(i.hasNext()) {
Instruction ins = (Instruction)i.next();
sb.append(" #");
sb.append(count);
if (count < 10) {
sb.append(' ');
}
if (count < 100) {
if (size() >= 100) {
sb.append(' ');
}
}
sb.append(" (");
sb.append(offset);
sb.append(") ");
sb.append(ins.toString());
if (i.hasNext()) {
sb.append('\n');
}
count++;
offset += ins.length();
}
return sb.toString();
}
private boolean isModified() {
return modified;
}
/**
* Clears this InstructionList. All the Instructions are removed.
*/
public void clear() {
instructions.clear();
}
/**
* Get the InstructionFactory, that is associated with this
* InstructionList.
*/
public InstructionFactory getInstructionFactory() {
return new InstructionFactory(constantPool);
}
/**
* Gets the declaring JiapiMethod of this InstructionList
*/
public JiapiMethod getDeclaringMethod() {
return declaringMethod;
}
// Package protected methods
void setDeclaringMethod(JiapiMethod jm) {
this.declaringMethod = jm;
}
/**
* Creates a view. View is created from <code>start</code> to the
* end of this list.
*
* @param start start of the view, inclusive
*/
public InstructionList createView(int start) {
return createView(0, instructions.size());
}
/**
* Creates a view.
*
* @param start start of the view, inclusive
* @param end end of the view, exclusive
*/
public InstructionList createView(int start, int end) {
InstructionList il =
new InstructionList(constantPool,instructions.subList(start, end));
il.setDeclaringMethod(declaringMethod);
return il;
}
/**
* Updates offsets of each instruction.
*/
void updateOffsets() {
short offset = 0;
Iterator i = instructions.iterator();
while(i.hasNext()) {
Instruction ins = (Instruction)i.next();
ins.setOffset(offset);
// BUG: If instruction needs byte padding,
// we must do it here.
// if (ins instanceof CaseInstruction) ....
offset += ins.length();
}
}
private void print(InstructionList il) {
for (int i = 0; i < il.size(); i++) {
Instruction ins = il.get(i);
System.out.println(" #" + (i) + " " + ins + ", stack-usage: " +
ins.stackUsage());
}
}
/**
* Change usage of local variables in target InstructionList so,
* that it will not overlap with 'other' list.
*
* If Advice declares local variables, they start from index 0,
* but so do local vars in target class. So this method changes
* local variable accesses in advice to start from
* <target.getMaxLocals()>
*
* @param advice InstructionList to change
* @param maxLocalsInOtherList Max-Locals in other list
*/
private Instruction changeLocalVars(Instruction ins) {
// if (maxLocalsInOtherList == 0) {
// // Nothing to do, other list does not contain
// // local variables
// return advice;
// }
// BUG: These should not be needed. For some reason,
// JiapiMethod.getMaxLocals() returns wrong values?
// Commenting these out has no harm other than
// missing unused local variable slots
int maxLocals = getDeclaringMethod().getMaxLocals();
// following could be added, if no longs/doubles are in target
// list; they reserve two slots for local vars. Another
// silly stuff for longs/doubles in JVMs
// maxLocalsInOtherList--;
InstructionFactory factory = new InstructionFactory();
switch(ins.getOpcode()) {
// -- ALOAD family --------------------------------------
case Opcodes.ALOAD_0:
return factory.aload(maxLocals);
case Opcodes.ALOAD_1:
return factory.aload(maxLocals + 1);
case Opcodes.ALOAD_2:
return factory.aload(maxLocals + 2);
case Opcodes.ALOAD_3:
return factory.aload(maxLocals + 3);
case Opcodes.ALOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
case Opcodes.ASTORE_0:
return factory.astore(maxLocals);
case Opcodes.ASTORE_1:
return factory.astore(maxLocals + 1);
case Opcodes.ASTORE_2:
return factory.astore(maxLocals + 2);
case Opcodes.ASTORE_3:
return factory.astore(maxLocals + 3);
case Opcodes.ASTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
// -- ILOAD family --------------------------------------
case Opcodes.ILOAD_0:
return factory.iload(maxLocals);
case Opcodes.ILOAD_1:
return factory.iload(maxLocals + 1);
case Opcodes.ILOAD_2:
return factory.iload(maxLocals + 2);
case Opcodes.ILOAD_3:
return factory.iload(maxLocals + 3);
case Opcodes.ILOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
case Opcodes.ISTORE_0:
return factory.istore(maxLocals);
case Opcodes.ISTORE_1:
return factory.istore(maxLocals + 1);
case Opcodes.ISTORE_2:
return factory.istore(maxLocals + 2);
case Opcodes.ISTORE_3:
return factory.istore(maxLocals + 3);
case Opcodes.ISTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
// -- DLOAD family --------------------------------------
case Opcodes.DLOAD_0:
return factory.dload(maxLocals);
case Opcodes.DLOAD_1:
return factory.dload(maxLocals + 1);
case Opcodes.DLOAD_2:
return factory.dload(maxLocals + 2);
case Opcodes.DLOAD_3:
return factory.dload(maxLocals + 3);
case Opcodes.DLOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
case Opcodes.DSTORE_0:
return factory.dstore(maxLocals);
case Opcodes.DSTORE_1:
return factory.dstore(maxLocals + 1);
case Opcodes.DSTORE_2:
return factory.dstore(maxLocals + 2);
case Opcodes.DSTORE_3:
return factory.dstore(maxLocals + 3);
case Opcodes.DSTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
// -- LLOAD family --------------------------------------
case Opcodes.LLOAD_0:
return factory.lload(maxLocals);
case Opcodes.LLOAD_1:
return factory.lload(maxLocals + 1);
case Opcodes.LLOAD_2:
return factory.lload(maxLocals + 2);
case Opcodes.LLOAD_3:
return factory.lload(maxLocals + 3);
case Opcodes.LLOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
case Opcodes.LSTORE_0:
return factory.lstore(maxLocals);
case Opcodes.LSTORE_1:
return factory.lstore(maxLocals + 1);
case Opcodes.LSTORE_2:
return factory.lstore(maxLocals + 2);
case Opcodes.LSTORE_3:
return factory.lstore(maxLocals + 3);
case Opcodes.LSTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
// -- FLOAD family --------------------------------------
case Opcodes.FLOAD_0:
return factory.fload(maxLocals);
case Opcodes.FLOAD_1:
return factory.fload(maxLocals + 1);
case Opcodes.FLOAD_2:
return factory.fload(maxLocals + 2);
case Opcodes.FLOAD_3:
return factory.fload(maxLocals + 3);
case Opcodes.FLOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
case Opcodes.FSTORE_0:
return factory.fstore(maxLocals);
case Opcodes.FSTORE_1:
return factory.fstore(maxLocals + 1);
case Opcodes.FSTORE_2:
return factory.fstore(maxLocals + 2);
case Opcodes.FSTORE_3:
return factory.fstore(maxLocals + 3);
case Opcodes.FSTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocals;
break;
}
return ins;
}
}