ASM Java 替换方法调用指令

ASM Java replace method call instruction

背景

我想使用 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 框架 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: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,就像原始调用一样,所以对周围的代码没有影响。