Faced this java.lang.VerifyError with code snippet as below during JVM loading bytecode.
try{
-----
} catch (NumberFormatException|CalculationException e) {
}
Here CalculationException is custom exception which extends java.lang.RuntimeException, while NumberFormatException is standard Java RuntimeException.
While the code compile and run fine locally windows machine.
It fails with VerifyError on one of the QA/prod/Dev unix node, and works fine on other unix node. While both unix nodes have the same config (using RedHat 6.2 and 1.8 jdk and same version jar files) also compared the bytecode generated on both nodes by javap -c and found this same.
I found two ways to resolve this on erroneous node also.
1) As this error is coming at byte-code verification step, tried by disabling the bytecode verification on dev unix box as -Xverify:none (Also tried -XX:-UseSplitVerifier but dint work, as i think its disabled from jdk 8)
However as we shall not disable bytecode verification in prod, have been looking for some other workaround.
2) Another workaround is by using the parent exception: RuntimeException in catch block instead of combining two exception.
What I am not able to understand if Java does have issue with this way of catching, why compiler dint complained it and why it works on one machine and not on other which have same config.
Also the error reason is not making sense which says: CalculationException (current frame, stack[0]) is not assignable to 'java/lang/RuntimeException
While its actually assignable as tested by
if (RuntimeException.class.isAssignableFrom(CalculationException.class)){
System.out.println("Assisgnable");
}
Full Exception Details:
Location:
com/markit/valuations/marketdata/snapper/domain/credit/BeanWrapperBuilder_CDXOCompositeVolSurface.getSpreadVol(Lcom/markit/valuations/dates/ImmutableDate;Lcom/markit/valuations/marketdata/data/indexeddata/IndexedData;DLcom/markit/valuations/dates/ImmutableDate;Lcom/markit/valuations/dates/ImmutableDate;Ljava/lang/String;Ljava/lang/String;Lcom/markit/qag/analytics/credit/indexpv/swaption/CreditIndexSwaptionCalculator;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Double; #51: astore
Reason:
Type 'com/markit/valuations/common/CalculationException' (current frame, stack[0]) is not assignable to 'java/lang/RuntimeException' (stack map, stack[0])
Current Frame:
bci: #0
flags: { }
locals: { 'com/markit/valuations/marketdata/snapper/domain/credit/BeanWrapperBuilder_CDXOCompositeVolSurface', 'com/markit/valuations/dates/ImmutableDate', 'com/markit/valuations/marketdata/data/indexeddata/IndexedData', double, double_2nd, 'com/markit/valuations/dates/ImmutableDate', 'com/markit/valuations/dates/ImmutableDate', 'java/lang/String', 'java/lang/String', 'com/markit/qag/analytics/credit/indexpv/swaption/CreditIndexSwaptionCalculator', 'java/lang/String', 'java/lang/String' }
stack: { 'com/markit/valuations/common/CalculationException' }
Stackmap Frame:
bci: #51
flags: { }
locals: { 'com/markit/valuations/marketdata/snapper/domain/credit/BeanWrapperBuilder_CDXOCompositeVolSurface', 'com/markit/valuations/dates/ImmutableDate', 'com/markit/valuations/marketdata/data/indexeddata/IndexedData', double, double_2nd, 'com/markit/valuations/dates/ImmutableDate', 'com/markit/valuations/dates/ImmutableDate', 'java/lang/String', 'java/lang/String', 'com/markit/qag/analytics/credit/indexpv/swaption/CreditIndexSwaptionCalculator', 'java/lang/String', 'java/lang/String' }
stack: { 'java/lang/RuntimeException' }
Bytecode:
0x0000000: 2c19 0ab9 0015 0200 b800 cb2b 1906 ba00
0x0000010: cc00 00b6 00cd ba00 ce00 00b6 00cf 1909
0x0000020: ba00 d000 00b6 00cf 0eb8 003b b600 d1c0
0x0000030: 0091 b03a 0cbb 0048 59b7 0049 12d3 b600
0x0000040: 4b19 0ab6 004b 12d4 b600 4b2c 1254 b900
0x0000050: 1502 00b6 004b 12d5 b600 4b29 b600 4c12
0x0000060: d6b6 004b 1907 b600 4b12 d7b6 004b 1905
0x0000070: b600 5b12 d8b6 004b 1906 b600 5b12 d9b6
0x0000080: 004b 190b b600 4b12 dab6 004b 1908 b600
0x0000090: 4bb6 004d 3a0d b200 4719 0d19 0cb9 0081
0x00000a0: 0300 0eb8 003b b0
Exception Handler Table:
bci [0, 50] => handler: 51
bci [0, 50] => handler: 51
Stackmap Table:
same_locals_1_stack_item_frame(#51,Object[#535])
Cause
This happened to me when there were conflicting versions of the same library (Jar) in my dependencies. To be more specific, I was importing different versions of Jackson library v2.9.10 and v2.11.0.
Troubleshooting
Start your application in verbose mode and make java log all the classes it is loading to see where the conflicting class is coming from. This can be done by passing the flag -verbose:class
Fix
Remove conflicting versions of dependencies and make sure all related dependencies/libraries are of the same major version (at the least) and preferably the same minor version too.
Remove dependencies that transitively import conflicting version of direct dependencies.
If you are not seeing any conflicting dependencies even after using mvn dependency:tree or Maven Helper plugins, the conflicting dependency could have been imported by one of the libraries' classpath. I know this sounds weird, since only applications should have classpath, but if someone added <addClasspath>true</addClasspath> in their library pom file, it could lead to this.
The dependency crash is the cause as Aditya pointed out. If you are using SBT, here is how you can resolve it.
First, add the dependency graph to your global plugins ~/.sbt/1.0/plugins/plugins.sbt:
SBT 1.4+: add
addDependencyTreePlugin
SBT <1.3: add
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
Then in your project run
sbt "whatDependsOn com.fasterxml.jackson.core jackson-databind"
were com.fasterxml.jackson.core is the organization and jackson-databind is the artifact name of conflicting library. Mine also was because play-json is using 2.10.4 and Confluent packages use 2.9.9. You have to find yours based on the error messages that you received.
Then from this point forward, you need to make a decision on how to resolve it. Upgrade packages, or downgrade, or exclude libraries. In my scenario, because play-json was transitive dependency from a library that was not really critical in my application, I picked the easiest path and excluded 2.10.4 from play-json library.
you need to set the following jvm args in your config:
-XX:-UseSplitVerifier
The follow code compiles but causes a java.lang.VerifyError. The error occurs even if the run() method is not executed.
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class TestCase {
public static void main(String[] args) {
new TestCase().run();
}
public void run() {
class Inner {
}
Map<String, Inner> map = new HashMap<>();
Function<String, Inner> function = (name) -> {
Inner i = map.get(name);
if (i == null) {
i = new Inner();
map.put(name, i);
}
return i;
};
function.apply("test");
}
}
The Error:
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
TestCase.lambda$0(Ljava/util/Map;Ljava/lang/String;)LTestCase$1Inner; #20: invokespecial
Reason:
Type 'java/util/Map' (current frame, stack[2]) is not assignable to 'TestCase'
Current Frame:
bci: #20
flags: { }
locals: { 'java/util/Map', 'java/lang/String', 'TestCase$1Inner' }
stack: { uninitialized 15, uninitialized 15, 'java/util/Map' }
Bytecode:
0000000: 2a2b b900 2d02 00c0 0032 4d2c c700 15bb
0000010: 0032 592a b700 344d 2a2b 2cb9 0037 0300
0000020: 572c b0
Stackmap Table:
append_frame(#33,Object[#50])
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2688)
at java.lang.Class.getMethod0(Class.java:2937)
at java.lang.Class.getMethod(Class.java:1771)
at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
However, if I move the 'Inner' class to be an inner class of TestCase (instead of declared in a method), the error goes away. Or, if I use an anonymous class to define the Function, the error goes away. It seems to be an issue with a class declared in the method and the use of a lamba.
Is this a JVM bug? Or am I missing something? I am using Oracle's Java 8. The error happens both on the command line and within Eclipse 4.4.
EDIT:
I upgraded to the latest JDK:
java version "1.8.0_11"
Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)
When compile via javac manually and run it works fine. If I run the class compiled by Eclipse, it doesn't. So now I suspect that the Eclipse compiler has a bug.
verify error is thrown when your compiler generates a code that is not verifiable by verifier.
when the "verifier" detects that a class file, though well formed, contains some sort of internal inconsistency or security problem. So it is clearly an issue with your eclipse compiler as you have suggested. it is unable to properly compile these constructs.
I 'm trying to use -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*MyClass.myMethod command lines as described in this post.
It seems thats it's available with open-jdk (https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly).
How can I use those options (or similar equivalents) with oracle JDK7 and the JVM HotSpot?
These instructions apply to Linux (Ubuntu 10.04.4 LTS), but should be applicable for your OS. After downloading Oracle JDK 7u3 and appropriately setting your JAVA_HOME and PATH environment variables, execute the following to check available options:
java -XX:+AggressiveOpts -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+PrintFlagsFinal -version
You should see the UnlockDiagnosticVMOptions, CompileCommand and PrintAssembly options are available. Using the CompileCommand option will also enable the PrintAssembly option. However, you will need the HotSpot disassembler plugin for PrintAssembly to work; without it, you might see something like the following:
$ java -version
java version "1.7.0_03"
Java(TM) SE Runtime Environment (build 1.7.0_03-b04)
Java HotSpot(TM) Server VM (build 22.1-b02, mixed mode)
$ java -server -XX:+UnlockDiagnosticVMOptions '-XX:CompileCommand=print,*Main.main' Main
CompilerOracle: print *Main.main
Java HotSpot(TM) Server VM warning: printing of assembly code is enabled; turning on DebugNonSafepoints to gain additional output
Compiled method (c2) 68 1 % Main::main # 4 (49 bytes)
total in heap [0xb3a97548,0xb3a979ec] = 1188
relocation [0xb3a97610,0xb3a97624] = 20
main code [0xb3a97640,0xb3a97840] = 512
stub code [0xb3a97840,0xb3a97850] = 16
oops [0xb3a97850,0xb3a97858] = 8
scopes data [0xb3a97858,0xb3a97898] = 64
scopes pcs [0xb3a97898,0xb3a979e8] = 336
dependencies [0xb3a979e8,0xb3a979ec] = 4
Could not load hsdis-i386.so; library not loadable; PrintAssembly is disabled
OopMapSet contains 1 OopMaps
To get the HotSpot disassembler plugin, you will need to build it. Looking at the OpenJDK 7u2 source, the hsdis plugin readme says:
To use the plugin with a JVM, you need a new version that can load it.
If the product mode of your JVM does not accept -XX:+PrintAssembly,
you do not have a version that is new enough.
To build this project you [need] a copy of GNU binutils to build against.
In theory this should be buildable on Windows but getting a working
GNU build environment on Windows has proven difficult.
We have confirmed above that Oracle JDK 7u3 supports PrintAssembly. I followed the hsdis plugin readme instructions, downloaded GNU binutils 2.22, placed it in the hsdis build/binutils directory and ran make. This eventually produced the following error:
hsdis.c:32:20: error: sysdep.h: No such file or directory
To correct this, I changed hsdis.c using the following patch:
diff -r 6259c6d3bbb7 src/share/tools/hsdis/hsdis.c
--- a/src/share/tools/hsdis/hsdis.c Mon Dec 12 23:08:01 2011 -0800
+++ b/src/share/tools/hsdis/hsdis.c Thu Feb 23 09:26:37 2012 -0500
## -29,7 +29,7 ##
#include "hsdis.h"
-#include <sysdep.h>
+#include <errno.h>
#include <libiberty.h>
#include <bfd.h>
#include <dis-asm.h>
Running make was then successful. Now just copy the hsdis-i386.so plugin in the hsdis build directory to the Oracle JDK 7u3 jre/lib/i386 directory.
Now you can see the disassembled compiled code:
$ java -server -XX:+UnlockDiagnosticVMOptions '-XX:CompileCommand=print,*Main.main' Main
CompilerOracle: print *Main.main
Java HotSpot(TM) Server VM warning: printing of assembly code is enabled; turning on DebugNonSafepoints to gain additional output
Compiled method (c2) 68 1 % Main::main # 4 (49 bytes)
total in heap [0xb3999548,0xb39999ec] = 1188
relocation [0xb3999610,0xb3999624] = 20
main code [0xb3999640,0xb3999840] = 512
stub code [0xb3999840,0xb3999850] = 16
oops [0xb3999850,0xb3999858] = 8
scopes data [0xb3999858,0xb3999898] = 64
scopes pcs [0xb3999898,0xb39999e8] = 336
dependencies [0xb39999e8,0xb39999ec] = 4
Loaded disassembler from [snip]/jdk1.7.0_03/jre/lib/i386/hsdis-i386.so
Decoding compiled method 0xb3999548:
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'main' '([Ljava/lang/String;)V' in 'Main'
0xb3999640: call 0xb6ff8510 ; {runtime_call}
0xb3999645: data32 xchg %ax,%ax
0xb3999648: mov %eax,-0x3000(%esp)
0xb399964f: push %ebp
0xb3999650: sub $0x38,%esp
0xb3999656: mov %ecx,%esi
0xb3999658: mov 0x4(%esi),%ebp
0xb399965b: mov 0x8(%esi),%edi
0xb399965e: mov (%ecx),%esi
0xb3999660: mov %ecx,(%esp)
0xb3999663: call 0xb7078cf0 ;*iload_3
[snip]
0xb399983e: hlt
0xb399983f: hlt
[Exception Handler]
[Stub Code]
0xb3999840: jmp 0xb39981e0 ; {no_reloc}
[Deopt Handler Code]
0xb3999845: push $0xb3999845 ; {section_word}
0xb399984a: jmp 0xb397e220 ; {runtime_call}
0xb399984f: .byte 0x0
OopMapSet contains 1 OopMaps
#0
OopMap{off=468}
The test class I've used is:
public class Main {
public static void main(final String[] args) {
long x = 0;
for (int i = 0; i < 1000000; i++) {
x += calculate(i);
}
System.out.println("x=" + x);
}
private static long calculate(final int i) {
return (long)i * (long)i;
}
}
in my case to see the disassembled compiled code:
$ java -XX:CompileThreshold=1 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand="compileonly pac/kage/MyClass myMethod" MyClass
in example above has is cycle: for (int i = 0; i < 1 000 000; i++) {...},
that is why in our case without 1 000 000 iterations we need -XX:CompileThreshold=1 option (by default 10 000 for -server) to see our disassembled compiled code.
public class Strange1 {
public static void main(String[] args) {
try {
Missing m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
public class Strange2 {
public static void main(String[] args) {
Missing m;
try {
m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
class Missing {
Missing() { }
}
If you run Strange1 and Strange2 after deleting Missing.class, Strange1 will throw NoClassDefFoundError; but Strange2 will print Got it!
Can anyone explain that? Thanks.
updated:
java bytecode for Strange1 :
0 new info.liuxuan.test.Missing [16]
3 dup
4 invokespecial info.liuxuan.test.Missing() [18]
7 astore_1 [m]
8 goto 20
11 astore_1 [ex]
12 getstatic java.lang.System.out : java.io.PrintStream [19]
15 ldc <String "Got it!"> [25]
17 invokevirtual java.io.PrintStream.println(java.lang.String) : void [27]
20 return
Exception Table:
[pc: 0, pc: 8] -> 11 when : java.lang.NoClassDefFoundError
Line numbers:
[pc: 0, line: 14]
[pc: 11, line: 15]
[pc: 12, line: 16]
[pc: 20, line: 18]
Local variable table:
[pc: 0, pc: 21] local: args index: 0 type: java.lang.String[]
[pc: 8, pc: 11] local: m index: 1 type: info.liuxuan.test.Missing
[pc: 12, pc: 20] local: ex index: 1 type: java.lang.NoClassDefFoundError
java bytecode for Strange2 :
0 new info.liuxuan.test.Missing [16]
3 dup
4 invokespecial info.liuxuan.test.Missing() [18]
7 astore_1 [m]
8 goto 20
11 astore_2 [ex]
12 getstatic java.lang.System.out : java.io.PrintStream [19]
15 ldc <String "Got it!"> [25]
17 invokevirtual java.io.PrintStream.println(java.lang.String) : void [27]
20 return
Exception Table:
[pc: 0, pc: 8] -> 11 when : java.lang.NoClassDefFoundError
Line numbers:
[pc: 0, line: 15]
[pc: 11, line: 16]
[pc: 12, line: 17]
[pc: 20, line: 19]
Local variable table:
[pc: 0, pc: 21] local: args index: 0 type: java.lang.String[]
[pc: 8, pc: 11] local: m index: 1 type: info.liuxuan.test.Missing
[pc: 12, pc: 20] local: ex index: 2 type: java.lang.NoClassDefFoundError
There is only one place is different:
11 astore_1 [ex]
and
11 astore_2 [ex]
updated again:
Everyone can try it in eclipse.
Prior to saying anything, i doub't this code won't even compile. because when compiler cannot find a class (Since its deleted). may be you are getting an error when trying to compile it using javac command. if thats the case its pretty normal and in no way its weird.
and let me add an another point.. check your imports, to contain Missing class. if it is there then remove it. and tell us whats happening.
I created two java files. Strange1.java contained classes Strange1 and Missing. Strange2.java contained Strange2 class. I removed Missing.class.
I got "Got it!" from both.
Please see the following details:
manohar#manohar-natty:~$ java -version
java version "1.6.0_25"
Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
Java HotSpot(TM) Server VM (build 20.0-b11, mixed mode)
manohar#manohar-natty:~$ gedit Strange1.java
manohar#manohar-natty:~$ gedit Strange2.java
manohar#manohar-natty:~$ javac Strange1.java
manohar#manohar-natty:~$ javac Strange2.java
manohar#manohar-natty:~$ java Strange1
manohar#manohar-natty:~$ java Strange2
manohar#manohar-natty:~$ rm Missing.class
manohar#manohar-natty:~$ java Strange1
Got it!
manohar#manohar-natty:~$ java Strange2
Got it!
I executed it in Ubuntu 11.04 linux machine.
So it might be the java's version that you are using.
NoClassDefFoundError is thrown whenever the first reference(declaring or creating an instance) to the missing class is made. Now, throwing an error or catching it depends on whether you use try-catch block for your first reference or not.
Output
The behavior of both programs depends on the version of javac used to compile them and not the version of java used to run the compiled classes. However, it's easier to use the same javac and java versions.
We'll be using J2SE 5.0 and Java SE 6 because those are the earliest versions where the programs' behavior deviates.
With build 1.5.0_22-b03:
$ jdk1.5.0_22/bin/javac {Strange1,Strange2,Missing}.java
$ rm Missing.class
$ jdk1.5.0_22/bin/java Strange1
Exception in thread "main" java.lang.NoClassDefFoundError: Missing
$ jdk1.5.0_22/bin/java Strange2
Got it!
$ jdk1.5.0_22/bin/javap -c Strange1
Compiled from "Strange1.java"
public class Strange1 extends java.lang.Object{
public Strange1();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2; //class Missing
3: dup
4: invokespecial #3; //Method Missing."<init>":()V
7: astore_1
8: goto 20
11: astore_1
12: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #6; //String Got it!
17: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
Exception table:
from to target type
0 8 11 Class java/lang/NoClassDefFoundError
}
Compiled from "Strange2.java"
public class Strange2 extends java.lang.Object{
public Strange2();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2; //class Missing
3: dup
4: invokespecial #3; //Method Missing."<init>":()V
7: astore_1
8: goto 20
11: astore_2
12: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #6; //String Got it!
17: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
Exception table:
from to target type
0 8 11 Class java/lang/NoClassDefFoundError
}
With build 1.6.0_45-b06:
$ jdk1.6.0_45/bin/javac {Strange1,Strange2,Missing}.java
$ rm Missing.class
$ jdk1.6.0_45/bin/java Strange1
Got it!
$ jdk1.6.0_45/bin/java Strange2
Got it!
$ jdk1.6.0_45/bin/javap -c Strange1
<Same output as the corresponding command for J2SE 5.0>
$ jdk1.6.0_45/bin/javap -c Strange2
<Same output as the corresponding command for J2SE 5.0>
Analysis
The bytecode of Strange1 and Strange2 is nearly identical, except for the mapping of the catch parameter ex to a VM local variable. Strange1 stores it in VM variable 1, i.e., 11: astore_1. Strange2 stores it in VM variable 2, i.e., 11: astore_2.
In both classes, the local variable m is stored in VM variable 1. Both versions of main also have a merge point where the flow of control from two different code paths converge. The merge point is 20: return. It can be reached either by completing the try block normally, i.e., 8: goto 20, or by completing the catch block and falling through from instruction 17.
The existence of the merge point causes an exception during the verification of class Strange1, but not class Strange2 in J2SE 5.0.
JLS, Java SE 6 Edition - Chapter 12 - Execution specifies activities that occur during execution of a program:
The Java Virtual Machine starts up by loading a specified class and then invoking the method main in this specified class. Section §12.1 outlines the loading, linking, and initialization steps involved in executing main, as an introduction to the concepts in this chapter. Further sections specify the details of loading (§12.2), linking (§12.3), and initialization (§12.4).
JLS, Java SE 6 Edition - Section 12.3 - Linking of Classes and Interfaces specifies that the first activity that is involved in linking is verification.
JVMS, Java SE 7 Edition - Section 4.10 - Verification of class Files specifies the two strategies that a VM may use for verification:
There are two strategies that Java Virtual Machine implementations may
use for verification:
Verification by type checking must be used to verify class files whose version number is greater than or equal to 50.0.
Verification by type inference must be supported by all Java Virtual Machine implementations, except those conforming to the Java
ME CLDC and Java Card profiles, in order to verify class files whose
version number is less than 50.0.
Verification on Java Virtual Machine implementations supporting the Java ME CLDC and Java Card profiles is governed by their
respective specifications.
(Verification by type checking was added to JVMS, Second Edition for Java SE 6, and JVMS, Java SE 7 Edition incorporates these changes in a more accessible way.)
Verification by type inference (for class files whose version number is less than 50.0, i.e., Java SE 6)
To merge two local variable array states, corresponding pairs of local
variables are compared. If the two types are not identical, then
unless both contain reference values, the verifier records that the
local variable contains an unusable value. If both of the pair of
local variables contain reference values, the merged state contains a
reference to an instance of the first common superclass of the two
types.
JVMS, Second Edition - Section 4.9.2 - The Bytecode Verifier
When instruction 20 is reached from instruction 8 in Strange1.main, VM variable 1 contains an instance of the class Missing. When reached from instruction 17, it contains an instance of the class NoClassDefFoundError.
Because Missing.class has been deleted, the verifier can't load it to determine the first common superclass, and throws a NoClassDefFoundError. Note that there is no stack trace printed for the uncaught exception because it's thrown during verification, before class initialization and long before main begins execution.
Verification by type checking (for class files whose version number is greater than or equal to 50.0, i.e., Java SE 6)
(I've tried my best to follow the rules as precisely as possible. However, they are complex, dense and new to me. Hence, if you spot any mistakes, please feel free to correct them. If you could summarize the rules, that'd be great too.)
Because of the StackMapTable attribute, it's not necessary for the verifier to compute the first common superclass to merge the two VM variable 1's types as in the other strategy. Also, there's no need to actually load classes Missing, NoClassDefFoundError, or any other classes other than Strange1 for verification thanks to the constant pool and how the rules work.
Hence, there are no exceptions during verification. If you modify the catch block to print out the exception's stack trace, you'll see that the exception is thrown during the execution of Strange1.main with a proper stack trace:
# Modify Strange1.main's catch block to print out the exception's stack trace
$ jdk1.6.0_45/bin/javac {Strange1,Missing}.java
$ rm Missing.class
$ jdk1.6.0_45/bin/java Strange1
java.lang.NoClassDefFoundError: Missing
at Strange1.main(Strange1.java:4)
Caused by: java.lang.ClassNotFoundException: Missing
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
... 1 more
Got it!
The StackMapTable of Strange1:
$ jdk1.6.0_45/bin/javap -verbose Strange1 | tail
line 10: 20
StackMapTable: number_of_entries = 2
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/NoClassDefFoundError ]
frame_type = 8 /* same */
}
Let's go through the rules (as the verifier) to prove that Strange1.main is type safe. Each instruction of Strange1.main is verified to be type safe and satisfies all applicable (exception) handlers:
methodWithCodeIsTypeSafe(Strange1Class, MainMethod) :-
parseCodeAttribute(Strange1Class, MainMethod, FrameSize, MaxStack,
ParsedCode, Handlers, StackMap),
mergeStackMapAndCode(StackMap, ParsedCode, MergedCode),
methodInitialStackFrame(Strange1Class, MainMethod, FrameSize, StackFrame, ReturnType),
Environment = environment(Strange1Class, MainMethod, ReturnType, MergedCode,
MaxStack, Handlers),
handlersAreLegal(Environment),
mergedCodeIsTypeSafe(Environment, MergedCode, StackFrame).
mergedCodeIsTypeSafe(Environment, [instruction(Offset, Parse) | MoreCode],
frame(Locals, OperandStack, Flags)) :-
instructionIsTypeSafe(Parse, Environment, Offset,
frame(Locals, OperandStack, Flags),
NextStackFrame, ExceptionStackFrame),
instructionSatisfiesHandlers(Environment, Offset, ExceptionStackFrame),
mergedCodeIsTypeSafe(Environment, MoreCode, NextStackFrame).
mergedCodeIsTypeSafe(Environment, [stackMap(Offset, MapFrame) | MoreCode],
afterGoto) :-
mergedCodeIsTypeSafe(Environment, MoreCode, MapFrame).
instructionSatisfiesHandlers(Environment, Offset, ExceptionStackFrame) :-
exceptionHandlers(Environment, Handlers),
sublist(isApplicableHandler(Offset), Handlers, ApplicableHandlers),
checklist(instructionSatisfiesHandler(Environment, ExceptionStackFrame),
ApplicableHandlers).
instructionSatisfiesHandler(Environment, StackFrame, Handler) :-
...
/* The stack consists of just the exception. */
StackFrame = frame(Locals, _, Flags),
ExcStackFrame = frame(Locals, [ ExceptionClass ], Flags),
operandStackHasLegalLength(Environment, ExcStackFrame),
targetIsTypeSafe(Environment, ExcStackFrame, Target).
targetIsTypeSafe(Environment, StackFrame, Target) :-
offsetStackFrame(Environment, Target, Frame),
frameIsAssignable(StackFrame, Frame).
Instructions 0-7 are type safe because instructionIsTypeSafe is true and frameIsAssignable(Environment, frame(Locals, [ NoClassDefFoundErrorClass ], Flags), frame(Locals, [ NoClassDefFoundErrorClass ], Flags)) is true for each of them thanks to the first stack map frame for the (exception) handler target at instruction 19 (= 75 - 64):
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/NoClassDefFoundError ]
Instructions 12-17 are type safe thanks to the constant pool and the corresponding rules which are not listed (because they don't make up of the StackMapTable attribute).
There are 3 instructions left to prove their type-safety:
8: goto 20 is type safe thanks to the second stack map frame for the target at instruction 20 (= 75 - 64 + 8 + 1 = 19 + 8 + 1), instructing the verifier that the stack frame there is the same as the previous stack frame at instruction 8:
frame_type = 8 /* same */
instructionIsTypeSafe(goto(Target), Environment, _Offset, StackFrame,
afterGoto, ExceptionStackFrame) :-
targetIsTypeSafe(Environment, StackFrame, Target),
exceptionStackFrame(StackFrame, ExceptionStackFrame).
11: astore_1 is type safe because the store is type safe, because it can pop a NoClassDefFoundError that is a subtype of reference off the stack (thanks to the first stack map frame again), and then legally assign that type to the local variable 1, i.e., Locals = [arrayOf(String), class(Missing, Lm)] -> NewLocals = [arrayOf(String), class(NoClassDefFoundError, Ln)].
An astore instruction with operand Index is type safe and yields an outgoing type state NextStackFrame, if a store instruction with operand Index and type reference is type safe and yields an outgoing type state NextStackFrame.
instructionIsTypeSafe(astore(Index), Environment, _Offset, StackFrame,
NextStackFrame, ExceptionStackFrame) :-
storeIsTypeSafe(Environment, Index, reference, StackFrame, NextStackFrame),
exceptionStackFrame(StackFrame, ExceptionStackFrame).
More precisely, the store is type safe if one can pop a type ActualType that "matches" Type (that is, is a subtype of Type) off the operand stack (§4.10.1.4), and then legally assign that type the local variable LIndex.
storeIsTypeSafe(_Environment, Index, Type,
frame(Locals, OperandStack, Flags),
frame(NextLocals, NextOperandStack, Flags)) :-
popMatchingType(OperandStack, Type, NextOperandStack, ActualType),
modifyLocalVariable(Index, ActualType, Locals, NextLocals).
20: return is type safe because Strange1.main declares a void return type, and the enclosing method is not an <init> method:
A return instruction is type safe if the enclosing method declares a void return type, and either:
- The enclosing method is not an <init> method, or
- this has already been completely initialized at the point where the instruction occurs.
instructionIsTypeSafe(return, Environment, _Offset, StackFrame,
afterGoto, ExceptionStackFrame) :-
thisMethodReturnType(Environment, void),
StackFrame = frame(_Locals, _OperandStack, Flags),
notMember(flagThisUninit, Flags),
exceptionStackFrame(StackFrame, ExceptionStackFrame).