Search code examples
javajava-bytecode-asm

Java ASM - How to create local variables in method


I'm trying to compile an AST to a java classfile, using ASM to create the class itself. I ran into a problem when I try to add local variables to functions (functions are created correctly).

F.e., a simple function like this:

public void test() {
    int a = 10;
    int b = 11;
    int c = 12;
    int d = 1;
}

Becomes this, once instrumented (decompiled with JD-GUI):

public void test() {
    int i = 10;
    int j = 11;
    int k = 12;
    int m = 4;
}

The bytecode for this looks like this:

 public test() { //()V
         bipush 10
         istore1
         bipush 11
         istore2
         bipush 12
         istore3
         iconst_4
         istore4
         return
 }

I'm creating the local variables like this:

methodVisitor.visitVarInsn(Opcodes.BIPUSH, symbol.value);
methodVisitor.visitVarInsn(Opcodes.ISTORE, position);

symbol is the current local variable, and position its index, 1-based. F.e. a would be on position 1 and d would be on position 4.

My problem is the correct use of the Opcodes for different values. I tried Opcodes like Opcodes.LDC, but then the generated class doesn't have any local variables in its methods, but all the globally defined variables and functions are in there twice.

What's the correct way of adding variables of arbitrary type and with an arbitrary value to a method?


edit:

As per the comments I tried to incorporate the other questions answer like so:

MethodVisitor methodVisitor = cw.visitMethod(
        access,
        methodName,
        typeDescriptors,
        null,
        null
);

LocalVariablesSorter mv = new LocalVariablesSorter(access, typeDescriptors, methodVisitor);

I then use the LocalVariablesSorter to add a local variable like this:

// type is net.bytebuddy.jar.asm.Type.INT_TYPE
net.bytebuddy.jar.asm.Type type = getType(symbol.type);
int id = mv.newLocal(type);
mv.visitVarInsn(Opcodes.ISTORE, id);

which decompiles to:

/* Error */
public void test()
{
    // Byte code:
    //   0: istore_2
    //   1: istore 4
    //   3: istore 6
    //   5: istore 8
    //   7: return
}

resulting in this bytecode:

public test() { //()V
    istore2
    istore4
    istore6
    istore8
    return
}

What am I missing here?


Solution

  • The ISTORE instruction stores the value on top of the stack to a register. You have no values on the stack and this results in a validation error.

    If you want to store the value 2 for example, you need to load the value onto the stack first:

    net.bytebuddy.jar.asm.Type type = getType(symbol.type);
    int id = mv.newLocal(type);
    mv.visitInsn(Opcodes.ICONST_2);
    mv.visitVarInsn(Opcodes.ISTORE, id);
    

    For "higher" values, there are other instructions such as BIPUSH or SIPUSH. Otherwise, you can use LDC to load a value from the constant pool. This is normally the most efficient way of storing values; ASM implicitly deduplicates values in it.