ASM Java 替换方法调用指令

4

背景

我想使用Java框架ASM对一些耗时的方法进行仪器化工作,例如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;
    }
}

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

问题:如何使用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个回答

11

你不需要"操作"一个方法调用。关键点是你的访问者通过将每个传入的访问者调用传递给写入器来生成代码,而你正好继承了一个执行这个一对一操作的实现。

因此,你没有重写的每个visit...方法都会将每个调用委托给写入器,从而生成完全相同的指令。当覆盖方法并且未中继调用时,相应的指令不会被再现,也就是说被有效地删除。当你调用其他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,就像原始调用一样,因此对周围代码没有影响。


非常感谢!我正在尝试深入研究字节码操作和asm框架,你能给我推荐一些文章或书籍吗? - kvh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接