Search code examples
javajava-bytecode-asm

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;

Solution

  • 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;