Search code examples
javaclassbytecode

Java LineNumberTable: Entry explanation


If I disassemble my class file, I get LineNumberTables of the form

  LineNumberTable:
    line 204: 0
    line 205: 9
    line 208: 57
    line 209: 63
    line 210: 72
    line 211: 75
    line 212: 78
    line 213: 87
    line 216: 90
    line 218: 118
    line 221: 126
    line 222: 131
    line 223: 138
    line 224: 143
    line 227: 150
    line 230: 157
    line 231: 160
    line 232: 170
    line 235: 194
    line 237: 228
    line 240: 249
    line 241: 259
    line 243: 266
    line 245: 269
    line 246: 292
    line 248: 295
    line 249: 301
    line 250: 308
    line 251: 315
    line 252: 322
    line 253: 329

I know that these tables contain debug information, and that the first entry is some kind of position in the class-file, while the second one is a position in the source code. I would like to know:

  1. Are the source code line numbers relative or absolute? If I interpret them absolutely, some point to the middle of multi-line comments which seems to be strange.

  2. Two different compilations of the same source code differ only in one byte: The entry "line 216: 90" is replaced by "line 215: 90". I try to find out what could be the cause for this. Any idea?


Solution

  • Apply common sense to what you are reading. While you read the spec correctly, i.e. the first number stored in the LineNumberTable attribute’s array is a byte code offset and the second one is a line number, that doesn’t imply that the Disassembler you are using will also print them in that order.

    There are two indicators that the order has been swapped

    1. There is the word “Line” printed before the first number, suggesting that this first number is a line number
    2. The first number ranges from 204 to 253, which is reasonable for source code lines of a method somewhere in a class declaration, whereas the second number ranges from 0 to 329, which is reasonable for byte code offsets within a method, which start by zero.

      In contrast, it’s unlikely to see line numbers starting with zero for a method, as source code usually starts with package and import declarations. And it would also be unusual, if the first 203 bytes of of a method’s code had no associated source code lines (though that wouldn’t be impossible).

    Both indicators together are quite strong. Then, the observed change is quite plausible. Obviously, the generated code didn’t change. But since there is no standard about how the line numbers and the generated code are associated, there might be subtle differences, depending on the compiler version, e.g. when an expression spans multiple lines, but generates only one instruction or when the compiler tries to avoid too excessive line number tables.

    E.g. the code

    foo(
    );
    

    generates only one instruction (if foo() is static) and it’s not defined, which of the two lines to associate with that instruction. When it is an instance method, it will consist of two instructions, but representing them as being in different line numbers would be debatable, as stepping in-between during debugging would not be much helpful. But it’s the compiler’s decision. Also with

    foo(
        null,
        1,
        true
    );
    

    pushing each of the constant arguments to the stack requires one byte in the instruction sequence, whereas associating a distinct line number to each of the instructions would require another four bytes per instruction. Since pushing these constants is unlikely to fail, thus, tracing them would make little sense, compilers may decide to associate the entire sequence with the sole line number of the invocation instruction. Since this decision depends on the actual compiler and perhaps even on its current configuration, recompiling may change the association.

    Another difference is how compilers handle synthetic methods, like bridge methods and inner class accessors. I’ve seen so far, them being associated with just zero, with the beginning of the surrounding class declaration, and with the beginning of the actual target member to which they delegate.