ASM Java替换方法调用指令

时间:2016-02-25 03:07:30

标签: java java-bytecode-asm bytecode-manipulation

背景

我想使用ASM Java框架对一些耗时的方法(例如 org/json/JSONObject.toString()进行一些检测工作。

对方法的原始调用

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

经过检测

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

使用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);

问题:如何使用ASM API获得一般溶剂?

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?
        }

    }
}

1 个答案:

答案 0 :(得分:9)

您无需“操纵”方法调用。关键点在于,您的访问者通过将每个传入的访问者呼叫转发给作者来生成代码,并且您可以方便地继承执行此操作的1:1。

因此,您未覆盖的每个visit…方法都会将每次调用委托给编写器,从而生成完全相同的指令。当它们委托给传递相同参数的原始super实现时,这同样适用于重写方法。当您重写方法并且不中继调用时,不会再现,读取和删除相应的指令。当您调用其他visit…方法(而不是)时,您将生成其他指令。

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
}

因此上面的代码拦截了您感兴趣的指令,并且不会重现它,而是生成所需的invokestatic指令。这没有额外的调整,因为静态方法调用将从堆栈中消耗JSONObject并生成String,就像原始调用一样,因此对周围的代码没有影响。