AndrewBourgeois AndrewBourgeois - 3 months ago 12
Java Question

Documentation on line numbers in stack traces

Is there any clear documentation on Java stack trace line numbers?

How are they "calculated" when printing a stack-trace (the logic behind it, not the implementaton)?

To show you why I'm confused, take the following code snippet:

public static void main(String[] args) {
String evilString = null;
System.out.println(new StringBuilder()
.append(evilString.toLowerCase()));
evilString.toUpperCase();
}


It gives:
Exception in thread "main" java.lang.NullPointerException
at be.company.training.ocjp6.App.main(App.java:28)

While the following piece of code:

public static void main(String[] args) {
String evilString = null;
System.out.println(new StringBuilder()
.append("".toLowerCase()));
evilString.toUpperCase();
}


Gives:
Exception in thread "main" java.lang.NullPointerException
at be.company.training.ocjp6.App.main(App.java:30)

So I understand that running the chain of
StringBuilder
methods makes it being treated as 1 line (the StringBuilder code starts at line 28 in my editor). But if the error happens in the evilString.toUpperCase() snippet, we're back on track with line 30.

I want to know so that when I see a stacktrace, I can know for sure on what line the error happened (chaining methods (on multiple lines) is very common in the code I'm looking at).

Answer

Seems like this is compiler dependent as @kdgregory noted.

Here's my java -version:

java version "1.6.0_29"
Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-10M3527)
Java HotSpot(TM) Client VM (build 20.4-b02-402, mixed mode)

With this code, I NPE on line 9 per the stack trace (which matches the physical line number in the source)

public static void main(String[] args) { 
    String evilString = null;
    System.out.println(new StringBuilder()
        .append(
                evilString.toLowerCase()));  // <--- NPE here (line 9)
}

So I dug a little deeper using javap:

This is the line number table for main as shown by javap -l

(line number table shows source line: instruction-offset

public static void main(java.lang.String[]);
LineNumberTable: 
line 6: 0
line 7: 2
line 9: 12
line 8: 16
line 7: 19
line 10: 22

source line 9 starts at offset 12.

javap -c to disassemble shows this:

public static void main(java.lang.String[]);
Code:
0:  aconst_null
1:  astore_1
2:  getstatic   #16; //Field java/lang/System.out:Ljava/io/PrintStream;
5:  new #22; //class java/lang/StringBuilder
8:  dup
9:  invokespecial   #24; //Method java/lang/StringBuilder."<init>":()V
12: aload_1                                                    <--- closest line in the line number table
13: invokevirtual   #25; //Method java/lang/String.toLowerCase:()Ljava/lang/String;       <--- NPE here
16: invokevirtual   #31; //Method java/lang/StringBuilder.append:      (Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual   #35; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
22: return

my guess: when the exception is hit at the invokevirtual at offset 13, the jvm looks up the closest prior entry in the line number table and puts that in the stack trace.

Comments