Strange exception table entry produced by Sun's javac
JavaJvmBytecodeJava Problem Overview
Given this program:
class Test {
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (NullPointerException npe) {
System.out.println("In catch");
} finally {
System.out.println("In finally");
}
}
}
Sun's javac
(v 1.6.0_24) produces the following bytecode:
public static void main(java.lang.String[]);
// Instantiate / throw NPE
0: new #2; // class NullPointerException
3: dup
4: invokespecial #3; // Method NullPointerException."<init>":()V
7: athrow
// Start of catch clause
8: astore_1
9: getstatic #4; // Field System.out
12: ldc #5; // "In catch"
14: invokevirtual #6; // Method PrintStream.println
17: getstatic #4; // Field System.out
// Inlined finally block
20: ldc #7; // String In finally
22: invokevirtual #6; // Method PrintStream.println
25: goto 39
// Finally block
// store "incomming" exception(?)
28: astore_2
29: getstatic #4; // Field System.out
32: ldc #7; // "In finally"
34: invokevirtual #6; // Method PrintStream.println
// rethrow "incomming" exception
37: aload_2
38: athrow
39: return
With the following exception table:
Exception table:
from to target type
0 8 8 Class NullPointerException
0 17 28 any
28 29 28 any
My question is: Why on earth does it include that last entry in the exception table?!
As I understand it, it basically says "if the astore_2
throws an exception, catch it, and retry the same instruction".
Such entry is produced even with empty try / catch / finally clauses such as
try {} catch (NullPointerException npe) {} finally {}
Some observations
- Eclipse compiler does not produce any such exception table entry
- The JVM spec does not document any runtime exceptions for the
astore
instruction. - I know that it is legal for the JVM to throw
VirtualMachineError
for any instruction. I guess the peculiar entry prevents any such errors from propagating out from that instruction.
Java Solutions
Solution 1 - Java
There are only two possible explanations: the compiler contains a bug or it's placing a kind of watermark for obscure reasons.
That entry is certainly bogus because any exception thrown by a finally block itself must send execution flow to outer exception handler or finally block, but never "run again" the same finally block.
Also, a good evidence that it's a bug/watermark, is the fact that Eclipse (and perhaps other Java compilers) are not generating such entry, and even so Eclipse-generated classes work fine on Sun's JVM.
That said, this post is interesting because it seems that the class file is valid and verified. If I were a JVM implementor, I would ignore that entry and fill a bug for Sun/Oracle!
Solution 2 - Java
Looking at the OpenJDK 7 source code, I would venture to guess the reason for
that last 28 29 28 any
exception table entry is because the code that
handles the astore
bytecode (see [code][genoop-cpp] starting at line 1871) can
throw an [java.lang.LinkageError
][linkage-err] exception if the popped value
from the operand stack is not a returnAddress
or reference
type (see the
Java Virtual Machine Specification for [astore][astore]) and they want this
error condition to show up on the stack trace.
In the event that there is a bad operand type on the operand stack, the JVM will
clear the operand stack (getting rid of that bad operand), put a LinkageError
on the operand stack, and execute the astore
bytecode again, this time
successfully executing the astore
bytecode using a JVM provided LinkageError
object reference. See the [athrow][athrow] documentation for more information.
I would greatly suspect the root cause of throwing a LinkageError
during
astore
processing is due to the [complexities JSR/RET subroutines]
[jsrcost-paper] introduce into bytecode verification (OpenJDK changes
[6878713][jsr-6878713], [6932496][jsr-6932496] and [7020373][jsr-7020373] are
recent evidence of JSR
's continued complexity; I'm sure Sun/Oracle has other
closed source tests that we're not seeing in OpenJDK). The OpenJDK [7020373]
[jsr-7020373] change uses LinkageError
to validate/invalidate test results.
[astore]: https://java.sun.com/docs/books/vmspec/2nd-edition/html/Instructions2.doc.html#astore "astore" [athrow]: https://java.sun.com/docs/books/vmspec/2nd-edition/html/Instructions2.doc.html#athrow "athrow" [genoop-cpp]: http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/9b0ca45cd756/src/share/vm/oops/generateOopMap.cpp "generateOopMap.cpp" [jsr-6878713]: http://hg.openjdk.java.net/jdk7/hotspot/hotspot/rev/4a9604cd7c5f "6878713: Verifier heap corruption, relating to backward jsrs" [jsr-6932496]: http://hg.openjdk.java.net/jdk7/hotspot/hotspot/rev/c466efa608d5 "6932496: c1: deoptimization of jsr subroutine fails on sparcv9" [jsr-7020373]: http://hg.openjdk.java.net/jdk7/hotspot/hotspot/rev/540930dc854d "7020373: JSR rewriting can overflow memory address size variables" [jsrcost-paper]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.104.7844 "The costs and benefits of Java bytecode subroutines" [linkage-err]: http://download.oracle.com/javase/6/docs/api/java/lang/LinkageError.html "LinkageError"
Solution 3 - Java
My understanding is that the second exception table entry is the implicit catch everything clause added by the compiler to cover any exceptions/errors thrown in the body or the catch handlers and the third entry is the guard on that implicit catch to force the flow through the finally execution.