package alt.jiapi.interceptor;
import java.util.List;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import alt.jiapi.InstrumentationException;
import alt.jiapi.event.EventInstrumentor;
import alt.jiapi.event.EventRuntime;
import alt.jiapi.reflect.Instruction;
import alt.jiapi.reflect.InstructionFactory;
import alt.jiapi.reflect.InstructionList;
import alt.jiapi.reflect.JiapiClass;
import alt.jiapi.reflect.JiapiField;
import alt.jiapi.reflect.JiapiMethod;
import alt.jiapi.reflect.Loader;
import alt.jiapi.reflect.Signature;
import alt.jiapi.reflect.SignatureUtil;
import alt.jiapi.reflect.FieldExistsException;
import alt.jiapi.reflect.MethodExistsException;
import alt.jiapi.reflect.instruction.Invocation;
import alt.jiapi.reflect.instruction.OpcodeGroups;
import alt.jiapi.reflect.instruction.Opcodes;
import alt.jiapi.instrumentor.HotSpot;
import org.apache.log4j.Category;
/**
* Class InvocationInstrumentor.
*
* @author Mika Riekkinen
*/
class InvocationInstrumentor extends EventInstrumentor {
private static Category log = Category.getInstance(InvocationInstrumentor.class);
private InvocationHandler handler;
InvocationInstrumentor(InvocationInterceptor ii, InvocationHandler handler) {
super(ii);
this.handler = handler;
}
public void instrument(JiapiMethod jm) {
InstructionList il = jm.getInstructionList();
if ("<clinit>".equals(jm.getName())) {
return;
}
InstructionFactory factory = il.getInstructionFactory();
JiapiClass ii = getEventProducer();
JiapiMethod invokeMethod = null;
try {
invokeMethod =
ii.getDeclaredMethod("invokeMethod",
new String[] { "java.lang.Object",
"java.lang.String",
"java.lang.Object[]",
"java.lang.String"});
}
catch(Exception e) {
e.printStackTrace();
}
int idx = -1;
while ((idx = il.indexOf(OpcodeGroups.INVOKE_INSTRUCTIONS, idx+1)) != -1) {
Invocation ins = (Invocation)il.get(idx);
if (!match(ins.getClassName() + "." + ins.getMethodName())) {
continue;
}
// if (System.getProperty("no-lfix") == null) {
// // --- bug workaround for long/doubles in method params
// boolean bailOutForLongDoubleSyndrome = false;
// String[] i_params = ins.getParameterTypes();
// for (int i = 0; i < i_params.length; i++) {
// if ("double".equals(i_params[i]) ||
// "long".equals(i_params[i])) {
// bailOutForLongDoubleSyndrome = true;
// break;
// }
// }
// if (bailOutForLongDoubleSyndrome) {
// log.warn("Will not instrument invocations to methods with long or double as parameter: In " + jm.getDeclaringClass().getName() + "#" + jm +
// ", a call to " + ins + ". This is a workaround for bug in jiapi.");
// continue;
// }
// // --- bug workaround for long/doubles in method params
// }
// We support only these methods at the moment.
if (ins.getOpcode() != Opcodes.INVOKESTATIC &&
ins.getOpcode() != Opcodes.INVOKEVIRTUAL) {
continue;
}
JiapiField interceptor = getEventProducerField();
// InstructionList nList = il.createEmptyList();
InstructionList nList = new InstructionList();
// each entry in pList holds creation of one argument
// to method invocation
// InstructionList[] pLists = createArgumentLists(il, idx);
ArgumentList al = createArgumentLists(il, idx);
InstructionList[] pLists = al.arguments;
int paramIdx = al.paramIndex;
short opCode = ins.getOpcode();
if (opCode == Opcodes.INVOKEVIRTUAL ||
opCode == Opcodes.INVOKESPECIAL) {
paramIdx--; // Include object ref
}
// Generate code, that replaces invocation with a new
// invocation to InvokeHandler
switch(opCode) {
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKESTATIC:
nList.add(factory.getField(interceptor)); // Interceptor
if (opCode == Opcodes.INVOKESTATIC) {
// addClassForNameInstructions(jm.getDeclaringClass().getName(), nList);
addClassForNameInstructions(ins.getClassName(), nList);
}
else {
nList.add(il.get(paramIdx)); // objref
}
String mName = ins.getMethodName();
//nList.add(factory.getField(rField));
nList.add(factory.pushConstant(mName));
nList.add(factory.newArray("java.lang.Object",
ins.getParameterTypes().length));
String[] i_params = ins.getParameterTypes();
// Populate Object array with call parameters
for (int i = 0; i < pLists.length; i++) {
if ("long".equals(i_params[i])) {
nList.add(factory.dup());
}
else if ("double".equals(i_params[i])) {
nList.add(factory.dup());
}
else {
nList.add(factory.dup());
}
nList.add(factory.pushConstant(i));
nList.add(pLists[i]);
nList.add(factory.aastore());
}
// Add Methods signature
// nList.add(factory.pushConstant(ins.getDescriptor()));
// Add cache-key
nList.add(factory.pushConstant(ins.getClassName() + ins.getMethodName() + ins.getDescriptor()));
// call Interceptor
nList.add(factory.invoke(invokeMethod));
handleReturnValue(nList, ins);
// Replace invocation and its parameters with new
// instruction list
il.replace(paramIdx, idx + 1, nList);
break;
}
// Next index. Skip Instructions created above.
//idx += nList.size() - (idx - paramIdx) - 1;
idx = paramIdx + nList.size() - 1;
}
}
/**
* Create an array of InstructionLists. Each element in array
* represents one argument of a method invocation.
*/
private ArgumentList createArgumentLists(InstructionList il, int invocationIndex) {
Invocation ins = (Invocation)il.get(invocationIndex);
// System.out.println("Creating arg list for " + ins);
String[] paramTypes = ins.getParameterTypes();
InstructionList[] argList =
new InstructionList[paramTypes.length];
// First pIdx points to Instruction just before Invocation
int pIdx = invocationIndex - 1;
// for (int i = 0; i < argList.length; i++) {
for (int i = argList.length - 1; i >= 0; i--) {
int stackUsage = ins.stackConsumption();
InstructionList pList = il.createEmptyList();
boolean primitive = SignatureUtil.isPrimitive(paramTypes[i]);
Instruction pr_ins = null;
if (primitive) {
pr_ins = handlePrimitiveType(paramTypes[i], pList);
}
int insertIdx = pList.size();
// When stack usage is 1, we have reached an argument in stack
// while (stackUsage != 1) {
while ((stackUsage > 0 && ins.getOpcode() == Opcodes.INVOKESTATIC)||
(stackUsage > 1 && ins.getOpcode() == Opcodes.INVOKEVIRTUAL)) {
Instruction pIns = il.get(pIdx);
// System.out.println(">> pIdx: " + pIdx + ": " + pIns +
// ", stackusage: " + stackUsage);
stackUsage -= pIns.stackUsage();
// Insert pIns allways to same index. We are scanning
// Instructions backwards!
// System.out.println("Adding " + pIns + " to plist");
pList.insert(insertIdx, pIns);
pIdx--;
}
if (primitive) {
pList.add(pr_ins); // Call constructor of primitive wrapper
}
argList[i] = pList;
}
// pIdx + 1 points to first instruction, that counts
// as an argument to method call.
return new ArgumentList(argList, pIdx+1);
}
/**
* @return an Invocation to constructor of primitive wrapper
*/
private Instruction handlePrimitiveType(String type, InstructionList il) {
InstructionFactory f = il.getInstructionFactory();
String cName = null;
Signature s = new Signature("void", new String[]{ type });
if ("int".equals(type)) {
cName = "java.lang.Integer";
}
else if ("long".equals(type)) {
cName = "java.lang.Long";
}
else if ("char".equals(type)) {
cName = "java.lang.Character";
}
else if ("boolean".equals(type)) {
cName = "java.lang.Boolean";
}
else if ("byte".equals(type)) {
cName = "java.lang.Byte";
}
else if ("float".equals(type)) {
cName = "java.lang.Float";
}
else if ("double".equals(type)) {
cName = "java.lang.Double";
}
il.add(f.newClass(cName));
il.add(f.dup());
Instruction ins = f.invoke(Modifier.PUBLIC, cName, "<init>", s);
return ins;
}
private void handleReturnValue(InstructionList il, Invocation ins) {
// Convert return value if needed.
InstructionFactory factory = il.getInstructionFactory();
String rType = ins.getReturnType();
if ("int".equals(rType)) {
try {
JiapiClass jc = new Loader().loadClass("java.lang.Integer");
JiapiMethod jm =
jc.getDeclaredMethod("intValue", new String[0]);
il.add(factory.cast("java.lang.Integer"));
il.add(factory.invoke(jm));
}
catch(Exception e) {
e.printStackTrace();
}
}
else if ("long".equals(rType)) {
try {
JiapiClass jc = new Loader().loadClass("java.lang.Long");
JiapiMethod jm =
jc.getDeclaredMethod("longValue", new String[0]);
il.add(factory.cast("java.lang.Long"));
il.add(factory.invoke(jm));
}
catch(Exception e) {
e.printStackTrace();
}
}
else if ("char".equals(rType)) {
try {
JiapiClass jc = new Loader().loadClass("java.lang.Character");
JiapiMethod jm =
jc.getDeclaredMethod("charValue", new String[0]);
il.add(factory.cast("java.lang.Character"));
il.add(factory.invoke(jm));
}
catch(Exception e) {
e.printStackTrace();
}
}
else if ("boolean".equals(rType)) {
try {
JiapiClass jc = new Loader().loadClass("java.lang.Boolean");
JiapiMethod jm =
jc.getDeclaredMethod("booleanValue", new String[0]);
il.add(factory.cast("java.lang.Boolean"));
il.add(factory.invoke(jm));
}
catch(Exception e) {
e.printStackTrace();
}
}
else if ("byte".equals(rType)) {
try {
JiapiClass jc = new Loader().loadClass("java.lang.Byte");
JiapiMethod jm =
jc.getDeclaredMethod("byteValue", new String[0]);
il.add(factory.cast("java.lang.Byte"));
il.add(factory.invoke(jm));
}
catch(Exception e) {
e.printStackTrace();
}
}
else if ("float".equals(rType)) {
try {
JiapiClass jc = new Loader().loadClass("java.lang.Float");
JiapiMethod jm =
jc.getDeclaredMethod("floatValue", new String[0]);
il.add(factory.cast("java.lang.Float"));
il.add(factory.invoke(jm));
}
catch(Exception e) {
e.printStackTrace();
}
}
else if ("double".equals(rType)) {
try {
JiapiClass jc = new Loader().loadClass("java.lang.Double");
JiapiMethod jm =
jc.getDeclaredMethod("doubleValue", new String[0]);
il.add(factory.cast("java.lang.Double"));
il.add(factory.invoke(jm));
}
catch(Exception e) {
e.printStackTrace();
}
}
else if ("void".equals(rType)){
// Pop out the return value(probably null) of
// the invocation handler if it was a 'void' method
il.add(new Instruction(new byte[]{Opcodes.POP}));
}
else { // Cast to correct Object
il.add(factory.cast(ins.getReturnType()));
}
}
private void addClassForNameInstructions(String name, InstructionList il) {
InstructionFactory f = il.getInstructionFactory();
InstructionList nl = il.createEmptyList();
// NOTE: We do not create exception handlers for
// Class.forName(...) invocation.
// However, we use this to get Class of the running object,
// so its Class is allways found.
try {
nl.add(f.pushConstant(name));
nl.add(f.invoke(Modifier.STATIC, "java.lang.Class",
"forName", new Signature("java.lang.Class",
new String[] {"java.lang.String"})));
}
catch(Exception e) {
e.printStackTrace();
il.add(f.pushNull());
}
il.add(nl);
}
private class ArgumentList {
public InstructionList[] arguments;
public int paramIndex;
public ArgumentList(InstructionList[] args, int paramIndex) {
this.arguments = args;
this.paramIndex = paramIndex;
}
}
}