7

I want to write a MethodVisitor that transforms LDC instructions that are for multiplication.

Example bytecode:

ldc #26
imul

This basically pushes a constant and then multiplies it.

It has to be a stateful transformation because I first have to check that it is for multiply and, if it is, I need to go back to the ldc instruction and modify the constant. I'm not entirely sure how I would go about this, and I don't know how to modify the constant (when I tried to pass a different value, the old value still remained in the constant pool).

Edit:

public class AdditionTransformer extends MethodAdapter {
    boolean replace = false;
    int operand = 0;

    AdditionTransformer(MethodVisitor mv) {
        super(mv);
    }

    @Override
    public void visitInsn(int opcode) {
        if (opcode == IMUL && replace) {
            operand *= 2;
            visitLdcInsn(operand);
            replace = false;
        }
        mv.visitInsn(opcode);
    }

    @Override
    public void visitLdcInsn(Object cst) {
        if (cst instanceof Integer && !replace) {
            operand = (Integer) cst;
            replace = true;
        } else {
            mv.visitLdcInsn(cst);
        }
    }
}

This is what I have, but it doesn't remove the old value in the constant pool, and it may have bugs.

someguy
  • 7,144
  • 12
  • 43
  • 57

2 Answers2

1

What you've got is just about right, but doesn't cater for other types of opcodes being called after the ldc, so you'll cause some breakage there, since they'll be looking for something on the stack that isn't there (since you didn't visit the ldc). I'm not so sure about removing the existing constant, but you can replace the constant like so:

@Override
public void visitInsn(int opcode) {
    if (opcode == IMUL && replace) {
        operand *= 2;
        mv.visitInsn(POP);
        mv.visitLdcInsn(operand);
        replace = false;
    }
    mv.visitInsn(opcode);
}

@Override
public void visitLdcInsn(Object cst) {
    if (cst instanceof Integer && !replace) {
        operand = (Integer) cst;
        replace = true;
    }
    mv.visitLdcInsn(cst);
}    

In other words, always visit the "ldc". If you then see an IMUL proceeding it, pop the stack, insert a new constant, and then visit the IMUL opcode. You'd need to do a bit of work to make this completely safe, in case some other method is visited after visiting ldc and before IMUL. To be paranoid, you could override all visitor methods, and if it's not visitInsn or not IMUL, you would visit the ldc and set replace = false.

Completely replacing the constant is a bit more tricky. You'd need to remember which constants have been seen by all methods visited in the class so far. If you haven't seen that constant so far, you can just replace the value when you visit the ldc.

axw
  • 6,908
  • 1
  • 24
  • 14
  • Thanks, this helped a bit, but your solution seems a little messy. It's passing the old value and the new, with a POP instruction in between. Are you sure this is the only way? 'And about replacing the constant: I thought that's what I was doing, but it isn't actually removing the old value from the constant pool. – someguy Dec 22 '10 at 14:45
  • @someguy You can store the operand, and defer calling visitLdcInsn. Just make sure you call visitLdcInsn if the next visitInsn(opcode) != IMUL, or the next visited method != visitInsn. The constant will only be removed if there's not another piece of code in the class referring to it. – axw Dec 22 '10 at 23:32
1

If you are interested in modifying bytecode in such a manner, you may want to look into the ASM tree API. You can easily replace LdcInsnNode.cst through a more comfortable DOM-style tree interface as opposed to the SAX-style visitor interface you are attempting to use.

obataku
  • 29,212
  • 3
  • 44
  • 57
  • I was keen on looking for a solution using the visitor API, since ASM made it pretty clear that it is recommended. However, if the tree API is the better choice in this case, I'll look into it. Thanks. – someguy Jan 27 '11 at 18:27
  • Using the visitor API as you are now, you can't just replace the constant in place; you'd have to add extra code to the flow to pop the old value and push a new one. Perhaps you should, however, look into subclassing ClassWriter; there are a few virtual methods you can override that deal with writing constants, although it might be a bit complex to verify you are modifying only the constant you intend to. – obataku Feb 05 '11 at 19:15