//
// This file is part of the prose package.
//
// 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. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// 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.
//
// The Original Code is prose.
//
// The Initial Developer of the Original Code is Angela Nicoara. Portions
// created by Angela Nicoara are Copyright (C) 2004 Angela Nicoara.
// All Rights Reserved.
//
// Contributor(s):
// $Id: HotSwapProceedWeaver.java,v 1.1 2008/11/18 11:12:07 anicoara Exp $
// =====================================================================
//
package ch.ethz.inf.iks.jvmai.jvmdi;
import org.apache.bcel.generic.*;
import ch.ethz.jvmai.IllegalProceedUsageException;
import ch.ethz.jvmai.JVMAIRuntimeException;
import java.util.Vector;
import java.util.Iterator;
/**
* Weaves proceed statements of an advice into redefined methods.
* <P>
* Proceed invocations must be replaced with <CODE>prepareAdvice()</CODE>
* before the proceed subroutine may be inlined into the redefined target method.
* <P>
* The 'proceed' code must be inlined with <CODE>weaveRedefinedMethod()</CODE>
* after <CODE>target</CODE> was redefined with <CODE>advice</CODE>.
* <P>
* Proceed statements will be inlined.
* <P>
* @version $Revision: 1.1 $
*
* @author Angela Nicoara
* @author Gerald Linhofer
*/
public class HotSwapProceedWeaver {
private final static ObjectType rest = new ObjectType("ch.ethz.prose.crosscut.REST");
private final static ObjectType any = new ObjectType("ch.ethz.prose.crosscut.ANY");
private final static ObjectType booleanType = new ObjectType("java.lang.Boolean");
private final static ObjectType byteType = new ObjectType("java.lang.Byte");
private final static ObjectType characterType = new ObjectType("java.lang.Character");
private final static ObjectType doubleType = new ObjectType("java.lang.Double");
private final static ObjectType floatType = new ObjectType("java.lang.Float");
private final static ObjectType integerType = new ObjectType("java.lang.Integer");
private final static ObjectType longType = new ObjectType("java.lang.Long");
private final static ObjectType shortType = new ObjectType("java.lang.Short");
/// BCEL generator for the advice method (that may contain proceed statements)
private final MethodGen advice;
private final ConstantPoolGen adviceCP;
/// BCEL generator for the target method (that will be executed for <CODE>proceed()</CODE>)
private final MethodGen target;
/// <CODE>true</CODE> after <CODE>prepareAdvice()</CODE> invocations was invoked.
private boolean isAdvicePrepared;
// fields that are initialized after 'prepareProceedInstruction()' was called
/// Collection that holds all found <CODE>proceed()</CODE> invocations
private Vector proceedInstructionHandles; // for JDK < 1.5 (compatible with newer JVMs)
//private Vector<ProceedListEntry> proceedInstructionHandles; // for JDK >= 1.5 (not compatible with older JVMs)
private HotSwapProceedCompiler proceedCompiler = null;
/**
* Entry for <CODE>proceedInstructionHandles</CODE>
*/
private static class ProceedListEntry {
// handle of the proceed invoking instruction
final InstructionHandle proceed;
// types of the arguments explicitly passed to 'proceed()'
final Type[] arguments;
// next instruction that will be executed after proceed, if it terminates regulary
InstructionHandle executeAfterProceed;
// instruction that will be executed if proceed throws an exception
InstructionHandle executeAfterException;
ProceedListEntry(InstructionHandle proceed, Type[] arguments) {
this.proceed = proceed;
this.arguments = arguments;
}
}
/**
* Constructor.
*
* @param advice advice that redefines <CODE>target<CODE>.
* @param adviceCP constant pool of the class declaring <CODE>advice</CODE>.
* @param target method that will be redefined by <CODE>advice</CODE>.
*/
public HotSwapProceedWeaver(MethodGen advice, ConstantPoolGen adviceCP, MethodGen target) {
this.advice = advice;
this.adviceCP = adviceCP;
this.target = target;
}
/**
* Constructor.
*
* @param advice advice that redefines <CODE>target<CODE>.
* @param target method that will be redefined by <CODE>advice</CODE>.
*/
public HotSwapProceedWeaver(MethodGen advice, MethodGen target) {
this(advice, advice.getConstantPool(), target);
}
/**
* Registers all <CODE>proceed()</CODE> invocations for a later weaving of
* the 'proceed' code.
* <P>
* Removes all <CODE>this</CODE> arguments, which are implicitly passed
* as argument to <CODE>proceed()</CODE> methods.
* <P>
* NOTE: Only <CODE>proceed()</CODE> methods that are members of the crosscut
* defining <CODE>advice</CODE> are recognized as <CODE>proceed</CODE>
* statement.
* <P>
* NOTE: the proceed code must still be woven into the redefined target method
* with <CODE>weaveRedefinedMethod()</CODE>.
*
* @return <CODE>true</CODE> if any <CODE>proceed</CODE> invocation was replaced.
*/
public boolean prepareAdvice() {
if(!isAdvicePrepared) {
isAdvicePrepared = true;
// Iterate over all instructions. Check if its a 'proceed' invocation
// and replace it if so.
//
// (Iteration from the last to the first instruction is faster, forward
// iteration would do also proceed checks for instructions that are later
// removed by 'prepareProceedInstruction()'.)
InstructionList instructions = advice.getInstructionList();
for(InstructionHandle h = instructions.getEnd();
null != h;
h = h.getPrev())
{
if(isProceedInvokation(h))
// prepare the proceed invokation for weaving
prepareProceedInstruction(h, instructions);
}
}
return hasProceed();
}
/**
* Indicates if <CODE>replaceProceedInvokation()</CODE> found any <CODE>proceed()</CODE>
* statement. This will always return <CODE>false</CODE> if <CODE>replaceProceedInvokation()</CODE>
* was not invoked before.
* <P>
* NOTE: Only <CODE>proceed()</CODE> methods that are members of the crosscut
* defining <CODE>advice</CODE> are recognized as <CODE>proceed</CODE>
* statement.
*
* @return <CODE>true</CODE> if <CODE>replaceProceedInvokations()<CODE> found
* any proceed statement.
*/
public boolean hasProceed() {
return null != proceedInstructionHandles;
}
/**
* Returns <CODE>true</CODE> if <CODE>ih</CODE> contains a proceed statement.
* <P>
* Proceed statements are invocations of a method thats name starts with proceed
* and which belongs the the <CODE>MethodRedefineCut</CODE> defining <CODE>advice</CODE>
*
* @param ih handle of the instruction to check.
* @return <CODE>true</CODE> if <CODE>ih</CODE> contains a proceed statement
*/
public boolean isProceedInvokation(InstructionHandle ih) {
boolean returnValue = false;
Instruction instr = ih.getInstruction();
// 1. Check if the instruction is a invoke instruction
if (instr instanceof InvokeInstruction) {
InvokeInstruction iinstr = (InvokeInstruction)instr;
// 2. Check if its invokes a 'proceed*()' method belonging
// to the crosscut from which the advice comes.
String proceedDeclaringClassName = iinstr.getLoadClassType(adviceCP).getClassName(); //angy-BUGFIX - Remote aspect insertion
if (advice.getClassName().equals(proceedDeclaringClassName) || "ch.ethz.prose.crosscut.MethodRedefineCut".equals(proceedDeclaringClassName)) { //angy-BUGFIX - Remote aspect insertion
// 3. Check if the complete method name is that of a valid proceed method.
String methodName = iinstr.getMethodName(adviceCP);
if (
"proceed".equals(methodName) ||
"proceedBoolean".equals(methodName) ||
"proceedByte".equals(methodName) ||
"proceedChar".equals(methodName) ||
"proceedDouble".equals(methodName) ||
"proceedFloat".equals(methodName) ||
"proceedInt".equals(methodName) ||
"proceedLong".equals(methodName) ||
"proceedObject".equals(methodName) ||
"proceedBooleanArray".equals(methodName) ||
"proceedByteArray".equals(methodName) ||
"proceedCharArray".equals(methodName) ||
"proceedDoubleArray".equals(methodName) ||
"proceedFloatArray".equals(methodName) ||
"proceedIntArray".equals(methodName) ||
"proceedLongArray".equals(methodName) ||
"proceedObjectArray".equals(methodName)
)
returnValue = true;
}
}
return returnValue;
}
/**
* Initializes for weaving if <CODE>prepareAdvice()</CODE> finds
* a proceed statement.
* <P>
* This make copies of the parts of <CODE>target</CODE> which are
* required for proceed weaving.
*/
private void initProceed() {
if(null == proceedInstructionHandles) {
proceedInstructionHandles = new Vector(); // for JDK < 1.5 (compatible with newer JVMs)
//proceedInstructionHandles = new Vector<InstructionHandle>(); // for JDK < 1.5 (compatible with newer JVMs)
}
}
/**
* Registers a <CODE>proceed()</CODE> invocation instructions handle to
* <CODE>proceedInstructionHandles</CODE> and removes the <CODE>this</CODE>
* Argument of the invokation.
*
* @param ih handle for the <CODE>proceed()</CODE> invocation.
* @param instructions instruction list containing the <CODE>proceed()</CODE> invocation.
*/
public void prepareProceedInstruction(InstructionHandle ih, InstructionList instructions) {
// 1. Make sure that the environment is prepared
initProceed();
InvokeInstruction instr = (InvokeInstruction)ih.getInstruction();
Type[] definedParameters = instr.getArgumentTypes(adviceCP);
// 2. remove VARARGS array (if required)
int maxDefined = definedParameters.length - 1;
if(
maxDefined >= 0 &&
definedParameters[maxDefined] instanceof ArrayType
) {
// may have varargs (check if the real method has also an array argument at
// the same position)
ArrayType varArgType = (ArrayType)definedParameters[maxDefined];
Type[] targetParameters = target.getArgumentTypes();
try {
if(
targetParameters.length <= maxDefined ||
!varArgType.isAssignmentCompatibleWith(targetParameters[maxDefined])
) {
// 2.a. has varargs (only in 'proceed()' declaration, but not in the signature
// of the redefined method.
// 2.a.1. remove array instructions (but let the entries on the stack)
int n = removeVarargsArrayInstructions(ih, instructions);
// 2.a.2. create replacement for 'definedParameters' holding also the
// arguments passed via varargs.
Type[] newDefinedParameters = new Type[maxDefined + n];
// 2.a.2.1. copy the existing entries from 'definedParameters' to its replacement
for(int i = maxDefined - 1; i >= 0; i--)
newDefinedParameters[i] = definedParameters[i];
// 2.a.2.2. add the entries from 'varargs' to the new 'definedParameters'
Type baseType = varArgType.getElementType();
for(int i = maxDefined; i < newDefinedParameters.length; i++)
newDefinedParameters[i] = baseType;//Type.OBJECT;
// 2.a.2.3. replace 'definedParameters'
definedParameters = newDefinedParameters;
}
} catch(ClassNotFoundException e) {throw new JVMAIRuntimeException(e.getMessage());}
}
// 3. remove signature pattern arguments (if any).
// All instructions accessing arguments of type 'ANY' or 'REST'
// must be removed before redefining 'target' method.
// 3.1. create a mapping from the argument slots to there types
// (probably faster than mapping all local variables)
Type[] adviceParameters = advice.getArgumentTypes();
// 3.1.1. calculate the number of slots used for arguments
int argSlot = advice.isStatic() ? 0 : 1;
int argumentSlots = argSlot;
for(int i = 0; i < adviceParameters.length; i++)
argumentSlots += adviceParameters[i].getSize();
// 3.1.2. create the map
Type[] localVariableMapper = new Type[argumentSlots];
// 3.1.3. fill the map (only entries which are mapped to an argument).
// if an argument needs more than 1 slot following slot will be
// empty.
for(int i = 0; i < adviceParameters.length; i++) {
localVariableMapper[argSlot] = adviceParameters[i];
argSlot += adviceParameters[i].getSize();
}
// 3.2. check all instructions that are loading arguments from
// the local variable table to the stack if they are accessing
// any argument of type 'ANY' or 'REST'
//
// The problem is that 'definedParameters' holds the types for
// the arguments used in the declaration of 'proceed()', but
// the advice may passe ANY or REST in places where 'proceed()'
// declares an argument of type java.lang.Object.
//
InstructionHandle h = ih;
for(int i = definedParameters.length - 1; i >= 0; i--) {
// 3.2.1. get the instruction
InstructionHandle currentHandle = getPreviousArgInstr(h);
Instruction argInstr = currentHandle.getInstruction();
// 3.2.2. check if it's a load instruction for an unmodified argument (of the advice)
int slot;
if(
argInstr instanceof LoadInstruction &&
(slot =((LoadInstruction)argInstr).getIndex()) < localVariableMapper.length
) {
// 3.2.2.a. loads an unmodified argument -> remove argument if required
// 3.2.2.a.a remove argument of type 'REST'
if(rest.equals(localVariableMapper[slot])) {
removeInstruction(h.getPrev(), instructions, h);
definedParameters[i] = rest;
}
// 3.2.2.a.b. remove argument of type 'ANY'
else if(any.equals(localVariableMapper[slot])) {
removeInstruction(h.getPrev(), instructions, h);
definedParameters[i] = any;
}
else // 3.2.2.a.c. go ahead (no signuture pattern type)
h = currentHandle;
}
else // 3.2.3.b. go ahead
h = currentHandle;
}
// 4. remove (implicit) 'this' argument loading instruction if required.
// (NOTE: static methods have no 'this' argument!)
if(!(instr instanceof INVOKESTATIC)) {
Instruction inst = h.getPrev().getInstruction();
// // only for debugging:
// if(!(inst instanceof ALOAD) || ((ALOAD)inst).getIndex() != 0) {
// //System.err.println(inst + " is not ALOAD_0");
// throw new JVMAIRuntimeException(
// this.getClass().getName() +
// " INTERNAL ERRROR: cannot remove (implicite) this argument of a proceed statement in "
// + advice.getClassName()
// );
// }
removeInstruction(h.getPrev(), instructions, h);
}
// 5. add the new entry to 'proceedInstructionHandles'
ProceedListEntry newEntry = new ProceedListEntry(ih, definedParameters);
proceedInstructionHandles.add(newEntry);
}
/**
* Remove instructions that are creating a vararg array. This instructions are created
* by java compilers to passe arguments to an method defining a vararg argument.
* <P>
* The method arguments that where wrapped into the array are left on the stack
* in order to invoke a method that has the passed arguments explicitly defined
* in its signature.
*
* @param invokeProceed handle holding the <CODE>proceed()</CODE> invocation
* @param instructions instruction list containing <CODE>inovkeProceed()</CODE>
* @return number of arguments in the varargs array
*/
final private int removeVarargsArrayInstructions(InstructionHandle invokeProceed, InstructionList instructions) {
// Example pseudo bytecode for proceed(Object...o):
//
// ICONST <size> creation of the array
// ANEWARRAY <type>
// ---------------------------
// DUP insertion of the first entry
// ICONST 0
// (instructions that put the
// new entry on the stack)
// AASTORE
// ---------------------------
// ...
// ---------------------------
// DUP insertion of the last entry
// ICONST <max index>
// ...
// AASTORE
// ---------------------------
// INVOKE proceed statement
//
//System.out.println("Entering: HotSwapProceedWeaver.removeVarargsArrayInstructions()");
int retVal = 0;
InstructionHandle currentHandle = invokeProceed.getPrev();
// remove setting of array entries (let the entries on the stack)
Instruction instr;
while(
!((instr = currentHandle.getInstruction()) instanceof ANEWARRAY)
&& !(instr instanceof NEWARRAY)
) {
// remove one array element insertion
InstructionHandle entryHandle = getPreviousArgInstr(currentHandle);
// 1. remove 'AASTORE' (last insertion instruction, stores the new entry into the array)
// // only for debugging:
// if(!(instr instanceof ArrayInstruction))
// throw new JVMAIRuntimeException(this.getClass().getName() + " INTERNAL ERROR on vararg handling: expected <AASTORE> but <" + instr + ">");
removeInstruction(currentHandle, instructions, currentHandle.getNext());
// 2. remove 'DUP' (first insertion instruction, copies the reference to the array)
currentHandle = entryHandle.getPrev();
// // only for debugging:
// if(!(currentHandle.getInstruction() instanceof ICONST))
// throw new JVMAIRuntimeException(this.getClass().getName() + " INTERNAL ERROR on vararg handling: expected <ICONST x> but <" + instr + ">");
removeInstruction(currentHandle, instructions, entryHandle);
// 3. remove 'ICONST <index>' (second insertion instruction, sets the index of the entry)
currentHandle = entryHandle.getPrev();
// // only for debugging:
// if(!(currentHandle.getInstruction() instanceof DUP))
// throw new JVMAIRuntimeException(this.getClass().getName() + " INTERNAL ERROR on vararg handling: expected <DUP> but <" + instr + ">");
removeInstruction(currentHandle, instructions, entryHandle);
currentHandle = entryHandle.getPrev();
retVal++;
}
// remove array creation code
// // only for debugging: check if we did all right
// if(retVal != ((ICONST)currentHandle.getPrev().getInstruction()).getValue().intValue())
// throw new JVMAIRuntimeException(
// "HotSwapProceedWeaver INTERNAL ERROR: something went wrong during codegeneration for unwrapping VARARGS"
// );
// remove pushing of array size onto the stack
removeInstruction(currentHandle.getPrev(), instructions, currentHandle.getNext());
// remove array creation instruction
removeInstruction(currentHandle, instructions, currentHandle.getNext());
return retVal;
}
/**
* Finds and returns the first instruction in a 'sequence' of instructions, which are
* producing a non-void value on the stack.
* <P>
* NOTE: The 'sequence' may consist of only one single instruction.
* <P>
* Example for such a bytecode sequence: <CODE>
* |...|ILOAD_1|ICONST_1|IADD|ISTORE_2|...|
* first last after
* </CODE>
*
* @param afterSequence first instruction after the 'sequence'.
* @return first instruction of a 'sequence' of instructions putting a (non-void) value
* on the stack. (this may be <CODE>null</CODE> if there is no such sequence in front of
* <CODE>afterSequence</CODE>).
*/
final private InstructionHandle getPreviousArgInstr(InstructionHandle afterSequence) {
InstructionHandle retVal = afterSequence.getPrev();
int stackSize = 0;
// precondition: 'retVal' is the last instruction for a sequence of instructions
// (with minimal length 1), which are putting an value (of unknown
// 'non-void' type) on the stack.
// postcondition: 'retVal' reference the first instruction in the sequence described
// above
while(null != retVal) {
Instruction instr = retVal.getInstruction();
stackSize += instr.produceStack(adviceCP);
stackSize -= instr.consumeStack(adviceCP);
if(0 < stackSize)
// as soon as an instruction is reached, which brings the cummulated
// 'stackSize' to a positive value is reached, the sequence is complete.
//
// assumption: long and double values are always produced by instructions
// that push/pop the full value at once to/from the stack
// (in other words: no different instructions for the uppor
// and lower part).
break;
retVal = retVal.getPrev();
}
return retVal;
}
/**
* Removes instruction <CODE>ih</CODE> form instruction list <CODE>il</CODE>.
* References to <CODE>ih</CODE> will be redirected to <CODE>newTarget</CODE>.
*
* @param ih handle for the instruction to remove.
* @param il instruction list containing <CODE>ih</CODE> and <CODE>newTarget</CODE>.
* @param newTarget handle to which all existing references to <CODE>ih</CODE> will
* be redirected.
*/
static final private void removeInstruction(InstructionHandle ih, InstructionList il, InstructionHandle newTarget) {
try {il.delete(ih);}
catch(TargetLostException e) {
InstructionHandle[] targets = e.getTargets();
for (int i = 0; i < targets.length; i++) {
InstructionTargeter[] etargeters = targets[i].getTargeters();
for (int j = 0; j < etargeters.length; j++) {
try {
etargeters[j].updateTarget(targets[i], newTarget);
} catch(ClassGenException cge) {
// TODO: why are there still some exceptions here?
// System.err.println();
// System.err.println(cge.getMessage() + ":");
// System.err.println(etargeters[i]);
// System.err.println();
// cge.printStackTrace();
// System.err.println();
}
}
}
}
}
/**
* Replaces an instruction by another one
* @param newInstr instruction to insert.
* @param oldInstr handle for the instruction to be replaced.
* @param instructions instruction list containing <CODE>oldInstr</CODE>.
* @return the handle of the inserted instruction.
*/
static final private InstructionHandle replaceInstruction(BranchInstruction newInstr, BranchHandle oldInstr, InstructionList instructions) {
// replace instruction
oldInstr.setInstruction(newInstr);
return oldInstr;
}
/**
* Replaces an instruction by another one
* @param newInstr instruction to insert.
* @param oldInstr handle for the instruction to be replaced.
* @param instructions instruction list containing <CODE>oldInstr</CODE>.
* @return the handle of the inserted instruction.
*/
static final private InstructionHandle replaceInstruction(BranchInstruction newInstr, InstructionHandle oldInstr, InstructionList instructions) {
// replace handle
InstructionHandle retVal = instructions.append(oldInstr, newInstr);
removeInstruction(oldInstr, instructions, retVal);
return retVal;
}
/**
* Replaces an instruction by another one
* @param newInstr instruction to insert.
* @param oldInstr handle for the instruction to be replaced.
* @param instructions instruction list containing <CODE>oldInstr</CODE>.
* @return the handle of the inserted instruction.
*/
static final private InstructionHandle replaceInstruction(Instruction newInstr, BranchHandle oldInstr, InstructionList instructions) {
// replace handle
InstructionHandle retVal = instructions.append(oldInstr, newInstr);
removeInstruction(oldInstr, instructions, retVal);
return retVal;
}
/**
* Replaces an instruction by another one
* @param newInstr instruction to insert.
* @param oldInstr handle for the instruction to be replaced.
* @param instructions instruction list containing <CODE>oldInstr</CODE>.
* @return the handle of the inserted instruction.
*/
static final private InstructionHandle replaceInstruction(Instruction newInstr, InstructionHandle oldInstr, InstructionList instructions) {
// replace instruction
oldInstr.setInstruction(newInstr);
return oldInstr;
}
/**
* Appends instructions to copy a local variable from one slot to another.
* @param oldSlot local variable slot of the original variable
* @param newSlot local variable slot for the copy of the variable
* @param type type of the local variable
* @param instructions instruction list to append the new instructions
*/
private final static void copyLocalVariable(int oldSlot, int newSlot, Type type, InstructionList instructions) {
// 1. push the original argument from the local variable table to the stack
instructions.append(InstructionFactory.createLoad(type, oldSlot));
// 2. copy the argument to its new place in the local variable table
instructions.append(InstructionFactory.createStore(type, newSlot));
}
/**
* Creates instructions for <A HREF=http://java.sun.com/j2se/1.5.0/docs/guide/language/autoboxing.html>Autoboxing</A>
* if required.
* <P>
* Autoboxing wrappes primitive types into java objects and vice versa, if an primitive
* type is assigned to an object or an object to a primitive type.
* <P>
* The created instruction(s) (if any is created) takes the top value from the stack
* and replaces it with an in- or outboxed version.
*
* @param inType
* @param outType
* @param instructions
* @param cpGen
*/
private final static void insertAutoBoxingInstructions(Type inType, Type outType, InstructionList instructions, ConstantPoolGen cpGen) {
if(inType.equals(outType))
// No boxing required if inType and outType are of same type
return;
if(inType instanceof ReferenceType && outType instanceof ReferenceType)
try{
if(((ReferenceType)inType).isAssignmentCompatibleWith(outType))
// No boxing required if inType may be assigned to outType
return;
//gery-angy else
//gery-angy throw new IllegalProceedUsageException("incompatible argument or return type for 'proceed()'");
} catch(ClassNotFoundException e) {throw new JVMAIRuntimeException(e.getMessage());}
if(!(inType instanceof ObjectType) && !(outType instanceof ObjectType))
throw new IllegalProceedUsageException("cannot convert from " + inType + " to " + outType + " for 'proceed()' statement (wrong type for an argument or return value)");
if(inType instanceof ObjectType) {
// outboxing (object -> primitive type)
//
// NOTE: insert inserts always at the begining of the instruction list
// so the instructions are inserted in reverse order
// (the last inserted instruction will be the first in the list).
//
// Casting may cause runtime exceptions, it may be safer to not
// insert the 'CHECKCAST' instruction.
//
try {
ObjectType rType = (ObjectType)inType;
boolean noCast;
if(Type.BOOLEAN.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(booleanType))
|| booleanType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Boolean", "booleanValue", "()Z")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Boolean")));
return;
}
}
else if(Type.BYTE.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(byteType))
|| byteType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Byte", "byteValue", "()B")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Byte")));
return;
}
}
else if(Type.CHAR.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(characterType))
|| characterType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Character", "charValue", "()C")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Character")));
return;
}
}
else if(Type.DOUBLE.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(doubleType))
|| doubleType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Double", "doubleValue", "()D")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Double")));
return;
}
}
else if(Type.FLOAT.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(floatType))
|| floatType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Float", "floatValue", "()F")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Float")));
return;
}
}
else if(Type.INT.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(integerType))
|| integerType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Integer", "intValue", "()I")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Integer")));
return;
}
}
else if(Type.LONG.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(longType))
|| longType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Long", "longValue", "()J")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Long")));
return;
}
}
else if(Type.SHORT.equals(outType)) {
if(
(noCast = rType.isAssignmentCompatibleWith(shortType))
|| shortType.isAssignmentCompatibleWith(rType)
) {
instructions.insert(new INVOKEVIRTUAL(cpGen.addMethodref("java.lang.Short", "shortValue", "()S")));
if(!noCast)
instructions.insert(new CHECKCAST(cpGen.addClass("java.lang.Short")));
return;
}
}
else { //gery-angy
instructions.insert(new CHECKCAST(cpGen.addClass(outType.toString()))); //gery-angy
return; //gery-angy
} //gery-angy
// throw new IllegalProceedUsageException("cannot outbox from " + inType + " to " + outType); //BEFORE
} catch(ClassNotFoundException e) {throw new JVMAIRuntimeException(e.getMessage());}
}
else {
// inboxing (primitive type -> object)
//
// Stack instructions have to move the arguments for the constructor
// into the right position on the stack.
// for types that need one word:
// word2, word1 -> word1, word1, word2
// for types that need two words:
// word3, word2, word1 -> word1, word1, word3, word2
//
try {
if(Type.BOOLEAN.equals(inType)) {
if(booleanType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Boolean", "<init>", "(Z)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new SWAP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP_X1()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Boolean"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
else if(Type.BYTE.equals(inType)) {
if(byteType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Byte", "valueOf", "(B)Ljava/lang/Byte;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Byte", "<init>", "(B)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new SWAP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP_X1()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Byte"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
else if(Type.CHAR.equals(inType)) {
if(characterType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Character", "valueOf", "(C)Ljava/lang/Character;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Character", "<init>", "(C)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new SWAP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP_X1()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Character"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
else if(Type.DOUBLE.equals(inType)) {
if(doubleType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Double", "valueOf", "(D)Ljava/lang/Double;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Double", "<init>", "(D)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new POP2()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP2_X2()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Double"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
else if(Type.FLOAT.equals(inType)) {
if(floatType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Float", "valueOf", "(F)Ljava/lang/Float;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Float", "<init>", "(F)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new SWAP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP_X1()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Float"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
else if(Type.INT.equals(inType)) {
if(integerType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Integer", "valueOf", "(I)Ljava/lang/Integer;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Integer", "<init>", "(I)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new SWAP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP_X1()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Integer"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
else if(Type.LONG.equals(inType)) {
if(longType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Long", "valueOf", "(J)Ljava/lang/Long;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Long", "<init>", "(J)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new POP2()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP2_X2()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Long"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
else if(Type.SHORT.equals(inType)) {
if(shortType.isAssignmentCompatibleWith(outType)) {
//instructions.insert(new INVOKESTATIC(cpGen.addMethodref("java.lang.Short", "valueOf", "(S)Ljava/lang/Short;"))); // Only for JDK > 5
instructions.insert(new INVOKESPECIAL(cpGen.addMethodref("java.lang.Short", "<init>", "(S)V"))); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new SWAP()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new DUP_X1()); // For JDK < 5 (works also with newer JDKs)
instructions.insert(new NEW(cpGen.addClass("java.lang.Short"))); // For JDK < 5 (works also with newer JDKs)
return;
}
}
throw new IllegalProceedUsageException("cannot inboxing from " + inType + " to " + outType);
} catch(ClassNotFoundException e) {throw new JVMAIRuntimeException(e.getMessage());}
}
}
private interface HotSwapProceedCompiler {
/**
* Inlines proceed routines in the redefined <CODE>target</CODE> method.
* <P>
* Note: the advice has to be prepared with <CODE>prepareAdvice()</CODE>
* before weaving.
* <P>
* @param redefinedMethod original method (after redefinition)
* @param redefinedConstantPool
* @throws JVMAIRuntimeException if the advice was not prepared before weaving.
* @throws IllegalProceedUsageException if incompatible argument or return value type
* where defined for <CODE>proceed()</CODE>.
*/
public abstract void weaveRedefinedMethod();
}
/**
* Weaves proceed statements of an advice into redefined methods.
* <P>
* Proceed invokations must be replaced with <CODE>prepareAdvice()</CODE>
* before the proceed code may be inlined into the redefined target method.
* <P>
* The 'proceed' code must be inlined with <CODE>weaveRedefinedMethod()</CODE>
* after <CODE>target</CODE> was redefined with <CODE>advice</CODE>.
* <P>
* 'Proceed' code is directly inlined into the redefinied target method
* instead of the invoke instruction for all 'proceed' invokations.
*
*/
private class HotSwapSingleProceedCompiler implements HotSwapProceedCompiler {
/// BCEL generator for the target method (that will be executed for <CODE>proceed()</CODE>)
private MethodGen target;
private ConstantPoolGen targetCP;
/// <CODE>true</CODE> after <CODE>prepareProceedBody</CODE> was invoked. (prevents multiple invokations)
private boolean isProceedBodyPrepared;
// fields that are initialized after 'prepareProceedInstruction()' was called
/// bytecode to be inlined for proceed statements
private InstructionList proceedCode;
/// exception handlers targeting the bytecode above
private CodeExceptionGen[] proceedExceptionHandlers;
/// local variable slots used for proceed
private int maxLocalsForProceed;
/**
* Constructor.
*
* @param target method that will be executed for 'proceed' invokations.
* @param targetCP constant pool of the class defining <CODE>target</CODE>.
*/
public HotSwapSingleProceedCompiler(MethodGen target, ConstantPoolGen targetCP) {
this.target = target;
this.targetCP = targetCP;
proceedCode = target.getInstructionList();
proceedExceptionHandlers = target.getExceptionHandlers();
maxLocalsForProceed = target.getMaxLocals() + 2 + target.getReturnType().getSize();
//System.out.println("SingleProceedCompiler generated (for one single proceed statement in " + advice);
}
/**
* Constructor.
*
* @param target method that will be executed for 'proceed' invokations.
*/
public HotSwapSingleProceedCompiler(MethodGen target) {
this(target, target.getConstantPool());
}
/**
* Inlines proceed routines into the redefined method.
* <P>
* Note: the advice has to be prepared with <CODE>prepareAdvice()</CODE>
* before weaving.
*
* @throws JVMAIRuntimeException if the advice was not prepared before weaving.
* @throws IllegalProceedUsageException if incompatible argument or return value type
* where defined for <CODE>proceed()</CODE>.
*/
public void weaveRedefinedMethod() {
if(!hasProceed())
return; // nothing to do
InstructionList code = target.getInstructionList();
// Shortcut for proceed invoking an 'empty' method
// (method that has only a (void) return instruction in its body)
if(proceedCode.getStart() == proceedCode.getEnd()/*proceedCode.getLength() <= 1*/) {
// remove all 'proceed()' invokations
Iterator iter = proceedInstructionHandles.iterator();
while(iter.hasNext()) {
InstructionHandle ih = ((ProceedListEntry)iter.next()).proceed;
removeInstruction(ih, code, ih.getNext());
}
// done
return;
}
// 1. get first slot available for 'proceed'
int minSlot = target.getMaxLocals();
// 2. prepare instruction list to inline it for 'proceed()' statements
prepareProceedBody(minSlot);
// 3. replace 'proceed()' invokations with inlined bytecode
for(int i = 1; i < proceedInstructionHandles.size(); i++)
weaveProceedStatment((ProceedListEntry)proceedInstructionHandles.get(i), code, minSlot, false);
weaveProceedStatment((ProceedListEntry)proceedInstructionHandles.get(0), code, minSlot, true);
// 4. set MaxLocals manually (faster)
target.setMaxLocals(minSlot + maxLocalsForProceed - 1);
}
/**
* Replaces a <CODE>proceed()</CODE> invokation by inlined <CODE>proceed()</CODE> code.
*
* @param proceedInvokation handle for the invoke statment that is to be replaced.
* @param code instruction list containing <CODE>proceedInvokation</CODE>
* @param minLocal number of local variable slots used by <CODE>proceedInvokation</CODE> (without the slots for proceed).
* @param isLastProceed <CODE>true</CODE> if this method will not be called another time.
* @throws IllegalProceedUsageException
*/
private void weaveProceedStatment(ProceedListEntry entry, InstructionList code, int minLocal, boolean isLastProceed) {
InstructionHandle proceedInvokation = entry.proceed;
InvokeInstruction instr = (InvokeInstruction)proceedInvokation.getInstruction();
// 1. insert instructions
// 1.1. get body
InstructionList proceed;
CodeExceptionGen[] pEx;
if(isLastProceed) {
// 1.1.a. only the last proceed statement that is woven is allowed
// to consume the original instruction list.
proceed = proceedCode;
pEx = proceedExceptionHandlers;
}
else {
// 1.1.b. if there are also other 'proceed()' statments to be woven,
// then the instruction must be copied, because It will be
// consumed by the redefined methods instruction list on insertion.
proceed = proceedCode.copy();
pEx = copyExceptionHandlers(proceedExceptionHandlers, proceedCode, proceed);
}
// 1.2. add epilog (argument handling) to the body
proceed.append(getProceedEpilog(instr));
// 1.3. add prolog (return value handling) to the body
proceed.insert(getProceedProlog(entry.arguments, minLocal));
// 1.4. remember the begin and the and of the inserted code
// (for curbing exception handlers)
final InstructionHandle first = proceed.getStart();
final InstructionHandle last = proceed.getEnd();
// 1.5. insert the code for proceed()
code.append(proceedInvokation, proceed);
//
// NOTE: WHAT IS CALLED EXCEPTION HANDLER BELOW IS ABOUT THE ATTRIBUTE
// AND NOT ABOUT THE INSTRUCTIONS THAT ARE HANDLING AN CATCHED EXCEPTION!!!
//
// 2. curb/adapt exception handlers
// 2.1. adapt targets exception handlers that are pointing
// to the 'proceed()' statement
if(proceedInvokation.hasTargeters()) {
// 2.1.1. Check all exception handler attributes referencing to 'proceedInvokation'
// If the proceed invokation instruction is in an exception handler clause
// All instructions of its inlined code must also get in this clause.
InstructionTargeter[] targeters = proceedInvokation.getTargeters();
for(int i = 0; i < targeters.length; i++) {
if(targeters[i] instanceof CodeExceptionGen) {
CodeExceptionGen ceg = (CodeExceptionGen)targeters[i];
// 2.1.2. adapt startPC (if required)
if(ceg.getStartPC() == proceedInvokation)
ceg.setStartPC(first);
// 2.1.3. adapt endPC (if required)
if(ceg.getEndPC() == proceedInvokation)
ceg.setEndPC(last);
// 2.1.4. adapt handlerPC (if required)
if(ceg.getHandlerPC() == proceedInvokation)
ceg.setHandlerPC(first);
}
}
}
// 2.2. curb proceeds exception handlers
for(int i = 0; i < pEx.length; i++) {
CodeExceptionGen eh = pEx[i];
target.addExceptionHandler(
eh.getStartPC(),
eh.getEndPC(),
eh.getHandlerPC(),
eh.getCatchType()
);
}
// 5. remove invoke instruction
removeInstruction(proceedInvokation, code, first);
}
/**
* Make a copy of <CODE>orig</CODE>, adapting the ranges
* for <CODE>newCode</CODE>, which must be an unmodified copy of
* <CODE>origCode</CODE>.
* <P>
* <B>NOTE: This is about the Attribute (BCEL type <CODE>CodeExceptionGen</CODE>) and
* not about the instructions that will run when an exception is catched!!!</B> (yes this
* is also called exception handler)
*
* @param orig original exception handlers
* @param origCode instructions for <CODE>orig</CODE>
* @param newCode instructions for the copy
* @return
*/
private CodeExceptionGen[] copyExceptionHandlers(CodeExceptionGen[] orig, InstructionList origCode, InstructionList newCode) {
// // alternative implementation
// CodeExceptionGen[] retVal = orig.clone();
//
// InstructionHandle newH = newCode.getStart();
// InstructionHandle oldH = origCode.getStart();
//
// while(null != newH) {
// newCode.redirectExceptionHandlers(retVal, oldH, newH);
// oldH = oldH.getNext();
// newH = newH.getNext();
// }
// 1. create an array for the exception handlers (The Attribute, not the instructions handling the exception!!!).
CodeExceptionGen[] retVal = new CodeExceptionGen[orig.length];
// 2. calculate (and set) the instructions position in booth instruction lists.
newCode.setPositions();
origCode.setPositions();
// 3. create new exception handler referencing to instructions in
// 'newCode' for each exception handler in 'orig'
for(int i = 0; i < retVal.length; i++) {
CodeExceptionGen eh = orig[i];
retVal[i] = new CodeExceptionGen(
newCode.findHandle(eh.getStartPC().getPosition()),
newCode.findHandle(eh.getEndPC().getPosition()),
newCode.findHandle(eh.getHandlerPC().getPosition()),
eh.getCatchType()
);
}
return retVal;
}
/**
* Generates code to handle the arguments for <CODE>proceed()</CODE>.
* The returned instructions should be woven in front of the inlined proceed body
* into the redefined methods bytecode.
*
* @param proceedArgTypes argument types defined for proceed.
* @param minSlot first available slot number for proceed
* @return instruction list holding the prolog (argument handling) instructions
* @throws IllegalProceedUsageException <CODE>proceed()</CODE> was defined with incompatible
* arguments.
*/
private InstructionList getProceedProlog(Type[] proceedArgTypes, int slotIncr) {
// 1. initialize local variables
InstructionList retVal = new InstructionList();
// the argument types that are passed to the real method
// (aka the redefined method)
final Type[] realArgTypes = target.getArgumentTypes();
// the first slot used for explicitely defined method arguments
// (used by the redefined method, 'proceed()' uses 'origSlot' + 'slotIncr')
int origSlot = (target.isStatic()) ? 0 : 1;
// holds the number of arguments that where explicitely passed to 'proceed()'
int maxCustomArg = proceedArgTypes.length;
// 2. handle rest like a undefined argument
if(maxCustomArg > 0 && rest.equals(proceedArgTypes[maxCustomArg - 1]))
maxCustomArg--;
// 3. proceed may not define more arguments than the real method
if(realArgTypes.length < maxCustomArg)
throw new IllegalProceedUsageException("more arguments for proceed (" + maxCustomArg + ") in " + advice + " than for the redefined method (" + realArgTypes.length + "): " + target);
// 4. write instructions for explicitly passed (user defined) arguments
int argCount = 0; // number of processed arguments
while(argCount < maxCustomArg) {
// 4.a. argument of type ANY
if(any.equals(proceedArgTypes[argCount]))
copyLocalVariable(origSlot, origSlot + slotIncr, realArgTypes[argCount], retVal);
// 4.b. argument of type REST
else if(rest.equals(proceedArgTypes[argCount]))
throw new IllegalProceedUsageException("'REST' is only allowed as last argument for 'proceed()'");
// 4.c. other arguments
else {
// 4.c.1. move the argument to its new place in the local variable table
retVal.insert(InstructionFactory.createStore(realArgTypes[argCount], origSlot + slotIncr));
// 4.c.2. do in- or outboxing if required (will be inserted infront of the instruction inserted above)
insertAutoBoxingInstructions(proceedArgTypes[argCount], realArgTypes[argCount], retVal, targetCP);
}
origSlot += realArgTypes[argCount].getSize();
argCount++;
}
// 5. write instructions for implicite arguments
while(argCount < realArgTypes.length) {
copyLocalVariable(origSlot, origSlot + slotIncr, realArgTypes[argCount], retVal);
origSlot += realArgTypes[argCount].getSize();
argCount++;
}
return retVal;
}
/**
* Generates code to handle the return value for <CODE>proceed()</CODE>.
* The returned instructions should be woven after the inlined proceed body
* into the redefined methods bytecode.
* <P>
* The generated code will pop the return value from the stack in case
* of <CODE>proceed()</CODE> statements without return value are replaced
* by methods that has return values.
*
* @param invokeProceed
* @return list holding the instructions for return value handling
* (the list is either empty or it holds one instruction that
* pops the return value from the stack).
* @throws IllegalProceedUsageException if the real return value is not
* compatible to the type defined for proceed.
*/
private InstructionList getProceedEpilog(InvokeInstruction invokeProceed) {
InstructionList retVal = new InstructionList();
final Type proceedReturnType = invokeProceed.getReturnType(targetCP);
final Type realReturnType = target.getReturnType();
if(Type.VOID.equals(proceedReturnType) && !(Type.VOID.equals(realReturnType))) {
// a. If the advice did not expect a return value from proceed.
// than pop the return value from the stack ()
retVal.append(InstructionFactory.createPop(realReturnType.getSize()));
}
else
// b. Check if the real return value is compatible to that one
// of 'proceed()'
insertAutoBoxingInstructions(realReturnType, proceedReturnType, retVal, targetCP);
// c. else (proceed push the return value on the stack that the advice
// expects there) NOTHING TO DO.
return retVal;
}
/**
* Adapts the method bytecode in <CODE>proceedCode</CODE> to inline it
* into redefined <CODE>target</CODE>
*
* @param minSlot first available slot for 'proceed()'
*/
private void prepareProceedBody(final int minSlot) {
// 0. do this only once
if(!isProceedBodyPrepared) {
// 1. append a NOP (Null Operation) to mark the end of 'proceed()'
InstructionHandle endOfProceed = proceedCode.append(new NOP());
// 2. adapt method bytecode for 'proceed()'
InstructionHandle ih = proceedCode.getStart();
while(null != ih) {
// 2.1. get instruction
Instruction inst = ih.getInstruction();
// 2.2. adapt local variable accesses
if (inst instanceof LocalVariableInstruction) {
LocalVariableInstruction lv_instr = (LocalVariableInstruction) inst;
int oSlot = lv_instr.getIndex();
if(0 < oSlot) { // dont change references to 'this'
lv_instr.setIndex(oSlot + minSlot);
}
}
// 2.3. replace RETURN (return from method) by GOTO instructions
else if (inst instanceof ReturnInstruction) {
if(ih.getNext() == endOfProceed) {
// it's faster to remove the instruction than to replace
// it with a jump to its successor.
removeInstruction(ih, proceedCode, endOfProceed);
break;
}
ih = replaceInstruction(new GOTO(endOfProceed), ih, proceedCode);
}
// 2.4. go to next instruction
ih = ih.getNext();
}
isProceedBodyPrepared = true;
}
}
}
/**
* Weaves proceed statements of an advice into redefined methods.
* <P>
* Proceed invokations must be replaced with <CODE>prepareAdvice()</CODE>
* before the proceed code may be inlined into the redefined target method.
* <P>
* The 'proceed' code must be inlined with <CODE>weaveRedefinedMethod()</CODE>
* after <CODE>target</CODE> was redefined with <CODE>advice</CODE>.
* <P>
* 'Proceed' code is appended to the redefinied target method and all 'proceed' invokations
* are replaced by GOTO instructions to that code.
*
*/
private class HotSwapMultiProceedCompiler implements HotSwapProceedCompiler {
/// BCEL generator for the target method (that will be inlined for <CODE>proceed()</CODE>)
private MethodGen target;
private ConstantPoolGen targetCP;
// fields that are initialized after 'prepareProceedInstruction()' was called
/// bytecode to be inlined for proceed statements
private InstructionList proceedCode;
/// exception handlers targeting the bytecode above
private CodeExceptionGen[] proceedExceptionHandlers;
/// local variable slots used for proceed
private int maxLocalsForProceed;
/**
* Constructor.
*
* @param target method to be redefined by <CODE>advice</CODE>.
* @param targetCP
*/
public HotSwapMultiProceedCompiler(MethodGen target, ConstantPoolGen targetCP) {
this.target = target;
this.targetCP = targetCP;
proceedCode = target.getInstructionList();
proceedExceptionHandlers = target.getExceptionHandlers();
maxLocalsForProceed = target.getMaxLocals() + 2 + target.getReturnType().getSize();
//System.out.println("MultiProceedCompiler generated (for proceed statements in " + advice);
}
/**
* Constructor.
*
* @param target method to be redefined by <CODE>advice</CODE>.
*/
public HotSwapMultiProceedCompiler(MethodGen target) {
this(target, target.getConstantPool());
}
/* (non-Javadoc)
* @see ch.ethz.inf.iks.jvmai.jvmdi.HotSwapProceedCompiler#weaveRedefinedMethod()
*/
public void weaveRedefinedMethod() {
// if(!isAdvicePrepared)
// throw new JVMAIRuntimeException("'replaceProceedInvokations()' must be called before 'weaveProceedSubroutine'");
if(!hasProceed())
return; // nothing to do
if(null == target)
throw new JVMAIRuntimeException("ProceedWeaver.weaveRedefinedMethod(): target must be set before!!!");
InstructionList code = target.getInstructionList();
// Shortcut for proceed invoking an 'empty' method
// (method that has only a (void) return instruction in its body)
if(proceedCode.getStart() == proceedCode.getEnd()/*proceedCode.getLength() <= 1*/) {
// remove all 'proceed()' invokations
Iterator iter = proceedInstructionHandles.iterator();
while(iter.hasNext()) {
InstructionHandle ih = ((ProceedListEntry)iter.next()).proceed;
removeInstruction(ih, code, ih.getNext());
}
// done
return;
}
// 1. get first slot available for 'proceed'
int minSlot = target.getMaxLocals();
// 2. Append first instruction of the proceed code
// (which stores the identifier for the caller location to a slot)
InstructionHandle proceedStart;
if(proceedInstructionHandles.size() > 1)
proceedStart = code.append(new ISTORE(minSlot));
else
proceedStart = code.append(new NOP());
// 3. replace 'proceed()' method invokations with jumps to the appended subroutine
for(int i = 0; i < proceedInstructionHandles.size(); i++)
weaveProceedStatment((ProceedListEntry)proceedInstructionHandles.get(i), code, minSlot, proceedStart, i);
// 4. append full proceed code
appendProceedBody(code, minSlot);
// 5. set MaxLocals manually (faster)
// target.setMaxLocals(minSlot + maxLocalsForProceed - 1);
target.setMaxLocals();
}
/**
* Replaces a <CODE>proceed()</CODE> invokation code by a GOTO to appended 'proceed' code
* and adds parameter and return value handling code.
*
* @param proceedInvokation handle for the invoke statment that is to be replaced.
* @param code instruction list containing <CODE>proceedInvokation</CODE>
* @param minLocal number of local variable slots used by <CODE>proceedInvokation</CODE> (without the slots for proceed).
* @param proceedStart first instruction of the proceed subroutine.
* @throws IllegalProceedUsageException
*/
private void weaveProceedStatment(ProceedListEntry entry, InstructionList code, int minLocal, InstructionHandle proceedStart, int idx) {
InstructionHandle proceedInvokation = entry.proceed;
InvokeInstruction instr = (InvokeInstruction)proceedInvokation.getInstruction();
// 1. create the instructions that will replace 'proceedInvokation'
InstructionList replacementCode = new InstructionList();
// 1.1. push the identifier (index) of this location to the stack
if(proceedInstructionHandles.size() > 1)
// replacementCode.append(new ICONST(idx)); //BEFORE: ICONST can be used only for constants between "0" and "5". Therefore, if are more then 5 'proceed', 'ICONST' must be changed with 'BIPUSH' or 'SIPUSH'
replacementCode.append(new BIPUSH((byte)idx)); //ANGY-bugfix: for more than 5 'proceed', 'BIPUSH' => ((byte)idx) or 'SIPUSH' => ((short)idx) has to be used
// 1.2. add GOTO to the inlined proceed code
InstructionHandle jumpToProceed = replacementCode.append(new GOTO(proceedStart));
// 1.3. instruction for rethrowing exceptions in the bounderies of the
// exception handlers, which are protecting the proceed invokation.
entry.executeAfterException = replacementCode.append(new ATHROW());
// 1.4. add code for handling the return value (epilog).
replacementCode.append(getProceedEpilog(instr));
// 1.5. add code for method parameter handling (prolog)
replacementCode.insert(getProceedProlog(entry.arguments, minLocal));
// 2. replace proceed invokation
InstructionHandle replacementEnd = replacementCode.getEnd();
InstructionHandle replacementStart = replacementCode.getStart();
// 2.1. append the generated code after 'proceedInvokation'
code.append(proceedInvokation, replacementCode);
// 2.2. adapt exception handler attributes referencing 'proceedInvokation'.
extendExceptionHandlerRanges(proceedInvokation, replacementEnd, replacementStart);
// 2.3. remove 'proceedInvokation'
removeInstruction(proceedInvokation, code, jumpToProceed);
entry.executeAfterProceed = entry.executeAfterException.getNext();
}
/**
* Make a copy of <CODE>orig</CODE>, adapting the ranges
* for <CODE>newCode</CODE>, which must be an unmodified copy of
* <CODE>origCode</CODE>.
* <P>
* <B>NOTE: This is about the Attribute (BCEL type <CODE>CodeExceptionGen</CODE>) and
* not about the instructions that will run when an exception is catched!!!</B> (yes this
* is also called exception handler)
*
* @param orig original exception handlers
* @param origCode instructions for <CODE>orig</CODE>
* @param newCode instructions for the copy
* @return
*/
private CodeExceptionGen[] copyExceptionHandlers(CodeExceptionGen[] orig, InstructionList origCode, InstructionList newCode) {
// // alternative implementation
// CodeExceptionGen[] retVal = orig.clone();
//
// InstructionHandle newH = newCode.getStart();
// InstructionHandle oldH = origCode.getStart();
//
// while(null != newH) {
// newCode.redirectExceptionHandlers(retVal, oldH, newH);
// oldH = oldH.getNext();
// newH = newH.getNext();
// }
// 1. create an array for the exception handlers (The Attribute, not the instructions handling the exception!!!).
CodeExceptionGen[] retVal = new CodeExceptionGen[orig.length];
// 2. calculate (and set) the instructions position in booth instruction lists.
newCode.setPositions();
origCode.setPositions();
// 3. create new exception handler referencing to instructions in
// 'newCode' for each exception handler in 'orig'
for(int i = 0; i < retVal.length; i++) {
CodeExceptionGen eh = orig[i];
retVal[i] = new CodeExceptionGen(
newCode.findHandle(eh.getStartPC().getPosition()),
newCode.findHandle(eh.getEndPC().getPosition()),
newCode.findHandle(eh.getHandlerPC().getPosition()),
eh.getCatchType()
);
}
return retVal;
}
/**
* Change all exception handler references pointing to <CODE>oldTarget</CODE>.
*
* @param oldTarget target instruction
* @param newEnd
* @param newStart
*/
private void extendExceptionHandlerRanges(InstructionHandle oldTarget, InstructionHandle newEnd, InstructionHandle newStart) {
if(oldTarget.hasTargeters()) {
// 2.1.1. Check all exception handler attributes referencing to 'proceedInvokation'
// If the proceed invokation instruction is in an exception handler clause
// All instructions of its inlined code must also get in this clause.
InstructionTargeter[] targeters = oldTarget.getTargeters();
for(int i = 0; i < targeters.length; i++) {
if(targeters[i] instanceof CodeExceptionGen) {
CodeExceptionGen ceg = (CodeExceptionGen)targeters[i];
// 2.1.2. adapt startPC (if required)
if(ceg.getStartPC() == oldTarget)
ceg.setStartPC(newStart);
// 2.1.3. adapt endPC (if required)
if(ceg.getEndPC() == oldTarget)
ceg.setEndPC(newEnd);
// 2.1.4. adapt handlerPC (if required)
if(ceg.getHandlerPC() == oldTarget)
ceg.setHandlerPC(newStart);
}
}
}
}
/**
* Generates code to handle the arguments for <CODE>proceed()</CODE>.
* The returned instructions should be woven in front of the inlined proceed body
* into the redefined methods bytecode.
*
* @param proceedArgTypes argument types defined for proceed.
* @param minSlot first available slot number for proceed
* @return instruction list holding the prolog (argument handling) instructions
* @throws IllegalProceedUsageException <CODE>proceed()</CODE> was defined with incompatible
* arguments.
*/
private InstructionList getProceedProlog(Type[] proceedArgTypes, int slotIncr) {
// 1. initialize local variables
InstructionList retVal = new InstructionList();
// the argument types that are passed to the real method
// (aka the redefined method)
final Type[] realArgTypes = target.getArgumentTypes();
// the first slot used for explicitely defined method arguments
// (used by the redefined method, 'proceed()' uses 'origSlot' + 'slotIncr')
int origSlot = (target.isStatic()) ? 0 : 1;
// holds the number of arguments that where explicitely passed to 'proceed()'
int maxCustomArg = proceedArgTypes.length;
// 2. handle rest like a undefined argument
if(maxCustomArg > 0 && rest.equals(proceedArgTypes[maxCustomArg - 1]))
maxCustomArg--;
// 3. proceed may not define more arguments than the real method
if(realArgTypes.length < maxCustomArg)
throw new IllegalProceedUsageException("more arguments for proceed (" + maxCustomArg + ") in " + advice + " than for the redefined method (" + realArgTypes.length + "): " + target);
// 4. write instructions for explicitly passed (user defined) arguments
int argCount = 0; // number of processed arguments
while(argCount < maxCustomArg) {
// 4.a. argument of type ANY
if(any.equals(proceedArgTypes[argCount]))
copyLocalVariable(origSlot, origSlot + slotIncr, realArgTypes[argCount], retVal);
// 4.b. argument of type REST
else if(rest.equals(proceedArgTypes[argCount]))
throw new IllegalProceedUsageException("'REST' is only allowed as last argument for 'proceed()'");
// 4.c. other arguments
else {
// 4.c.1. move the argument to its new place in the local variable table
retVal.insert(InstructionFactory.createStore(realArgTypes[argCount], origSlot + slotIncr));
// 4.c.2. do in- or outboxing if required (will be inserted infront of the instruction inserted above)
insertAutoBoxingInstructions(proceedArgTypes[argCount], realArgTypes[argCount], retVal, targetCP);
}
origSlot += realArgTypes[argCount].getSize();
argCount++;
}
// 5. write instructions for implicite arguments
while(argCount < realArgTypes.length) {
copyLocalVariable(origSlot, origSlot + slotIncr, realArgTypes[argCount], retVal);
origSlot += realArgTypes[argCount].getSize();
argCount++;
}
return retVal;
}
/**
* Generates code to handle the return value for <CODE>proceed()</CODE>.
* The returned instructions should be woven after the inlined proceed body
* into the redefined methods bytecode.
* <P>
* The generated code will pop the return value from the stack in case
* of <CODE>proceed()</CODE> statements without return value are replaced
* by methods that has return values.
*
* @param invokeProceed
* @return list holding the instructions for return value handling
* (the list is either empty or it holds one instruction that
* pops the return value from the stack or autoboxing code).
* @throws IllegalProceedUsageException if the real return value is not
* compatible to the type defined for proceed.
*/
private InstructionList getProceedEpilog(InvokeInstruction invokeProceed) {
InstructionList retVal = new InstructionList();
final Type proceedReturnType = invokeProceed.getReturnType(targetCP);
if(!Type.VOID.equals(proceedReturnType))
// insert autoboxing code if required
insertAutoBoxingInstructions(target.getReturnType(), proceedReturnType, retVal, targetCP);
else
if(!Type.VOID.equals(target.getReturnType()))
// pop not used return value from stack
retVal.append(InstructionFactory.createPop(target.getReturnType().getSize()));
return retVal;
}
/**
* Append TABLESWITCH code for returning from inlined proceed code.
* <P>
* @param il instruction list where the generated code will be appended.
* @param idxSlot slot of the local variable where the identifier (index)
* of the return location is stored.
* @return begin of the appended code
*/
private InstructionHandle appendTableSwitches(InstructionList il, int idxSlot) {
final int size = proceedInstructionHandles.size();
if(1 < size) {
// 1. Normal return from proceed
// 1.1. push the indentifier for the caller location to the stack
InstructionHandle retVal = il.append(new ILOAD(idxSlot));
// 1.2. switch statement for normal return from proceed
int[] idxList = new int[size];
InstructionHandle[] targetList = new InstructionHandle[size];
for(int i = 0; i < size; i++) {
idxList[i] = i;
targetList[i] = ((ProceedListEntry)proceedInstructionHandles.get(i)).executeAfterProceed;
}
il.append(new TABLESWITCH(idxList, targetList, targetList[0]));
// 2. Exception handler code
// 2.1. push the indentifier for the caller location to the stack
il.append(new ILOAD(idxSlot));
// 2.2. switch statement for normal return from proceed
InstructionHandle[] eTargetList = new InstructionHandle[size];
for(int i = 0; i < size; i++) {
idxList[i] = i;
eTargetList[i] = ((ProceedListEntry)proceedInstructionHandles.get(i)).executeAfterException;
}
il.append(new TABLESWITCH(idxList, eTargetList, eTargetList[0]));
return retVal;
}
else {
InstructionHandle retVal = il.append(new GOTO(((ProceedListEntry)proceedInstructionHandles.firstElement()).executeAfterProceed));
il.append(new GOTO(((ProceedListEntry)proceedInstructionHandles.firstElement()).executeAfterException));
return retVal;
}
}
/**
* Adapts the method bytecode in <CODE>proceedCode</CODE> in order to be
* appended to redefined <CODE>target</CODE> and appends it.
* <P>
* Following adaptations are done:
* <UL>
* <LI>Adapt local variable slot numbers. The numbers must be increased in order to
* prevent conflicts with local variables of the surrounding method.</LI>
* <LI>Replaces <CODE>RETURN</CODE> instructions with <CODE>GOTO</CODE> instructions.</LI>
* <LI>Appends general exception handler code.</LI>
* </UL>
* <P>
* @param il instruction list where the proceed body will be appended.
* @param minSlot first available slot for 'proceed()'
*/
private void appendProceedBody(InstructionList il, int minSlot) {
// do this only if the code was not already appended
if(!proceedCode.isEmpty()) {
// Adapt method bytecode for 'proceed()'
// first available slot is reserved for the indentifier (index)
// of the caller location.
InstructionHandle ih = proceedCode.getStart();
// 1. append code for returning from the inlined method.
// 'tswitch' is the start of the regular return routine,
// if the code throws an exception, it must be handled by
// 'tswitch.getNext().getNext()'.
InstructionHandle tswitch = appendTableSwitches(proceedCode, minSlot);
// 2. adapt existing instructions
while(tswitch != ih) {
// 2.1. get instruction
Instruction inst = ih.getInstruction();
if (inst instanceof LocalVariableInstruction) {
// 2.2. adapt local variable accesses (increases slot number)
LocalVariableInstruction lv_instr = (LocalVariableInstruction) inst;
final int oSlot = lv_instr.getIndex();
if(0 < oSlot) { // dont change references to 'this'
lv_instr.setIndex(oSlot + minSlot);
}
}
else if (inst instanceof ReturnInstruction) {
// 2.3. replace RETURN (return from method) by GOTO instructions
if(ih.getNext() == tswitch) {
removeInstruction(ih, proceedCode, tswitch);
break;
}
ih = replaceInstruction(new GOTO(tswitch), ih, proceedCode);
}
// 2.4. go to next instruction
ih = ih.getNext();
}
// 3. set exception handler attributes for 'target'
// 3.1. copy original exception handlers from 'target' to the redefined 'target'.
CodeExceptionGen[] pEx = proceedExceptionHandlers;
for(int i = 0; i < pEx.length; i++) {
CodeExceptionGen eh = pEx[i];
target.addExceptionHandler(
eh.getStartPC(),
eh.getEndPC(),
eh.getHandlerPC(),
eh.getCatchType()
);
}
// 3.2. set general exception handler (protecting the inlined proceed code,
// must be added after the individual handlers, so that they are matched
// first)
if(proceedInstructionHandles.size() > 1)
target.addExceptionHandler(proceedCode.getStart(), tswitch, tswitch.getNext().getNext(), null);
else
target.addExceptionHandler(proceedCode.getStart(), tswitch, tswitch.getNext(), null);
// 4. append the code
il.append(proceedCode);
}
else
// A valid java method has at least a RETURN instruction.
throw new JVMAIRuntimeException("attempt to create the same proceed subroutine a second time");
}
}
/**
* Sets the target method that will be inlined for 'proceed'.
* <P>
* <B>Note:</B> this method must be called before the target method
* is redefined by <CODE>advice</CODE>, but redefinitions for advices with
* lower precedence must already be done.
* <P>
* @param target class generator for the method that will be executed for
* 'proceed' invokations.
* @param targetCP constant pool generator for <CODE>target</CODE>.
* @see ch.ethz.inf.iks.jvmai.jvmdi.HotSwapProceedCompiler#setTarget(org.apache.bcel.generic.MethodGen, org.apache.bcel.generic.ConstantPoolGen)
*/
public void setTarget(MethodGen target, ConstantPoolGen targetCP) {
if(1 < proceedInstructionHandles.size())
proceedCompiler = new HotSwapMultiProceedCompiler(target, targetCP);
else
proceedCompiler = new HotSwapSingleProceedCompiler(target, targetCP);
}
/**
* Inlines proceed routines into the redefined method.
* <P>
* <B>Note:</B> the advice has to be prepared with <CODE>prepareAdvice()</CODE>
* before weaving. And the target method must be redefined with the advice.
* <P>
* After this weaving procees the target method is ready for class redefinition.
* <P>
* @throws JVMAIRuntimeException if the advice was not prepared before weaving.
* @throws IllegalProceedUsageException if incompatible argument or return value type
* where defined for <CODE>proceed()</CODE>.
*/
public void weaveRedefinedMethod() {
if(null == proceedCompiler)
throw new JVMAIRuntimeException("setTarget() must be called first!");
proceedCompiler.weaveRedefinedMethod();
}
}