Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASM Java replace method call instruction

Background

I want to do some instrumentation work on some time consuming method such as org/json/JSONObject.toString(), using ASM Java framework.

Original call to the Method

public class JSONUsage {
    public void callToString() {
        JSONObject jsonObject = new JSONObject();
        String a = jsonObject.toString();//original call
        System.out.println(a);
    }
}

After instrumented

public class JSONUsage {
    public void callToString() {
        JSONObject jsonObject = new JSONObject();
        // **important!**
        //pass the instance as an param, replace the call to a static method
        String a = JSONReplacement.jsonToString(jsonObject);
        System.out.println(a);
    }
}

public class JSONReplacement {

    public static String jsonToString(JSONObject jsonObject) {
        //do the time caculation
        long before = System.currentTimeMillis();
        String ret = jsonObject.toString();
        long elapsed = System.currentTimeMillis() - before;

        return ret;
    }
}

Using ASM framework 3.0

ClassReader cr = new ClassReader("JSONUsage");
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ReplaceClassVisitor replaceClassVisitor = new ReplaceClassVisitor(cw);

cr.accept(replaceClassVisitor, ClassReader.EXPAND_FRAMES);

Question: How can i use ASM API to get a general sollution?

public class ReplaceClassVisitor extends ClassAdapter {

    public ReplaceClassVisitor(ClassVisitor cv) {
        super(cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        return new MethodReplaceMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc);
    }

    private static final class MethodReplaceMethodVisitor extends GeneratorAdapter {

        public MethodReplaceMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
            super(mv, access, name, desc);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
            //org/json/JSONObject.toString() here is a example, 
           //i want a general instruction
            if (owner.equals("org/json/JSONObject") && name.equals("toString")) {
                replaceCall(opcode, owner, name, desc);
            }
        }

        private void replaceCall(int opcode, String owner, String name, String desc) {
            //how can i have a general asm instruction to manipulate this method call?
        }

    }
}
like image 500
kvh Avatar asked Dec 19 '22 19:12

kvh


1 Answers

You don’t need to “manipulate” a method call. The key point is that your visitor is producing the code by relaying every incoming visitor call to the writer and you’re conveniently inheriting an implementation which does this 1:1.

So every visit… method you’re not overriding will delegate every invocation to the writer, producing exactly the same instruction. The same applies to overridden methods when they delegate to their original super implementation passing the same arguments. When you override a method and do not relay an invocation, the corresponding instruction is not reproduced, read, effectively removed. When you invoke other visit… methods (instead) you will produce other instructions.

private static final class MethodReplaceMethodVisitor extends GeneratorAdapter {

  public MethodReplaceMethodVisitor(
      MethodVisitor mv, int access, String name, String desc) {
      super(mv, access, name, desc);
  }

  @Override
  public void visitMethodInsn(
      int opcode, String owner, String name, String desc, boolean itf) {

      if(opcode==Opcodes.INVOKEVIRTUAL && owner.equals("org/json/JSONObject")
      && name.equals("toString") && desc.equals("()Ljava/lang/String;")) {
        // not relaying the original instruction to super effectively removes the original 
        // instruction, instead we're producing a different instruction:
        super.visitMethodInsn(Opcodes.INVOKESTATIC, "whatever/package/JSONReplacement",
          "jsonToString", "(Lorg/json/JSONObject;)Ljava/lang/String;", false);
      }
      else // relaying to super will reproduce the same instruction
        super.visitMethodInsn(opcode, owner, name, desc, itf);
  }

  // all other, not overridden visit methods reproduce the original instructions
}

So the code above intercepts the instruction you’re interested in and will not reproduce it, but produce the desired invokestatic instruction instead. This works without additional adaptations as your static method invocation will consume a JSONObject from the stack and produce a String, just like the original invocation, so there is no effect on the surrounding code.

like image 154
Holger Avatar answered Dec 21 '22 07:12

Holger