ConcurrentHashMap crashing application compiled with JDK 8 but targeting JRE 7 - java

I ran into a very unexpected error today and while I was able to find a way to fix the problem as a whole I'm not sure I completely understand why it did what it did.
The code I'm working with was originally written with a JDK 7 environment of course targeting JRE 7. In the code I was using a ConcurrentHashMap and needed to iterate over the keys in the map. For this I was using the map.keySet() which according to the JavaDocs should return a Set<K>. This worked fine until our build environment switched to JDK8.
When we moved to JDK8 I ensured that I was calling a target/source for 1.7 when calling the javac. So I was pretty surprised when the code started failing right when it wanted to iterate through the keys of the map. No error was thrown, no exception, the thread just simply stopped. After doing some research I found that Java8's implementation for ConcurrentHashMap the .keySet() method returns a KeySetView<K,V>.
I fixed the problem by switching from using the map.keySet() to getting an Enumeration<K> using map.keys().
Now my guess as to the problem is that although the project was compiled targeting Java7 since the JDK8 was used the Java8 libraries were included, but why didn't it thrown an error or an exception when it hit the mismatch?
As asked here is a code snippet:
class MapProcessing
{
private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
public MapProcessing()
{
map.put("First",new Object());
map.put("Second",new Object());
map.put("Third",new Object());
}
public void processing()
{
// when calling this type of loop causes a freeze on our system.
for(String key : map.keySet())
{
System.out.println(key);
}
}
public void working()
{
// This is what I had to do to fix the problem.
Enumeration<String> keys = map.keys();
while(keys.hasMoreElements())
{
String key = keys.nextElement();
System.out.println(key);
}
}
}
We are compiling using Oracle JDK 8 build 40 using a target for 1.7 and source 1.7 in the javac on a Windows 2012 server.
The code is running using Oracle JVM 7 build 25 running on Windows 2012 server.

If i compile your code with Java 8 and javac -source 1.7 -target 1.8 and then run it with Java 7 i get an
Exception in thread "main" java.lang.NoSuchMethodError:
java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
at stackoverflowt.Test.processing(Test.java:20)
at stackoverflowt.Test.main(Test.java:27)
This is because the the byte code looks like
public void processing();
Code:
0: aload_0
1: getfield #4 // Field map:Ljava/util/concurrent/ConcurrentHashMap;
4: invokevirtual #10 // Method java/util/concurrent/ConcurrentHashMap.keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
7: invokevirtual #11 // Method java/util/concurrent/ConcurrentHashMap$KeySetView.iterator:()Ljava/util/Iterator;
10: astore_1
and referring explicitly to ConcurrentHashMap$KeySetView which is not present in Java 7. I am on Mac with Java 1.7.0_79 and 1.8.0_45
If you change the code to (only use the Map Interface):
private Map<String, Object> map = new ConcurrentHashMap<String, Object>();
then it work's for me. Bytecode then looks like
public void processing();
Code:
0: aload_0
1: getfield #4 // Field map:Ljava/util/Map;
4: invokeinterface #10, 1 // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
9: invokeinterface #11, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
14: astore_1

Whenever you build a project using a newer JDK using the -source argument targeting an older version, you'll get this compiler warning:
warning: [options] bootstrap class path not set in conjunction with -source 1.7
This blog entry talks about what it means.
Basically, you get this warning because Java is compiling it using older language rules but against the newer class library... and there are some compatibility issues with the Java 8 versions as Oracle moved some of the internal classes around.
The fix is to use the -bootclasspath argument to point it at the rt.jar from the older version while compiling.

Related

why getSimpleName() is twice in com.sun.tools.javac.tree.JCTree$JCClassDecl

I had a weird bug in an application code, which is an annotation processor and I could find that the root cause of the bug was that the class com.sun.tools.javac.tree.JCTree$JCClassDecl contains the method getSimpleName() twice when I query the class using the reflective method getMethods(). The two versions differ only in the return type. This is legal in JVM code, but not legal in Java. This is not method overloading, because it is only the return type that differs and the return type is not part of the method signature.
The issue can be demonstrated with the simple code:
Method[] methods = com.sun.tools.javac.tree.JCTree.JCClassDecl.class.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
It will print
...
public javax.lang.model.element.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
public com.sun.tools.javac.util.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
...
(The ellipsis stands for more output lines showing the various other methods that are not interesting for us now.)
The Java version I used to test this is
$ java -version
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)
on a Windows 10 machine.
QUESTION: How was this class code created? My understanding is that this part of the code is written in Java, but in Java this is not possible. Also: what is the aim to have two same-signature versions of a method? Any hint?
If you look at the source code1 you'll see there's only one method with a name of getSimpleName(). This method returns com.sun.tools.javac.util.Name. There's two critical things to note about this:
That method is actually overriding com.sun.source.tree.ClassTree#getSimpleName() which is declared to return javax.lang.model.element.Name.
The com.sun.tools.javac.util.Name abstract class implements the javax.lang.model.element.Name interface, and since the overridden method returns the former it is taking advantage of covariant return types.
According to this Oracle blog, a method which overrides another but declares a covariant return type is implemented using bridge methods.
How is this implemented?
Although the return type based overloading is not allowed by java language, JVM always allowed return type based overloading. JVM uses full signature of a method for lookup/resolution. Full signature includes return type in addition to argument types. i.e., a class can have two or more methods differing only by return type. javac uses this fact to implement covariant return types. In the above, CircleFactory example, javac generates code which is equivalent to the following:
class CircleFactory extends ShapeFactory {
public Circle newShape() {
// your code from the source file
return new Circle();
}
// javac generated method in the .class file
public Shape newShape() {
// call the other newShape method here -- invokevirtual newShape:()LCircle;
}
}
We can use javap with -c option on the class to verify this. Note that we still can't use return type based overloading in source language. But, this is used by javac to support covariant return types. This way, there is no change needed in the JVM to support covariant return types.
And in fact, if you run the following command:
javap -v com.sun.tools.javac.tree.JCTree$JCClassDecl
The following will be output (only including the relevant methods):
public com.sun.tools.javac.util.Name getSimpleName();
descriptor: ()Lcom/sun/tools/javac/util/Name;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #13 // Field name:Lcom/sun/tools/javac/util/Name;
4: areturn
LineNumberTable:
line 801: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
And:
public javax.lang.model.element.Name getSimpleName();
descriptor: ()Ljavax/lang/model/element/Name;
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #96 // Method getSimpleName:()Lcom/sun/tools/javac/util/Name;
4: areturn
LineNumberTable:
line 752: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
As you can see, the second method, the one which returns javax.lang.model.element.Name, is both synthetic and a bridge. In other words, the method is generated by the compiler as part of the implementation of covariant return types. It also simply delegates to the "real" method, the one actually present in the source code which returns com.sun.tools.javac.util.Name.
1. The source code link is for JDK 13.

Can javac stack frame operand verification types be disabled or ignored at class load in java 7?

The problem I am facing can be distilled down to this simple example. Compile this with Oracle java 7 jdk, and then attempt to run it with IBM jre 7 or jre 8. It fails with NoClassDefFoundError on java.lang.AbstractStringBuilder.
package org.example;
public class Example {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("got it") ;
System.out.println(" works "+sb);
StringBuilder sb2 = new StringBuilder("other") ;
sb2.append("killer") ;
sb2.length() ;
System.out.println("builder "+sb2);
System.out.println( sb == null ? sb2 : sb);
}
}
Related to http://chrononsystems.com/blog/java-7-design-flaw-leads-to-huge-backward-step-for-the-jvm the java 7 compiler adds stack frames to the byte code which include type verification for the stack operands.
Byte code for this example compiled with Oracle jdk includes:
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/StringBuffer, class java/lang/StringBuilder ]
stack = [ class java/io/PrintStream, class java/lang/AbstractStringBuilder ]
While bytecode from IBM jdk compiler is:
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/StringBuffer, class java/lang/StringBuilder ]
stack = [ class java/io/PrintStream, class java/io/Serializable ]
The stack frame from Oracle resolves to the base class AbstractStringBuilder class. That class does not exist in IBM JRE rt.jar. We use Oracle JDK in our build process & I’m reluctant to change that. This java 7 type verification seems to create non-portable bytecode.
Is there a way to disable this type verification checking at either compile time or runtime?
Thanks
Why would you try to compile and run Java in different Vendors. I see imports are different. Something might have gone wrong somewhere. Oracle JDK has StringBuilder as well
Try using java.lang.StringBuilder instead of StringBuilder and share what output you are getting.
java.lang.StringBuffer sb = new java.lang.StringBuffer("got it") ;
System.out.println(" works "+sb);
java.lang.StringBuilder sb2 = new java.lang.StringBuilder("other") ;
Thanks for the advice & help. After posting this, we found the root cause. we were using an older version of aspect-j to instrument the classes before packaging them in our application. Sorry, I never found why the aspect j instrumentation added AbstractStringBuilder to the altered class file.
We were no longer requiring aspect-j. So removing that solved the problem.
Thanks for the help

The type java.lang.CharSequence cannot be resolved. TomCat

Hi Guys I'm not new to java web edition. I have a websystem which my login page (index.jsp) runs fine. When a login is successful that's where the problem comes, i get this error.
org.apache.jasper.JasperException: Unable to compile class for JSP:
An error occurred at line: 12 in the jsp file: /MSS_portal_frameset.jsp
The type java.lang.CharSequence cannot be resolved. It is indirectly referenced from required .class files
9: StringBuffer result = new StringBuffer();
10:
11: while ((e = str.indexOf(pattern, s)) >= 0) {
12: result.append(str.substring(s, e));
13: result.append(replace);
14: s = e+pattern.length();
15: }
Stacktrace:
org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.java:85)
org.apache.jasper.compiler.ErrorDispatcher.javacError(ErrorDispatcher.java:330)
org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:435)
org.apache.jasper.compiler.Compiler.compile(Compiler.java:298)
org.apache.jasper.compiler.Compiler.compile(Compiler.java:277)
org.apache.jasper.compiler.Compiler.compile(Compiler.java:265)
org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:564)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:299)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:315)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
That section of the code is inside scriptlets <% %>.
java.lang.CharSequence was added in Java 1.4, so if it can't find it, you're either running a very old Java, or you classpath is really messed up.
You should replace use of StringBuffer with StringBuilder, which was added in Java 5. Use of StringBuffer is obsolete.
Quoting javadoc:
As of release JDK 5, this class (StringBuffer) has been supplemented with an equivalent class designed for use by a single thread, StringBuilder. The StringBuilder class should generally be used in preference to this one, as it supports all of the same operations but it is faster, as it performs no synchronization.

For loop with generic collection fails to compile in Java 7

Maybe someone can explain the behaviour below. I know there were some generic type-handling changes from Java 6 to 7, but I couldn't find one to explain this.
This is happening with this library:
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>3.2.2</version>
</dependency>
And the following demonstration code:
import org.apache.felix.framework.util.manifestparser.ManifestParser;
ManifestParser manifestParser = new ManifestParser(null, null, null, null);
for (Capability capability : manifestParser.getCapabilities()) {
capability.toString();
}
// where the signature of getCapabilities() is:
// public List<Capability> getCapabilities() { return m_capabilities; }
// and there are no other methods with similar signatures or names
This demo code compiles just fine with JDK 6 (x86, 1.6.0_45, 32-bit), but fails to compile with JDK 7 (x86, 1.7.0_25, 32-bit, same host):
// line number matches the for loop
java: incompatible types
required: org.apache.felix.framework.capabilityset.Capability
found: java.lang.Object
After some head scratching, I have a workaround but no explanation. The following modification to the demo code compiles with JDK 7:
ManifestParser manifestParser = new ManifestParser(null, null, null, null);
List<Capability> capabilities = manifestParser.getCapabilities();
for (Capability capability : capabilities) {
capability.toString();
}
Why is this?
See How to compile mavenized OSGi 4.3 bundle with OpenJDK 7?
Because of the OSGi classes in that felix jar, you cannot use it to compile against with Java 7.
Are you sure that there is no classpath problem, like different versions of the same class in the classpath? This could be possible if you have in the classpath two versions of the same class, one for java 1.4 returning List and one for java 5+ returning List<Capability>.

Tricky try-catch java 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).

Categories

Resources