Java 通过ASM替换完整方法

Java 通过ASM替换完整方法,java,java-bytecode-asm,Java,Java Bytecode Asm,我试图编写一个脚本,用一个基本的throw new exception()行替换每个方法体。我正处于学习ASM的开始阶段,因此任何关于在哪里寻找的建议都将不胜感激 到目前为止我所做的: package methodtester; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.Inpu

我试图编写一个脚本,用一个基本的throw new exception()行替换每个方法体。我正处于学习ASM的开始阶段,因此任何关于在哪里寻找的建议都将不胜感激

到目前为止我所做的:

package methodtester;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
import static org.objectweb.asm.Opcodes.ASM4;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.NEW;

public class MethodTransformer {
    public static void main(String[] args) throws IOException {
        InputStream in = MethodTester.class.getResourceAsStream("/methodtester/testingMethod.class");

        ClassReader classReader = new ClassReader(in);

        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        ExceptionThrower exceptionThrower = new ExceptionThrower(classWriter);

        classReader.accept(exceptionThrower, 0);

        File outputDir = new File("build/classes/methodtester/");

        outputDir.mkdirs();

        DataOutputStream dataOutputStream =
            new DataOutputStream(
                new FileOutputStream(
                    new File(outputDir,"testingMethod-postASM.class")));

        dataOutputStream.write(classWriter.toByteArray());
    }

    public static class ExceptionThrower extends ClassVisitor {
        private String _className;
        private boolean _isInterface;

        public ExceptionThrower(ClassVisitor classVisitor) {
            super(ASM4, classVisitor);
        }

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

            cv.visit(version, access, name, signature, superName, interfaces);

            _className = name;

            _isInterface = (access & ACC_INTERFACE) != 0;
        }

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

            MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
                exceptions);

            if (!_isInterface && mv != null && !name.equals("<init>")) {
                ExceptionThrowerMethod exceptionThrowerMethod =
                    new ExceptionThrowerMethod(mv);

                return exceptionThrowerMethod;
            }

            return mv;
        }

        public static class ExceptionThrowerMethod extends MethodVisitor {
            public ExceptionThrowerMethod(MethodVisitor methodVisitor) {
                super(ASM4, methodVisitor);
            }

            @Override
            public void visitCode() {
                mv.visitCode();
                mv.visitTypeInsn(NEW, "java/io/IOException");
                mv.visitInsn(DUP);
                mv.visitMethodInsn(INVOKESPECIAL, "java/io/IOException", "<init>", "()V", false);
                mv.visitInsn(ATHROW);
                mv.visitMaxs(2, 0);
                mv.visitEnd();
            }
        }
    }
}
看看javap-c-v,我得到:

 public void testing() throws java.io.IOException;
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #14                 // class java/io/IOException
         3: dup
         4: invokespecial #15                 // Method java/io/IOException."<init>":()V
         7: athrow
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            8       0     0  this   Lmethodtester/testingMethod;
      LineNumberTable:
        line 24: 8
        line 25: 8
    Exceptions:
      throws java.io.IOException
}
public void testing()抛出java.io.IOException;
描述符:()V
旗帜:ACC_PUBLIC
代码:
堆栈=2,局部变量=1,参数大小=1
0:new#14//类java/io/IOException
3:dup
4:invokespecial#15//方法java/io/IOException。”“:()V
7:athrow
LocalVariableTable:
起始长度插槽名称签名
8.0本方法试验/测试方法;
LineNumberTable:
第24行:8
第25行:8
例外情况:
抛出java.io.IOException
}
通过使用asmizer查看类,我发现该方法是:

mv = cw.visitMethod(ACC_PUBLIC, "testing", "()V", null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, "java/io/IOException");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/io/IOException", "<init>", "()V", false);
mv.visitInsn(ATHROW);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("testing method");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
mv=cw.visitMethod(根据公共“测试”和“()V”,空,空);
mv.visitCode();
mv.visitTypeInsn(新的“java/io/IOException”);
mv.visitInsn(DUP);
mv.visitMethodInsn(调用,特别是“java/io/IOException”,“”,“()V”,false);
visitInsn号(ATHROW);
mv.visitFieldInsn(GETSTATIC,“java/lang/System”,“out”,“Ljava/io/PrintStream;”);
mv.visitLdcInsn(“试验方法”);
mv.visitMethodInsn(INVOKEVIRTUAL,“java/io/PrintStream”,“println”,“Ljava/lang/String;)V”,false);
mv.visitInsn(返回);
mv.visitmax(2,1);
mv.visitEnd();
}
看起来我需要更改LocalVariableTable以使其从0开始,长度为0

谢谢


编辑:使用COMPUTE_帧更新并更新新的错误代码。您正在将从
类编写器收到的原始
MethodVisitor
传递给自定义
MethodVisitor
的超级构造函数。这意味着,您没有覆盖的每个
visit…
调用都将委托给该
MethodVisitor
,复制整个原始代码

当然,首先,您不想复制原始代码,其次,在调用
visitMaxs
visitEnd
后,接收到对编写器的矛盾
visit…
调用无助于创建有效代码

当您想要完全替换一个方法时,不应将目标编写器的
MethodVisitor
展示给基类,而应仅将其用于您自己的代码生成:

public static class ExceptionThrowerMethod extends MethodVisitor {
    private final MethodVisitor target;

    public ExceptionThrowerMethod(MethodVisitor methodVisitor) {
        super(ASM4, null);
        this.target=methodVisitor;
    }

    @Override
    public void visitCode() {
        target.visitCode();
        target.visitTypeInsn(NEW, "java/io/IOException");
        target.visitInsn(DUP);
        target.visitMethodInsn(INVOKESPECIAL,"java/io/IOException","<init>","()V",false);
        target.visitInsn(ATHROW);
        target.visitMaxs(2, 0);
        target.visitEnd();
    }
}
公共静态类ExceptionThrowerMethod扩展MethodVisitor{
私人目标;
公共例外RowerMethod(MethodVisitor MethodVisitor){
super(ASM4,null);
this.target=methodVisitor;
}
@凌驾
公共无效访问代码(){
target.visitCode();
visitTypeInsn(新的,“java/io/IOException”);
目标visitInsn(DUP);
visitMethodInsn(INVOKESPECIAL,“java/io/IOException”,“”,“()V”,false);
target.visitInsn(ATHROW);
target.visitmax(2,0);
target.visitEnd();
}
}

通过这种方式,您可以通过让
在本地解析资源来简化资源访问,例如
testingMethod.Class.getResourceAsStream(“testingMethod.Class”)

要替换的方法没有参数?@saka1029,但现在我只是在测试一个没有参数的简单方法,并希望先让它工作。尝试将ClassWriter选项
ClassWriter.COMPUTE\u MAXS
更改为
ClassWriter.COMPUTE\u FRAMES
。请参阅@saka1029进行了更改并用新错误更新了问题,似乎我需要使用asm以某种方式更改LocalVariableTable。谢谢,我是通过在所有visit*方法的覆盖版本中添加布尔检查来实现的,这是一个混乱,这个解决方案更干净!
public static class ExceptionThrowerMethod extends MethodVisitor {
    private final MethodVisitor target;

    public ExceptionThrowerMethod(MethodVisitor methodVisitor) {
        super(ASM4, null);
        this.target=methodVisitor;
    }

    @Override
    public void visitCode() {
        target.visitCode();
        target.visitTypeInsn(NEW, "java/io/IOException");
        target.visitInsn(DUP);
        target.visitMethodInsn(INVOKESPECIAL,"java/io/IOException","<init>","()V",false);
        target.visitInsn(ATHROW);
        target.visitMaxs(2, 0);
        target.visitEnd();
    }
}