Search code examples
javabytecodejava-bytecode-asm

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


I read a class' methods and their contents using the ASM library for Java ByteCode. This is to output any method's local variables names in the 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);
    }
}

And this is the source of the compiled TheClass class file:

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

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

So the output is, logically, this, c, d. Now, I need to copy this compiled class into a new file, but change the <init> method's parameters (local variables) to different names (e, f). How can I do so? I have little to no experience with MethodVisitors and such.


Solution

  • You'll need to write an adapter (a subclass of ClassVisitor) and chain it with reader. For instance,

    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();
    

    With it you'll get byte[], which you can save into file, or load into Class with a ClassLoader.

    (TraceClassVisitor is just another ClassVisitor that I chain it also to get a human-readable log in your temp directory.)

    The adapter could look like the following. The method you'll want to override is 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);
        }
    }