package rocks.inspectit.agent.java.instrumentation.asm;
import info.novatec.inspectit.org.objectweb.asm.Label;
import info.novatec.inspectit.org.objectweb.asm.MethodVisitor;
import info.novatec.inspectit.org.objectweb.asm.Opcodes;
import info.novatec.inspectit.org.objectweb.asm.Type;
import org.apache.commons.lang.ArrayUtils;
import rocks.inspectit.agent.java.hooking.IHookDispatcher;
import rocks.inspectit.shared.all.instrumentation.config.impl.SubstitutionDescriptor;
/**
* Instrumenter for our special sensor dispatching. Currently able to replace the return value of
* the method by the result received from the dispatch method.
*
* @author Ivan Senic
*
*/
public class SpecialMethodInstrumenter extends AbstractMethodInstrumenter {
/**
* {@link SubstitutionDescriptor} that defines what to be substituted after calling the special
* sensor.
*/
private SubstitutionDescriptor substitutionDescriptor;
/**
* Return type.
*/
private Type returnType;
/**
* Argument types.
*/
private Type[] argumentTypes;
/**
* Local of the passed arguments to the beforeBody method.
*/
private int passedArgumentsLocal;
/**
* Default constructor. Defines method id that will be used during instrumentation.
*
* @param mv
* Super method visitor.
* @param access
* Method access code.
* @param name
* Method name.
* @param desc
* Method description.
* @param methodId
* Method id that will be passed to {@link IHookDispatcher}.
* @param substitutionDescriptor
* {@link SubstitutionDescriptor} that defines what to be substituted after calling
* the special sensor.
*/
public SpecialMethodInstrumenter(MethodVisitor mv, int access, String name, String desc, long methodId, SubstitutionDescriptor substitutionDescriptor) {
super(mv, access, name, desc, methodId, false);
if (null == substitutionDescriptor) {
throw new IllegalArgumentException("SubstitutionDescriptor must not be null when creating the SpecialMethodInstrumenter.");
}
this.returnType = Type.getReturnType(desc);
this.argumentTypes = Type.getArgumentTypes(desc);
this.substitutionDescriptor = substitutionDescriptor;
}
/**
* {@inheritDoc}
*/
@Override
protected void generateBeforeCatchCall() {
// should never be called as we don't use the enhanced exception sensor in special method
// instrumentation
throw new UnsupportedOperationException("SpecialMethodInstrumenter can not generate before catch call.");
}
/**
* {@inheritDoc}
*/
@Override
protected void onMethodEnter() {
// generate code for calling before body
generateBeforeBodyCall();
if (substitutionDescriptor.isReturnValueSubstitution()) {
// and code for returning if result is not null
generateReturnIfResultNotNull();
} else {
// pop to clear result on stack
pop();
}
if (substitutionDescriptor.isParameterValueSubstitution()) {
// add code for substituting the parameter
generateParameterSubstitutionIfResultNotNull();
}
// start our try block
visitLabel(tryBlockStart);
}
/**
* {@inheritDoc}
*/
@Override
protected void onMethodExit(int opcode) {
// we wont add any byte-code prior to athrow return
// if exception is thrown we will skip the after body call
if (opcode == ATHROW) {
// exception return
return;
}
// just ensure that result is duplicated on the stack
// in case of void return or push null since we don't have result
if (opcode == RETURN) {
// standard return with no object (void)
// we push null so that null is passed to our dispatching method
pushNull();
} else if (opcode == ARETURN) {
// duplicate the original object
dup();
} else {
// this is a primitive result return
if ((opcode == LRETURN) || (opcode == DRETURN)) {
// if we have either long or double return, we need to duplicate the last two stacks
dup2();
} else {
// all other primitives just one duplicate
dup();
}
// box
box(returnType);
}
// generate code for calling after body
generateAfterBodyCall();
if (substitutionDescriptor.isReturnValueSubstitution()) {
// and code for returning if result is not null
generateReturnIfResultNotNull();
} else {
// no need to the support parameter substitution when the original method was already
// called, pop to clear result on stack
pop();
}
}
/**
* {@inheritDoc}
* <p>
* Here we add code to catch the unexpected exception that can occur during method execution and
* notify our hooks about it. We are sending the raised exception as the result of the method
* invocation.
*/
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// the definition of the end of the try block
Label tryBlockEnd = new Label();
visitLabel(tryBlockEnd);
// setup for the finally block
super.visitTryCatchBlock(tryBlockStart, tryBlockEnd, finallyHandler, null);
visitLabel(finallyHandler);
// generate code for calling after body
// push exception on the stack and pass to after body call as the result
dup();
generateAfterBodyCall();
// we can not substitute the result when exception is thrown just pop
pop();
mv.visitInsn(ATHROW);
// update the max stack stuff
super.visitMaxs(maxStack, maxLocals);
}
/**
* Generates before body call.
*/
private void generateBeforeBodyCall() {
// load hook dispatcher
loadHookDispatcher();
// first push method id
push(methodId);
// then this object or null if's static
if (isStatic) {
pushNull();
} else {
loadThis();
}
// then parameters
loadArgArray();
if (substitutionDescriptor.isParameterValueSubstitution()) {
// note here that we are saving the passed array as local variable
// this enables us to substitute the parameters later on if they are changed in the
// beforeBody
passedArgumentsLocal = newLocal(IInstrumenterConstant.OBJECT_ARRAY_TYPE);
storeLocal(passedArgumentsLocal);
loadLocal(passedArgumentsLocal);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, IInstrumenterConstant.IHOOK_DISPATCHER_INTERNAL_NAME, "dispatchSpecialMethodBeforeBody",
IInstrumenterConstant.DISPATCH_SPECIAL_METHOD_BEFORE_BODY_DESCRIPTOR, true);
}
/**
* Generates code for the after body call. This method expects the result of the method call on
* the stack that can be consumed.
*/
private void generateAfterBodyCall() {
// prepare for calls
// we expect result on stack so we must swap as result is last argument in the call
loadHookDispatcher();
swap();
// first push method id
push(methodId);
// can not just swap because method id is long, thus a bit of gymnastic
// r-l-l2
dup2X1();
// l-l2-r-l-l2
pop2();
// l-l2-r :)
// then this object or null if's static
if (isStatic) {
pushNull();
} else {
loadThis();
}
swap();
// then parameters
loadArgArray();
swap();
// execute after body
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, IInstrumenterConstant.IHOOK_DISPATCHER_INTERNAL_NAME, "dispatchSpecialMethodAfterBody",
IInstrumenterConstant.DISPATCH_SPECIAL_METHOD_AFTER_BODY_DESCRIPTOR, true);
}
/**
* Generates call for returning result on stack if needed.
*/
private void generateReturnIfResultNotNull() {
// create new local and store result
int local = newLocal(IInstrumenterConstant.OBJECT_TYPE);
storeLocal(local);
Label continueExecution = new Label();
// then load for null check
loadLocal(local);
ifNull(continueExecution);
// we can only return result if return type is not Void
if (!Type.VOID_TYPE.equals(returnType)) {
// then load for instance of check if it's an object
loadLocal(local);
instanceOfSafe(returnType);
// if zero compare
// we compare the result of the instance of check with the 0
// so we will continue execution if the result is false (0)
ifZCmp(EQ, continueExecution);
// load again for return
loadLocal(local);
// we need to unbox here if needed as we are changing the result
unbox(returnType);
}
// if method returned something, anyway return (also on void)
returnValue();
visitLabel(continueExecution);
}
/**
* Generates call to substitute the parameters of the method if the result on stack is a not
* null array.
*/
private void generateParameterSubstitutionIfResultNotNull() {
if (ArrayUtils.isEmpty(argumentTypes)) {
return;
}
for (int index = 0; index < argumentTypes.length; index++) {
// load our parameter array and get the element on the current index
loadLocal(passedArgumentsLocal);
push(index);
arrayLoad(IInstrumenterConstant.OBJECT_TYPE);
// create new local and store current
int local = newLocal(IInstrumenterConstant.OBJECT_TYPE);
storeLocal(local);
// load type that we need to substitute to
Type type = argumentTypes[index];
Label continueExecution = new Label();
// then load for instance of check if it's an correct object
loadLocal(local);
instanceOfSafe(type);
// if zero compare
// we compare the result of the instance of check with the 0
// so we will continue execution if the result is false (0)
ifZCmp(EQ, continueExecution);
// load again for setting
loadLocal(local);
// we need to unbox here if needed as we are changing
unbox(type);
// the store to the wanted index
storeArg(index);
visitLabel(continueExecution);
}
}
/**
* Based on the given type performs the safe instance of. This means that if given type is
* primitive, instance of check will be done with corresponding primitive wrapper.
* <p>
* This method expects object on stack. If method type is passed as the argument to this
* function the object on the stack will be removed and <code>false</code> will be pushed
* (imitating the failed instanceOf check).
*
* @param type
* Type
*/
private void instanceOfSafe(Type type) {
switch (type.getSort()) {
case Type.BOOLEAN:
instanceOf(IInstrumenterConstant.BOOLEAN_WRAPPER_TYPE);
break;
case Type.CHAR:
instanceOf(IInstrumenterConstant.CHAR_WRAPPER_TYPE);
break;
case Type.BYTE:
instanceOf(IInstrumenterConstant.CHAR_WRAPPER_TYPE);
break;
case Type.SHORT:
instanceOf(IInstrumenterConstant.CHAR_WRAPPER_TYPE);
break;
case Type.INT:
instanceOf(IInstrumenterConstant.INT_WRAPPER_TYPE);
break;
case Type.FLOAT:
instanceOf(IInstrumenterConstant.FLOAT_WRAPPER_TYPE);
break;
case Type.LONG:
instanceOf(IInstrumenterConstant.LONG_WRAPPER_TYPE);
break;
case Type.DOUBLE:
instanceOf(IInstrumenterConstant.DOUBLE_WRAPPER_TYPE);
break;
case Type.OBJECT:
case Type.ARRAY:
instanceOf(type);
break;
default:
// safety if somebody passes a method type
pop();
push(false);
}
}
}