如何使用ASM Bytecode框架修改catch块代码

时间:2016-03-21 07:12:58

标签: java java-bytecode-asm

我正在尝试更改方法内现有try / catch块的catch块的代码内容。

public static void hello(Throwable throwable) {
    try{
        System.out.println("in try");
    }catch(Exception e){
        System.out.println("in catch");
    }
}

我的目的是在catch块中添加一个方法调用。像,

public static void hello(Throwable throwable) {
    try{
        System.out.println("in Try");
    }catch(Exception e){
        *passException(e);*
        System.out.println("in catch");
    }
}

注意:我已尝试覆盖visitTryCatchBlock的{​​{1}}方法。并尝试以多种方式访问​​该标签,但没有任何帮助。我在网上的任何文档/指南/示例中都找不到这个。我希望我已经清楚地解释了我在尝试一切之后发布了这个问题。

2 个答案:

答案 0 :(得分:1)

当您对尝试的描述指向正确的方向visitTryCatchBlockvisitLabel时,您所面临的实际障碍尚不清楚。这是一个完成工作的自包含示例:

import java.io.IOException;
import java.lang.reflect.Method;

import org.objectweb.asm.*;

public class EnhanceExceptionHandler {
    static class Victim {
        public static void hello(boolean doThrow) {
            try {
                System.out.println("in try");
                if(doThrow) {
                    throw new Exception("just for demonstration");
                }
            } catch(Exception e){
                System.out.println("in catch");
            }
        }
        static void passException(Exception e) {
            System.out.println("passException(): "+e);
        }
    }

    public static void main(String[] args)
        throws IOException, ReflectiveOperationException {

        Class<EnhanceExceptionHandler> outer = EnhanceExceptionHandler.class;
        ClassReader classReader=new ClassReader(
            outer.getResourceAsStream("EnhanceExceptionHandler$Victim.class"));
        ClassWriter classWriter=new ClassWriter(classReader,ClassWriter.COMPUTE_FRAMES);
        classReader.accept(new ClassVisitor(Opcodes.ASM5, classWriter) {
            private String className;

            @Override
            public void visit(int version, int access, String name, String signature,
                String superName, String[] interfaces) {

                className=name;
                super.visit(version, access, name, signature, superName, interfaces);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String desc,
                String signature, String[] exceptions) {

                MethodVisitor visitor
                    = super.visitMethod(access, name, desc, signature, exceptions);
                if(name.equals("hello")) {
                    visitor=new MethodVisitor(Opcodes.ASM5, visitor) {
                        Label exceptionHandler;

                        @Override
                        public void visitLabel(Label label) {
                            super.visitLabel(label);
                            if(label==exceptionHandler) {
                                super.visitInsn(Opcodes.DUP);
                                super.visitMethodInsn(Opcodes.INVOKESTATIC, className,
                                    "passException", "(Ljava/lang/Exception;)V", false);
                            }
                        }

                        @Override
                        public void visitTryCatchBlock(
                            Label start, Label end, Label handler, String type) {

                            exceptionHandler=handler;
                            super.visitTryCatchBlock(start, end, handler, type);
                        }
                    };
                }
                return visitor;
            }
        }, ClassReader.SKIP_FRAMES|ClassReader.SKIP_DEBUG);

        byte[] code=classWriter.toByteArray();
        Method def=ClassLoader.class.getDeclaredMethod(
            "defineClass", String.class, byte[].class, int.class, int.class);
        def.setAccessible(true);
        Class<?> instrumented=(Class<?>)def.invoke(
            outer.getClassLoader(), outer.getName()+"$Victim", code, 0, code.length);
        Method hello=instrumented.getMethod("hello", boolean.class);
        System.out.println("invoking "+hello+" with false");
        hello.invoke(null, false);
        System.out.println("invoking "+hello+" with true");
        hello.invoke(null, true);
    }
}

正如您所看到的,它是直截了当的,只需在visitTryCatchBlock中记录异常处理程序的标签,并在遇到visitLabel中的代码位置后立即注入所需的代码。其余的是用于读取和转换类文件并加载结果以用于测试目的的批量代码。

答案 1 :(得分:0)

如果在ASM中使用Tree API,则可以获取类的MethodNode,然后获取MethodNode的指令(InsnList)。使用InsnList的toArray()方法,您可以迭代单个指令。要编辑说明,您可以执行以下操作:

for (MethodNode method : classNode.methods) {
    method.instructions.set(insn, otherInsn); // Sets the instruction to another one
    method.instructions.remove(insn); //Removes a given instruction
    method.instructions.add(insn); //Appends to end
    method.instructions.insert(insn, otherInsn); // Inserts an instruction after the given insn
    method.instructions.insertBefore(insn, otherInsn); // Inserts an instruction before the given insn
}

我个人认为这是编辑方法体的最简单方法。