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:
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.
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?
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
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.