Search code examples
javabytecodejava-bytecode-asm

ASM. Clone JumpInsnNode correctly


Do you know the way how to clone JumpInsnNode correctly?

I tried several things:

1. endList.add(insn.clone(labelNodes));

insn is JumpInsnNode here; labelNodes - all labelNodes in the method. This code throws NullPointerException because of implementation of clone method that calls clone method from AbsractInsnNode class:

static LabelNode clone(final LabelNode label, final Map<LabelNode, LabelNode> map) {
        return map.get(label);
    }

This method returns null.

  1. endList.add(new JumpInsnNode(insn.getOpcode(), new LabelNode(insn.label.getLabel())));

It throws an exception:

java.lang.ArrayIndexOutOfBoundsException: 0
    at org.objectweb.asm.Frame.a(Unknown Source)
    at org.objectweb.asm.Frame.a(Unknown Source)
    at org.objectweb.asm.MethodWriter.visitMaxs(Unknown Source)
    at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
    at org.objectweb.asm.tree.MethodNode.accept(Unknown Source)
    at org.objectweb.asm.tree.ClassNode.accept(Unknown Source)
    at com.smartbear.real.agent.Agent.transform(Agent.java:221)
    at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:424)
    at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
    at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
    at com.smartbear.real.agent.Agent.agentmain(Agent.java:118)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:382)
    at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:407)

The reasoning: I need to have ability to load method return values once again. I was able to do this with other types of instruction. However this type of instruction: return (test != null); doesn't work for me now. Byte code for this:

  LINENUMBER 112 L2
    ALOAD 3: tr
    IFNULL L3
    ICONST_1
    IRETURN
   L3
   FRAME APPEND [Boolean]
    ICONST_0
    IRETURN

Solution

  • It seems like you are using the wrong approach. You shouldn’t clone arbitrary instructions to replicate the return values, not only does this become unnecessarily complex, it might also replicate side-effects thus create unintended behavior different than without Instrumentation.

    Replicating the value, a method is about to return, is much simpler. Right before one of the …RETURN instructions is executed, there must be the actual return value on the operand stack. So all you have to do, is to insert an appropriate DUP instruction (either DUP or DUP2) before the …RETURN instruction to duplicate the value. Then you can insert your logging code consuming that duplicated value.

    E.g. you may change occurrences of IRETURN to:

    DUP
    INVOKESTATIC java/lang/Integer valueOf (I)Ljava/lang/Integer;
    INVOKESTATIC MyLogger logReturnValue (Ljava/lang/Object;)V
    IRETURN // the original return instruction
    

    or DRETURN to:

    DUP2
    INVOKESTATIC java/lang/Double valueOf (D)Ljava/lang/Double;
    INVOKESTATIC MyLogger logReturnValue (Ljava/lang/Object;)V
    DRETURN // the original return instruction
    

    or ARETURN to:

    DUP
    INVOKESTATIC MyLogger logReturnValue (Ljava/lang/Object;)V
    ARETURN // the original return instruction
    

    etc.