Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-6203391

JVM crash for large values of max_locals

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P3 P3
    • 9
    • 5.0
    • hotspot
    • x86
    • windows_2000

      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)

            zgu Zhengyu Gu
            rmandalasunw Ranjith Mandala (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: