Инструкция вызова метода замены ASM Java

Фон

Я хочу провести некоторую инструментальную работу над некоторым трудоемким методом, таким как org/json/JSONObject.toString(), с использованием платформы ASM Java.

Оригинальный вызов метода

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

    }
}

person kvh    schedule 25.02.2016    source источник


Ответы (1)


Вам не нужно «манипулировать» вызовом метода. Ключевым моментом является то, что ваш посетитель создает код, передавая каждый входящий вызов посетителя автору, и вы удобно наследуете реализацию, которая делает это 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, как и исходный вызов, поэтому это не влияет на окружающий код.

person Holger    schedule 25.02.2016
comment
Большое спасибо! Я пытаюсь глубже погрузиться в манипулирование байт-кодом и структуру ассемблера, не могли бы вы дать мне несколько статей или книг для чтения? - person kvh; 26.02.2016