Java 使用ASM选择和修改'if'语句

Java 使用ASM选择和修改'if'语句,java,java-bytecode-asm,bytecode-manipulation,Java,Java Bytecode Asm,Bytecode Manipulation,我想在特定行上更新已有类中的if语句,而不改变整个方法 以下是目标代码(类、方法的名称和一些因不相关而更改的代码): 在这段代码中,我想将if(firstPar.check())更改为如下内容: if(firstPar.check() && !Utilities.someOtherCheck(firstPar, secondPar)) 以下是我试图解决这个问题的方法: ClassNode node = new ClassNode(); ClassReader reader =

我想在特定行上更新已有类中的
if
语句,而不改变整个方法

以下是目标代码(类、方法的名称和一些因不相关而更改的代码):

在这段代码中,我想将if(firstPar.check())更改为如下内容:

if(firstPar.check() && !Utilities.someOtherCheck(firstPar, secondPar))
以下是我试图解决这个问题的方法:

ClassNode node = new ClassNode();
ClassReader reader = new ClassReader(bytes); //bytes - original target class
reader.accept(node, 0);
ClassWriter cw = new ClassWriter(0);
node.accept(cw);
MethodVisitor visitor = cw.visitMethod(ACC_PUBLIC, "targetMethod", "<it's signature>", null, null);
visitor.visitCode();
//No idea what to do next
ClassNode节点=新的ClassNode();
ClassReader=新的ClassReader(字节)//字节-原始目标类
reader.accept(节点,0);
ClassWriter cw=新的ClassWriter(0);
接受节点(cw);

MethodVisitor=cw.visitMethod(ACC_PUBLIC,“targetMethod”,“问题1可能是最难的。您需要通过识别一些模式来找出插入指令的位置。如果您假设
firstPar.check()
只调用一次,那么您可以为
If查找以下字节码指令(firstPar.check())

其中
L3
是跳转标签,如果
check
return
false

对于问题2,请注意
if(firstPar.check()&&!Utilities.someOtherCheck(firstPar,secondPar))
的字节码指令是:

ALOAD 1
INVOKEVIRTUAL Data.check ()Z
IFEQ L3
ALOAD 1
ALOAD 2
INVOKESTATIC Utilities.someOtherCheck (LData;LData;)Z
IFNE L3
因此,您需要在
IFEQ L3
之后插入4条新指令

您可以通过使用为
targetMethod
创建适配器,方法是子类化
ClassVisitor
MethodNode

private static class ClassAdapter extends ClassVisitor {
    public ClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        if (name.equals("targetMethod"))
            return new MethodAdapter(access, name, desc, signature, exceptions, mv);
        else
            return mv;
    }
}

private static class MethodAdapter extends MethodNode {
    public MethodAdapter(int access, String name, String desc,
            String signature, String[] exceptions, MethodVisitor mv) {
        super(Opcodes.ASM5, access, name, desc, signature, exceptions);
        this.mv = mv;
    }
    // More to come ...
}
MethodAdapter
内部,您可以覆盖
visitEnd
以迭代方法内部的所有
指令,并尝试检测上述3条指令,并在它们之后插入4条新指令:

@Override
public void visitEnd() {
    // Iterates all instructions in the method
    ListIterator<AbstractInsnNode> itr = instructions.iterator();
    while (itr.hasNext()) {
        // Checks whether the instruction is ALOAD 1
        AbstractInsnNode node = itr.next();
        if (node.getOpcode() != Opcodes.ALOAD
                || ((VarInsnNode) node).var != 1)
            continue;

        // Checks whether the next instruction is INVOKEVIRTUAL
        if (node.getNext() == null
                || node.getNext().getOpcode() != Opcodes.INVOKEVIRTUAL)
            continue;

        // Checks the invoked method name and signature
        MethodInsnNode next = (MethodInsnNode) node.getNext();
        if (!next.owner.equals("Data")
                || !next.name.equals("check")
                || !next.desc.equals("()Z"))
            continue;

        // Checks whether the next of the next instruction is IFEQ
        AbstractInsnNode next2 = next.getNext();
        if (next2 == null
                || next2.getOpcode() != Opcodes.IFEQ)
            continue;

        // Creates a list instructions to be inserted
        InsnList list = new InsnList();
        list.add(new VarInsnNode(Opcodes.ALOAD, 1));
        list.add(new VarInsnNode(Opcodes.ALOAD, 2));
        list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, 
            "Utilities", "someOtherCheck", 
            "(LData;LData;)Z", false));
        list.add(new JumpInsnNode(Opcodes.IFNE, ((JumpInsnNode) next2).label));

        // Inserts the list, updates maxStack to at least 2, and we are done
        instructions.insert(next2, list);
        maxStack = Math.max(2, maxStack);
        break;
    }
    accept(mv);
}

谢谢你的回答,效果很好!顺便问一下
ClassWriter
构造函数和
reader\accept
方法中的零表示什么?没有带0值的标志。这只是默认行为吗?@RedEnergy它们是写和读时的选项。你是对的,0是默认行为。事实上,你刚刚指出了一个bug.:-)我还应该将
maxStack
更新为至少
2
,以确保堆栈足够大,可以容纳
someOtherCheck
的参数。请参见编辑:我添加了一行。
private static class ClassAdapter extends ClassVisitor {
    public ClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        if (name.equals("targetMethod"))
            return new MethodAdapter(access, name, desc, signature, exceptions, mv);
        else
            return mv;
    }
}

private static class MethodAdapter extends MethodNode {
    public MethodAdapter(int access, String name, String desc,
            String signature, String[] exceptions, MethodVisitor mv) {
        super(Opcodes.ASM5, access, name, desc, signature, exceptions);
        this.mv = mv;
    }
    // More to come ...
}
@Override
public void visitEnd() {
    // Iterates all instructions in the method
    ListIterator<AbstractInsnNode> itr = instructions.iterator();
    while (itr.hasNext()) {
        // Checks whether the instruction is ALOAD 1
        AbstractInsnNode node = itr.next();
        if (node.getOpcode() != Opcodes.ALOAD
                || ((VarInsnNode) node).var != 1)
            continue;

        // Checks whether the next instruction is INVOKEVIRTUAL
        if (node.getNext() == null
                || node.getNext().getOpcode() != Opcodes.INVOKEVIRTUAL)
            continue;

        // Checks the invoked method name and signature
        MethodInsnNode next = (MethodInsnNode) node.getNext();
        if (!next.owner.equals("Data")
                || !next.name.equals("check")
                || !next.desc.equals("()Z"))
            continue;

        // Checks whether the next of the next instruction is IFEQ
        AbstractInsnNode next2 = next.getNext();
        if (next2 == null
                || next2.getOpcode() != Opcodes.IFEQ)
            continue;

        // Creates a list instructions to be inserted
        InsnList list = new InsnList();
        list.add(new VarInsnNode(Opcodes.ALOAD, 1));
        list.add(new VarInsnNode(Opcodes.ALOAD, 2));
        list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, 
            "Utilities", "someOtherCheck", 
            "(LData;LData;)Z", false));
        list.add(new JumpInsnNode(Opcodes.IFNE, ((JumpInsnNode) next2).label));

        // Inserts the list, updates maxStack to at least 2, and we are done
        instructions.insert(next2, list);
        maxStack = Math.max(2, maxStack);
        break;
    }
    accept(mv);
}
ClassReader reader = new ClassReader("Target");
ClassWriter writer = new ClassWriter(reader, 0);
TraceClassVisitor printer = new TraceClassVisitor(writer, 
        new PrintWriter(System.getProperty("java.io.tmpdir") + File.separator + "Target.log"));
ClassAdapter adapter = new ClassAdapter(printer);
reader.accept(adapter, 0);
byte[] b = writer.toByteArray(); // The modified bytecode