FULL PRODUCT VERSION :
java version "1.4.2_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_06-b03)
Java HotSpot(TM) Client VM (build 1.4.2_06-b03, mixed mode)
java version "1.5.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-b64)
Java HotSpot(TM) Client VM (build 1.5.0-b64, mixed mode)
FULL OS VERSION :
Microsoft Windows 2000 [Version 5.00.2195]
A DESCRIPTION OF THE PROBLEM :
A class file was created with one method's max_locals set to ~63000.
When that method is invoked, the JVM crashes silently (without core dump)
(this bug was originally posted in error to the JVM forum:
http://forum.java.sun.com/thread.jspa?threadID=574794&messageID=2866316
)
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the source code (MyTestClass.java) provided
At a command prompt, run the following commands from the same directory that contains MyTestClass.class
set doTest=java -cp ./ MyTestClass
%doTest% 58000
%doTest% 60000
%doTest% 63000
-- -- -- --
The above values display the behaviour of interest. The third test demonstrates the bug of concern.
Many tests were run to determine the interesting ranges of max_locals and the associated bahaviour:
max_locals : Observed Behaviour
-------------------------------
0 VerifyError (as expected)
1 Success (unmodified max_locals)
2-58389 Success
58390-61895 StackOverflowError
61896-63938 JVM Silent crash (no core dump)
63939-65535 StackOverflowError
Some special values of max_locals where the JVM Crash is not silent:
61896-61898 message box "java.exe has generated errors and will be closed by Windows..."
61900 error message to console "Unexpected Signal : EXCEPTION_ACCESS_VIOLATION..."
EXPECTED VERSUS ACTUAL BEHAVIOR :
Actual results:
-- -- -- --
C:\test>set doTest=java -cp ./ MyTestClass
C:\test>%doTest% 58000
Running test for max_locals=58000
In targetMethod
Method call successul
Successful Exit
C:\test>%doTest% 60000
Running test for max_locals=60000
StackOverflowError
Successful Exit
C:\test>%doTest% 63000
Running test for max_locals=63000
-- -- -- --
-- -- -- --
Expected Results
I don't know whether the StackOverflowError is according to spec.
If it *is*, I disagree with the spec, and contend that it is unreasonable to successfully define a class which has methods that
will always throw StackOverflowError regardless of the actual assembly instructions present.
I think the most sensible behaviour here is to throw a VerifyError.
As to the threshold value of max_locals, I can think of 3 choices:
- 58389 (value determined by the fundamental cause for these observed bugs)
- Short.MAX_VALUE (because it is a simple, is far from causing bugs, yet still unreasonably generous limit)
- max_used_variable_index + max_count_unused_variables (where max_count_unused_variables would be defined in the spec)
I think the last option would be most in the spirit of with java's strict bytecode verification rules.
-- -- -- --
C:\test>set doTest=java -cp ./ MyTestClass
C:\test>%doTest% 58000
Running test for max_locals=58000
in targetMethod
Method call successul
Successful Exit
C:\test>%doTest% 60000
Running test for max_locals=60000
Exception in thread "main" java.lang.VerifyError: (class: MyTestClass, method: targetMethod signature: ()V) specified value for
max_locals(60000) is too large
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:1655)
at java.lang.Class.getDeclaredMethod(Class.java:1262)
at MyTestClass.main(MyTestClass.java:64)
C:\test>%doTest% 63000
Running test for max_locals=63000
Exception in thread "main" java.lang.VerifyError: (class: MyTestClass, method: targetMethod signature: ()V) specified value for
max_locals(63000) is too large
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:1655)
at java.lang.Class.getDeclaredMethod(Class.java:1262)
at MyTestClass.main(MyTestClass.java:64)
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Here is the console output for the most promising error message when the JVM crashes:
-- -- -- --
C:\test>%doTest% 61900
Running test for max_locals=61900
Unexpected Signal : EXCEPTION_ACCESS_VIOLATION (0xc0000005) occurred at PC=0x807248E
Function=[Unknown.]
Library=C:\JSDKs\j2sdk1.4.2_06\jre\bin\client\jvm.dll
NOTE: We are unable to locate the function name symbol for the error
just occurred. Please refer to release documentation for possible
reason and solutions.
Current Java thread:
****************
Another exception has been detected while we were handling last error.
Dumping information about last error:
ERROR REPORT FILE = (N/A)
PC = 0x0807248e
SIGNAL = -1073741819
FUNCTION NAME = (N/A)
OFFSET = 0xFFFFFFFF
LIBRARY NAME = C:\JSDKs\j2sdk1.4.2_06\jre\bin\client\jvm.dll
Please check ERROR REPORT FILE for further information, if there is any.
Good bye.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Displays JVM behaviour in the case of bad max_locals values.
* (see VM Spec The class File Format - Section 4.7.3 - The CodeAttribute)
*
* For conciseness, what should conventionally be modelled as 3 separate
* classes has been combined into one. These aspects are:
* A target class (that will be modified) with a single target method
* A ClassLoader to create Class objects from modified bytecode
* A Class to modify bytecode, and invoke the target method
*
* @author Josh Micich
*/
public class MyTestClass extends ClassLoader {
//------------------------
// Target Class Aspect
//------------------------
/**
* The max_locals value of this method will be modified for each test
*/
public static void targetMethod() {
int i=0;
System.out.println("In targetMethod");
i++;
}
//------------------------
// ClassLoader Aspect
//------------------------
public MyTestClass(ClassLoader parent) {
super(parent);
}
public Class makeClass(String className, byte[] bytecode) {
return defineClass(className, bytecode, 0, bytecode.length);
}
//------------------------
// Main Test Runner Aspect
//------------------------
public static void main(String[] args) {
int maxLocals = Integer.parseInt(args[0]);
System.out.println("Running test for max_locals=" + maxLocals);
Class c = MyTestClass.class;
InputStream is = c.getResourceAsStream("MyTestClass.class");
byte[] byteCode = createByteArrayFromStream(is);
setMaxLocals(byteCode, maxLocals);
MyTestClass classLoader = new MyTestClass(c.getClassLoader());
Class cPrime = classLoader.makeClass(c.getName(), byteCode);
Method m;
try {
m = cPrime.getDeclaredMethod("targetMethod", null);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
try {
m.invoke(null, null);
System.out.println("Method call successul");
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
if(!(e.getTargetException() instanceof StackOverflowError)) {
throw new RuntimeException(e);
}
System.out.println("StackOverflowError");
// fall through
}
System.out.println("Successful Exit");
}
/**
* Modifies the 'u2 max_locals' for the 'targetMethod()'
*/
private static void setMaxLocals(byte[] bb, int maxLocals) {
int max = bb.length - 13;
int location = -1;
for (int i = 0; i < max; i++) {
if(isDesiredBytecodeLocation(bb, i)) {
if(location >=0) {
throw new RuntimeException("more than one bytecode location found");
}
location = i;
}
}
if(location < 0) {
throw new RuntimeException("bytecode location not found");
}
location +=2; // +2 step over 'u2 max_stack'
// make the modification to the bytecode:
bb[location + 0] = (byte)(maxLocals >> 8);
bb[location + 1] = (byte)(maxLocals >> 0);
}
/**
* @return true if i is the index of the desired 'u2 max_stack' bytecode element
*/
private static boolean isDesiredBytecodeLocation(byte[] bb, int i) {
final byte EXPECTED_MAX_STACK = 2;
final byte EXPECTED_MAX_LOCALS = 1;
if(bb[i+0] != 0 || bb[i+1] != EXPECTED_MAX_STACK) {
return false;
}
if(bb[i+2] != 0 || bb[i+3] != EXPECTED_MAX_LOCALS) {
return false;
}
// next 4 bytes should be code length
int codeLength = intFrom4Bytes(bb, i+4);
// this is where the u2 exception_table_length is found
// (assuming codeLength is valid)
int exTabLengthIx = i + 8 + codeLength;
if(codeLength < 0 || exTabLengthIx + 4 > bb.length) {
return false;
}
// looking for method with no exception handlers
if(bb[exTabLengthIx+0] != 0 || bb[exTabLengthIx+1] != 0) {
return false;
}
return true;
}
private static int intFrom4Bytes(byte[] byteCode, int offset) {
return
((((int)byteCode[offset + 0]) << 24) & 0xFF000000)
+ ((((int)byteCode[offset + 1]) << 16) & 0x00FF0000)
+ ((((int)byteCode[offset + 2]) << 8) & 0x0000FF00)
+ ((((int)byteCode[offset + 3]) << 0) & 0x000000FF);
}
private static byte[] createByteArrayFromStream(InputStream is) {
final int MAX_SIZE = 6000;
byte[] buf = new byte[MAX_SIZE];
int totalBytesReadSoFar = 0;
while (true) {
int desiredReadLen = buf.length - totalBytesReadSoFar;
if(desiredReadLen == 0) {
throw new RuntimeException("buffer is not big enough");
}
int bytesRead;
try {
bytesRead = is.read(buf, totalBytesReadSoFar, desiredReadLen);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (bytesRead < 1) {
// exhausted input stream
break;
}
totalBytesReadSoFar += bytesRead;
}
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
byte[] result = new byte[totalBytesReadSoFar];
System.arraycopy(buf, 0, result, 0, totalBytesReadSoFar);
return result;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
don't set max_locals to any large value (>~50000)
java version "1.4.2_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_06-b03)
Java HotSpot(TM) Client VM (build 1.4.2_06-b03, mixed mode)
java version "1.5.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-b64)
Java HotSpot(TM) Client VM (build 1.5.0-b64, mixed mode)
FULL OS VERSION :
Microsoft Windows 2000 [Version 5.00.2195]
A DESCRIPTION OF THE PROBLEM :
A class file was created with one method's max_locals set to ~63000.
When that method is invoked, the JVM crashes silently (without core dump)
(this bug was originally posted in error to the JVM forum:
http://forum.java.sun.com/thread.jspa?threadID=574794&messageID=2866316
)
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the source code (MyTestClass.java) provided
At a command prompt, run the following commands from the same directory that contains MyTestClass.class
set doTest=java -cp ./ MyTestClass
%doTest% 58000
%doTest% 60000
%doTest% 63000
-- -- -- --
The above values display the behaviour of interest. The third test demonstrates the bug of concern.
Many tests were run to determine the interesting ranges of max_locals and the associated bahaviour:
max_locals : Observed Behaviour
-------------------------------
0 VerifyError (as expected)
1 Success (unmodified max_locals)
2-58389 Success
58390-61895 StackOverflowError
61896-63938 JVM Silent crash (no core dump)
63939-65535 StackOverflowError
Some special values of max_locals where the JVM Crash is not silent:
61896-61898 message box "java.exe has generated errors and will be closed by Windows..."
61900 error message to console "Unexpected Signal : EXCEPTION_ACCESS_VIOLATION..."
EXPECTED VERSUS ACTUAL BEHAVIOR :
Actual results:
-- -- -- --
C:\test>set doTest=java -cp ./ MyTestClass
C:\test>%doTest% 58000
Running test for max_locals=58000
In targetMethod
Method call successul
Successful Exit
C:\test>%doTest% 60000
Running test for max_locals=60000
StackOverflowError
Successful Exit
C:\test>%doTest% 63000
Running test for max_locals=63000
-- -- -- --
-- -- -- --
Expected Results
I don't know whether the StackOverflowError is according to spec.
If it *is*, I disagree with the spec, and contend that it is unreasonable to successfully define a class which has methods that
will always throw StackOverflowError regardless of the actual assembly instructions present.
I think the most sensible behaviour here is to throw a VerifyError.
As to the threshold value of max_locals, I can think of 3 choices:
- 58389 (value determined by the fundamental cause for these observed bugs)
- Short.MAX_VALUE (because it is a simple, is far from causing bugs, yet still unreasonably generous limit)
- max_used_variable_index + max_count_unused_variables (where max_count_unused_variables would be defined in the spec)
I think the last option would be most in the spirit of with java's strict bytecode verification rules.
-- -- -- --
C:\test>set doTest=java -cp ./ MyTestClass
C:\test>%doTest% 58000
Running test for max_locals=58000
in targetMethod
Method call successul
Successful Exit
C:\test>%doTest% 60000
Running test for max_locals=60000
Exception in thread "main" java.lang.VerifyError: (class: MyTestClass, method: targetMethod signature: ()V) specified value for
max_locals(60000) is too large
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:1655)
at java.lang.Class.getDeclaredMethod(Class.java:1262)
at MyTestClass.main(MyTestClass.java:64)
C:\test>%doTest% 63000
Running test for max_locals=63000
Exception in thread "main" java.lang.VerifyError: (class: MyTestClass, method: targetMethod signature: ()V) specified value for
max_locals(63000) is too large
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:1655)
at java.lang.Class.getDeclaredMethod(Class.java:1262)
at MyTestClass.main(MyTestClass.java:64)
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Here is the console output for the most promising error message when the JVM crashes:
-- -- -- --
C:\test>%doTest% 61900
Running test for max_locals=61900
Unexpected Signal : EXCEPTION_ACCESS_VIOLATION (0xc0000005) occurred at PC=0x807248E
Function=[Unknown.]
Library=C:\JSDKs\j2sdk1.4.2_06\jre\bin\client\jvm.dll
NOTE: We are unable to locate the function name symbol for the error
just occurred. Please refer to release documentation for possible
reason and solutions.
Current Java thread:
****************
Another exception has been detected while we were handling last error.
Dumping information about last error:
ERROR REPORT FILE = (N/A)
PC = 0x0807248e
SIGNAL = -1073741819
FUNCTION NAME = (N/A)
OFFSET = 0xFFFFFFFF
LIBRARY NAME = C:\JSDKs\j2sdk1.4.2_06\jre\bin\client\jvm.dll
Please check ERROR REPORT FILE for further information, if there is any.
Good bye.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Displays JVM behaviour in the case of bad max_locals values.
* (see VM Spec The class File Format - Section 4.7.3 - The CodeAttribute)
*
* For conciseness, what should conventionally be modelled as 3 separate
* classes has been combined into one. These aspects are:
* A target class (that will be modified) with a single target method
* A ClassLoader to create Class objects from modified bytecode
* A Class to modify bytecode, and invoke the target method
*
* @author Josh Micich
*/
public class MyTestClass extends ClassLoader {
//------------------------
// Target Class Aspect
//------------------------
/**
* The max_locals value of this method will be modified for each test
*/
public static void targetMethod() {
int i=0;
System.out.println("In targetMethod");
i++;
}
//------------------------
// ClassLoader Aspect
//------------------------
public MyTestClass(ClassLoader parent) {
super(parent);
}
public Class makeClass(String className, byte[] bytecode) {
return defineClass(className, bytecode, 0, bytecode.length);
}
//------------------------
// Main Test Runner Aspect
//------------------------
public static void main(String[] args) {
int maxLocals = Integer.parseInt(args[0]);
System.out.println("Running test for max_locals=" + maxLocals);
Class c = MyTestClass.class;
InputStream is = c.getResourceAsStream("MyTestClass.class");
byte[] byteCode = createByteArrayFromStream(is);
setMaxLocals(byteCode, maxLocals);
MyTestClass classLoader = new MyTestClass(c.getClassLoader());
Class cPrime = classLoader.makeClass(c.getName(), byteCode);
Method m;
try {
m = cPrime.getDeclaredMethod("targetMethod", null);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
try {
m.invoke(null, null);
System.out.println("Method call successul");
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
if(!(e.getTargetException() instanceof StackOverflowError)) {
throw new RuntimeException(e);
}
System.out.println("StackOverflowError");
// fall through
}
System.out.println("Successful Exit");
}
/**
* Modifies the 'u2 max_locals' for the 'targetMethod()'
*/
private static void setMaxLocals(byte[] bb, int maxLocals) {
int max = bb.length - 13;
int location = -1;
for (int i = 0; i < max; i++) {
if(isDesiredBytecodeLocation(bb, i)) {
if(location >=0) {
throw new RuntimeException("more than one bytecode location found");
}
location = i;
}
}
if(location < 0) {
throw new RuntimeException("bytecode location not found");
}
location +=2; // +2 step over 'u2 max_stack'
// make the modification to the bytecode:
bb[location + 0] = (byte)(maxLocals >> 8);
bb[location + 1] = (byte)(maxLocals >> 0);
}
/**
* @return true if i is the index of the desired 'u2 max_stack' bytecode element
*/
private static boolean isDesiredBytecodeLocation(byte[] bb, int i) {
final byte EXPECTED_MAX_STACK = 2;
final byte EXPECTED_MAX_LOCALS = 1;
if(bb[i+0] != 0 || bb[i+1] != EXPECTED_MAX_STACK) {
return false;
}
if(bb[i+2] != 0 || bb[i+3] != EXPECTED_MAX_LOCALS) {
return false;
}
// next 4 bytes should be code length
int codeLength = intFrom4Bytes(bb, i+4);
// this is where the u2 exception_table_length is found
// (assuming codeLength is valid)
int exTabLengthIx = i + 8 + codeLength;
if(codeLength < 0 || exTabLengthIx + 4 > bb.length) {
return false;
}
// looking for method with no exception handlers
if(bb[exTabLengthIx+0] != 0 || bb[exTabLengthIx+1] != 0) {
return false;
}
return true;
}
private static int intFrom4Bytes(byte[] byteCode, int offset) {
return
((((int)byteCode[offset + 0]) << 24) & 0xFF000000)
+ ((((int)byteCode[offset + 1]) << 16) & 0x00FF0000)
+ ((((int)byteCode[offset + 2]) << 8) & 0x0000FF00)
+ ((((int)byteCode[offset + 3]) << 0) & 0x000000FF);
}
private static byte[] createByteArrayFromStream(InputStream is) {
final int MAX_SIZE = 6000;
byte[] buf = new byte[MAX_SIZE];
int totalBytesReadSoFar = 0;
while (true) {
int desiredReadLen = buf.length - totalBytesReadSoFar;
if(desiredReadLen == 0) {
throw new RuntimeException("buffer is not big enough");
}
int bytesRead;
try {
bytesRead = is.read(buf, totalBytesReadSoFar, desiredReadLen);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (bytesRead < 1) {
// exhausted input stream
break;
}
totalBytesReadSoFar += bytesRead;
}
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
byte[] result = new byte[totalBytesReadSoFar];
System.arraycopy(buf, 0, result, 0, totalBytesReadSoFar);
return result;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
don't set max_locals to any large value (>~50000)