Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I add code to message before every return?

I'm currently trying to generate code through the wonderfully crafted java-asm library (Version 4). More specifically, I want to append code to the end of a method, just before every return call. I sucessfully am able to add code BEFORE the method's code. However currently I have no idea how to perform the aforementioned transformation. I really would appreciate pointers in how this can be acomplished.

MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 42);
return mv;
like image 764
nanoquack Avatar asked Apr 26 '13 10:04

nanoquack


2 Answers

You have two solutions:

1. Use the visitInsn(int opcode) method in MethodVisitor:

//this is the custom method visitor
private class InsertInitCodeBeforeReturnMethodVisitor extends MethodVisitor{

    public InsertInitCodeBeforeReturnMethodVisitor(MethodVisitor mv) {
        super(Opcodes.ASM4, mv);
    }

    @Override
    public void visitInsn(int opcode) {
        //whenever we find a RETURN, we instert the code, here only crazy example code
        switch(opcode) {
          case Opcodes.IRETURN:
      case Opcodes.FRETURN:
      case Opcodes.ARETURN:
      case Opcodes.LRETURN:
      case Opcodes.DRETURN:
      case Opcodes.RETURN:
              mv.visitVarInsn(Opcodes.ALOAD, 42);
              break;
          default: // do nothing
        }
        super.visitInsn(opcode);
    }
}

2. Use the onMethodExit(int opcode) method in AdviceAdapter in org.objectweb.asm.commons:

//this is the custom method visitor
private class InsertInitCodeBeforeReturnMethodVisitor extends AdviceAdapter{

    public InsertInitCodeBeforeReturnMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
        super(Opcodes.ASM4, mv, access, name, desc);
    }

    @Override
    protected void onMethodExit(int opcode) {
        if(opcode != Opcdoes.ATHROW) {
            mv.visitVarInsn(Opcodes.ALOAD, 42);
        }
    }
}

I personally like the AdviceAdapter better as it removes the headache of making the actual call to the original return instruction like you have to do with the first solution (e.g. super.visitInsn(opcode);). And secondly, it provides a good abstraction specifically for the visits to RETURN instructions (and ATHORW); which is not true of the visitInsn(int opcode) method in the vanilla MethodVisitor, where you have to detect the visits the RETURN instructions among so many others like, DUPs, ICONST_0, etc. which might or might not be relevant to the problem at hand.

But this again depends on the problem at hand. If this is the only instrumentation that is being performed, i will stick to AdviceAdapter. If there are other things you would like to do in conjunction with visiting RETURN instructions, i might stay with the simple MethodVisitor, as it just might give me more flexibility. That being said, i have been using the AdviceAdapter for a little over a year now for a heavy instrumentation driven project, its worked out fine so far!


Edit:

applying a method-visitor

It is often not clear as to how to use or apply a method-visitor/method-adaptor (at least for me), so i have put together a quick code example here: gist.github.com/VijayKrishna/1ca807c952187a7d8c4d, that shows how to use a method adaptor, via its corresponding class-visitor/class-adaptor. In the example code snippet, i have changed the name of the method-adaptor from what i am using in this answer, but they do the same thing. Also, the code snippet shows a method adaptor that extends the AdviceAdapter.

In summary, you first "invoke" the class adapter as follows:

ClassReader cr = new ClassReader(in);
ClassWriter cw = new ClassWriter(ClassReader.EXPAND_FRAMES);
ReturnAdapter returnAdapter = new ReturnAdapter(cw, className);
cr.accept(returnAdapter, 0);

You then follow it up adapting the methods as follows in the class-adaptor's visitMethod method:

MethodVisitor mv;
mv = cv.visitMethod(access, name, desc, signature, exceptions);
mv = new MethodReturnAdapter(Opcodes.ASM4, className, access, name, desc, mv);
return mv;
like image 113
vijay Avatar answered Oct 13 '22 05:10

vijay


Actually it turned out to be easy, but I was searching in the completely wrong place. In order to transform the method body at arbitrary positions, you have to create a custom MethodVisitor subclass and pipe the existing MethodVisitor through it in order to perform the transformations you want it to do. In my example, whenever the custom MethodVisitor finds an opcode RETURN, it adds code, like so:

public class ActorInterfaceTransformer extends ClassVisitor {
    public ActorInterfaceTransformer(ClassVisitor cv) {
        super(Opcodes.ASM4, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        //if the method is the one we want to transform
        if(name.equals("<init>")){
            //... then we pipe the method visitor 
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            return new InsertInitCodeBeforeReturnMethodVisitor(mv);
        }
        return super.visitMethod(access, name, desc, signature, exceptions);
    }
}

//this is the custom method visitor
private class InsertInitCodeBeforeReturnMethodVisitor extends MethodVisitor{

    public InsertInitCodeBeforeReturnMethodVisitor(MethodVisitor mv) {
        super(Opcodes.ASM4, mv);
    }

    @Override
    public void visitInsn(int opcode) {
        //whenever we find a RETURN, we instert the code, here only crazy example code
        if(opcode==Opcodes.RETURN){
            mv.visitVarInsn(Opcodes.ALOAD, 42);
        }
        super.visitInsn(opcode);
    }
}
like image 41
nanoquack Avatar answered Oct 13 '22 04:10

nanoquack