Search code examples
javajava-bytecode-asm

Modifiying <clinit> with ASM


Hello I have a little problem with ASM. It produces a class with bytecode error:

Exception in thread "main" java.lang.VerifyError: Bad instruction
Exception Details:
  Location:
    me/test/Main.<clinit>()V @0: wide
  Reason:
    Error exists in the bytecode

    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
    at java.lang.Class.privateGetMethodRecursive(Unknown Source)
    at java.lang.Class.getMethod0(Unknown Source)
    at java.lang.Class.getMethod(Unknown Source)
    at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
    at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

When i use the following code to inject code into the <clinit> method, which fills an static byte array with the "key" (byte[]) variable:

   if (key.length > 128)
        this.visitVarInsn(SIPUSH, key.length);
    else
        this.visitVarInsn(BIPUSH, key.length);
    this.visitVarInsn(NEWARRAY, 8);
    for (int i = 0; i < key.length; i++) {
        this.visitInsn(DUP);
        if (i > 127) {
            this.visitVarInsn(SIPUSH, i + 32768);
        } else if (i < 6) {
            switch (i) {
                case 0:
                    this.visitInsn(ICONST_0);
                    break;
                case 1:
                    this.visitInsn(ICONST_1);
                    break;
                case 2:
                    this.visitInsn(ICONST_2);
                    break;
                case 3:
                    this.visitInsn(ICONST_3);
                    break;
                case 4:
                    this.visitInsn(ICONST_4);
                    break;
                case 5:
                    this.visitInsn(ICONST_5);
                    break;
                default:
                    System.out.println("Logic mistake!");
                    break;
            }
        }
        else {
            this.visitVarInsn(BIPUSH, i);
        }
        if (key[i] < 6 && key[i] >= 0) {
            switch (key[i]) {
                case 0:
                    this.visitInsn(ICONST_0);
                    break;
                case 1:
                    this.visitInsn(ICONST_1);
                    break;
                case 2:
                    this.visitInsn(ICONST_2);
                    break;
                case 3:
                    this.visitInsn(ICONST_3);
                    break;
                case 4:
                    this.visitInsn(ICONST_4);
                    break;
                case 5:
                    this.visitInsn(ICONST_5);
                    break;
                default:
                    System.out.println("Logic mistake!");
                    break;
            }
        } else {
            this.visitVarInsn(BIPUSH, key[i]);
        }
        this.visitInsn(BASTORE);

Solution

  • The main cause for your incorrect code is your multiple misuse of visitVarInsn.

    visitVarInsn is designed for instructions whose argument is the index of a local variable. Since it depends on the particular instruction, how the argument is encoded into the instruction, this method produces incorrect code when used together with instructions, it is not designed for. So, instead of

    this.visitVarInsn(NEWARRAY, 8);
    

    you should use

    this.visitIntInsn(NEWARRAY, Opcodes.T_BYTE);
    

    The same applies to the incorrect invocations of this.visitVarInsn(SIPUSH, key.length); and this.visitVarInsn(BIPUSH, key.length);, but in these cases there are additional errors in your duplicated code, e.g. right your first occurrence,

    if (key.length > 128)
        this.visitVarInsn(SIPUSH, key.length);
    else
        this.visitVarInsn(BIPUSH, key.length);
    

    will break, when the constant value is exactly 128. Further, this.visitVarInsn(SIPUSH, i + 32768); contains a spurious + 32768 additionally to the use of the wrong method.

    Generally, you should not repeat this code for generating an optimal instruction. Do either, create a utility method whose correctness you have to prove only once, or use an existing method. E.g., if you inherit from InstructionAdapter rather than directly from MethodVisitor, you can use iconst(int) which will automatically generate ICONST_x, BIPUSH, SIPUSH or LDC. Or, when you inherit from GeneratorAdapter instead, you can use push(int) to achieve the same.

    So using GeneratorAdapter, the code would be as simple as

    push(key.length);
    newArray(Type.BYTE_TYPE);
    for(int i = 0; i < key.length; i++) {
        this.visitInsn(Opcodes.DUP);
        push(i);
        push(key[i]);
        visitInsn(Opcodes.BASTORE);
    }