Search code examples
javabytecodejava-bytecode-asm

java.lang.VerifyError: Bad local variable type after bytecode instrumentation


I instrumented some Java bytecode. What I try to do is this:

InstrumentStackElem[] stack;
int stackpointer;
void foo(){
    stackpointer = (stackpointer + 1) % stack.length;
    InstrumentStackElem localref = stack[stackpointer];
    localref.init();

    //before each return
    localref.clear();
    stackpointer = (stackpointer -1 +stack.length) % stack.length;

So at the beginning of each method I increment the stackpointer and grab a local reference to the array element. Before each return, I call a clear method on the element and decrease the counter again.

When I try to access the field for the call of localref.clear() I always get a java.lang.VerifyError.

Example method:

public static boolean isValidName(final String s) {
        if (!s.matches("[_a-zA-Z][_a-zA-Z0-9]*")) {
            return false;
        }

        if (s.toCharArray()[0] == '_') { // this check should not be -> this is
                                            // a bug
            return false;
        }

        return true;
    }

Corresponding instrumented bytecode

 public static boolean isValidName(java.lang.String);
    Code:
       0: getstatic     #38                 // Field instrumentation_static_stackpointer:I
       3: iconst_1
       4: iadd
       5: bipush        16
       7: irem
       8: putstatic     #38                 // Field instrumentation_static_stackpointer:I
      11: getstatic     #40                 // Field instrumentation_static_stack:[Lme/instrumentor/InstrumentStackElem;
      14: getstatic     #38                 // Field instrumentation_static_stackpointer:I
      17: aaload
      18: astore_2
      19: aload_2
      20: bipush        -1
      22: ldc           #113                // String isValidName_Ljava_lang_String__Z
      24: invokevirtual #48                 // Method me/instrumentor/InstrumentStackElem.init:(ILjava/lang/String;)V
      27: aload_0
      28: ldc           #115                // String [_a-zA-Z][_a-zA-Z0-9]*
      30: invokevirtual #118                // Method java/lang/String.matches:(Ljava/lang/String;)Z
      33: ifne          56
      36: iconst_0
      37: aload_2
      38: invokevirtual #110                // Method me/instrumentor/InstrumentStackElem.clear:()V
      41: getstatic     #38                 // Field instrumentation_static_stackpointer:I
      44: iconst_1
      45: isub
      46: bipush        16
      48: iadd
      49: bipush        16
      51: irem
      52: putstatic     #38                 // Field instrumentation_static_stackpointer:I
      55: ireturn
      56: aload_0
      57: invokevirtual #122                // Method java/lang/String.toCharArray:()[C
      60: iconst_0
      61: caload
      62: bipush        95
      64: if_icmpne     87
      67: iconst_0
      68: aload_2
      69: invokevirtual #110                // Method me/instrumentor/InstrumentStackElem.clear:()V
      72: getstatic     #38                 // Field instrumentation_static_stackpointer:I
      75: iconst_1
      76: isub
      77: bipush        16
      79: iadd
      80: bipush        16
      82: irem
      83: putstatic     #38                 // Field instrumentation_static_stackpointer:I
      86: ireturn
      87: iconst_1
      88: aload_2
      89: invokevirtual #110                // Method me/instrumentor/InstrumentStackElem.clear:()V
      92: getstatic     #38                 // Field instrumentation_static_stackpointer:I
      95: iconst_1
      96: isub
      97: bipush        16
      99: iadd
     100: bipush        16
     102: irem
     103: putstatic     #38                 // Field instrumentation_static_stackpointer:I
     106: ireturn

This is the exact error thrown:

  Unexpected error: Lexer: java.lang.VerifyError: Bad local variable type
Exception Details:
  Location:
    lexer/Lexer.isValidName(Ljava/lang/String;)Z @68: aload_2
  Reason:
    Type top (current frame, locals[2]) is not assignable to reference type
  Current Frame:
    bci: @68
    flags: { }
    locals: { 'java/lang/String' }
    stack: { integer }
  Bytecode:
    0000000: b200 2604 6010 1070 b300 26b2 0028 b200
    0000010: 2632 4d2c 10ff 1271 b600 302a 1273 b600
    0000020: 769a 0017 032c b600 6eb2 0026 0464 1010
    0000030: 6010 1070 b300 26ac 2ab6 007a 0334 105f
    0000040: a000 1703 2cb6 006e b200 2604 6410 1060
    0000050: 1010 70b3 0026 ac04 2cb6 006e b200 2604
    0000060: 6410 1060 1010 70b3 0026 ac            
  Stackmap Table:
    same_frame(@56)
    same_frame(@87)

I use the asm library to instrument the methods and used this.method.visitLocalVariable("localstackref", "[Lme/instrumentor/InstrumentStackElem;",null, bl, el,this.stackelementindex); to add the local variable. stackelementindex in this case was 2.

Anybody can help me?


Solution

  • The generated bytecode is correct, except for the fact that you forgot to update the stackmap table.

    For reference, here's pseudocode for what your generated bytecode does.

    instrumentation_static_stackpointer = (instrumentation_static_stackpointer + 1) % 16
    a2 = instrumentation_static_stack[instrumentation_static_stackpointer]
    a2.init(-1, "isValidName_Ljava_lang_String__Z")
    if (!s.matches("[_a-zA-Z][_a-zA-Z0-9]*")) goto L56
    a2.clear()
    instrumentation_static_stackpointer = (instrumentation_static_stackpointer - 1 + 16) % 16
    return 0
    
    L56:
    if (s.toCharArray()[0] != 95) goto L87
    a2.clear()
    instrumentation_static_stackpointer = (instrumentation_static_stackpointer - 1 + 16) % 16
    return 0
    
    L87:
    instrumentation_static_stackpointer = (instrumentation_static_stackpointer - 1 + 16) % 16
    return 1
    

    The problem is that in bytecode version 51.0+, which is presumably what you are using, it is mandatory to include type information about the bytecode variables as well in the StackMapTable attribute. However you did not update this to include the type for your new variable a2. The stack map table is only consulted whenever there is a jump or jump target. So when it gets to L56, it consults the table to see what the incoming types are, and the table says that the only variable is s with type String. Then it gets to a2.clear(), sees that you are using a variable which didn't exist in the stack map table, and fails verification.