使用 ASM 库覆盖 Java 字节码中的局部变量名称

Overriding a Local Variable name in Java Bytecode using the ASM library

我使用 Java 字节码的 ASM 库阅读了 class' 方法及其内容。这是在 class:

中输出任何方法的局部变量名称
ClassReader reader = new ClassReader(new FileInputStream(new File("TheClass.class")));

final ClassNode classNode = new ClassNode();
reader.accept(classNode, 0);

for (final MethodNode mn : (List<MethodNode>)classNode.methods) {
    for (LocalVariableNode n : (List<LocalVariableNode>)mn.localVariables) {
        System.out.println(n.name);
    }
}

这是编译后的 TheClass class 文件的来源:

public class TheClass {
    public final String a;
    public final String b;

    public TheClass(String c, String d) {
        this.b = d;
        this.a = c;
    }
}

所以输出在逻辑上是 thiscd现在,我需要将这个已编译的 class 复制到一个新文件中,但将 <init> 方法的参数(局部变量)更改为不同的名称(ef).我该怎么做? 我对 MethodVisitors 等几乎没有经验。

您需要编写一个适配器(ClassVisitor 的子类)并将其与 reader 链接起来。例如,

ClassReader reader = new ClassReader(new FileInputStream(new File("TheClass")));
ClassWriter writer = new ClassWriter(reader, 0);
TraceClassVisitor printer = new TraceClassVisitor(writer, 
    new PrintWriter(System.getProperty("java.io.tmpdir") 
            + File.separator + name + ".log"));
ClassAdapter adapter = new ClassAdapter(printer);
reader.accept(adapter, 0);
byte[] b = writer.toByteArray();

有了它,您将获得 byte[],您可以将其保存到文件中,或使用 ClassLoader.

加载到 Class

TraceClassVisitor 只是另一个 ClassVisitor,我还链接它以在您的临时目录中获得人类可读的日志。)

适配器可能如下所示。您要覆盖的方法是 visitLocalVariable:

public 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);
        return new MethodAdapter(mv);
    }
}

public class MethodAdapter extends MethodVisitor {
    public MethodAdapter(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitLocalVariable(String name, String desc, String signature,
            Label start, Label end, int index) {
        // Put your rename logic here
        if (name.equals("c"))
            name = "e";
        else if (name.equals("d"))
            name = "f";
        super.visitLocalVariable(name, desc, signature, start, end, index);
    }
}