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

Unsafe write after primitive array creation may result in array length change

XMLWordPrintable

    • b19
    • generic
    • generic

        ADDITIONAL SYSTEM INFORMATION :
        CentOS 7.2 / Ubuntu 14.04

        A DESCRIPTION OF THE PROBLEM :
        Generally speaking, this a problem that the primitive array length changes when Unsafe.putInt is following the array creation.

        This problem happens in all our test jvm (7u91, 8u132 (openjdk), 8u172, 10.0.1)

        The core problem code can be simplified as:

        byte[] buf = new byte[397];
        THE_UNSAFE.putInt(buf, BYTE_ARRAY_BASE_OFFSET + 1, buf.length);

        After these code, buf.length "should" never be changed.
        But the length really changes on some running of multiple executions.

        Under default settings with hotspot jvm (no user defined options provided),
        the problem can be reproduced occasionally.

        Under extra option provided (-XX:CompileOnly=JvmTest.serBytes),
        the problem can always be reproduces.

        With openjdk hotspot jvm with ASSERT enabled, (my jdk8 build is 8u132)
        the jvm crash with following information:

        # A fatal error has been detected by the Java Runtime Environment:
        #
        # Internal Error (/root/lty/jdk8/openjdk/hotspot/src/share/vm/opto/memnode.cpp:2883), pid=19749, tid=140692759176960
        # assert((end_offset % BytesPerInt) == 0) failed: odd end offset
        #


        For your convenience, our analysis shows the problem may relate to array InitializeNode logic.
        It `capture_store` the the memory write of Unsafe.putInt.
        Since the putInt occupied offset range [17, 21] from the array pointer,
        then it decided to `clear_memory` of offset range [16, 17] of the array pointer.
        This range actually cannot pass the assert "assert((end_offset % BytesPerInt) == 0, "odd end offset")".
        While in jvm product mode, without the assert, the compiler falsely calculated to clear range [13, 17],
        which will clear the three most significant bytes of the `length` of this array.

        In the example code, byte array with length 397 (0x0000018d) becomes length 141 (0x0000008d).

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        Run the java code multiple times

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        (Output nothing)
        ACTUAL -
        Occasionally output multiple lines:
        "array length internal error, expected: 397 (0x18d) actual: 141 (0x8d)"

        ---------- BEGIN SOURCE ----------
        import sun.misc.Unsafe;

        import java.lang.reflect.Field;
        import java.security.AccessController;
        import java.security.PrivilegedAction;

        public class JvmTest {

          public static void main(String[] args) {
            System.err.close();
            int count = 0;
            while (count++ < 120000) {
              test();
            }
          }

          public static void test() {
            byte[] newBuf = serBytes(397);

            if (newBuf.length != 397) {
              System.out.println("array length internal error, expected: " +
                      397 + " (0x" + Integer.toHexString(397) + ")"
                      + " actual: " + newBuf.length
                      + " (0x" + Integer.toHexString(newBuf.length) + ")");
            }
          }

          public static byte[] serBytes(int bufLen) {
            byte[] buf = new byte[bufLen];
            THE_UNSAFE.putInt(buf, BYTE_ARRAY_BASE_OFFSET + 1, buf.length);
            System.err.println("ref " + buf);
            return buf;
          }


          /*
            Unsafe fields and initialization
           */
          static final Unsafe THE_UNSAFE;
          static final long BYTE_ARRAY_BASE_OFFSET;
          static {
            THE_UNSAFE = (Unsafe) AccessController.doPrivileged(
                    new PrivilegedAction<Object>() {
                      @Override
                      public Object run() {
                        try {
                          Field f = Unsafe.class.getDeclaredField("theUnsafe");
                          f.setAccessible(true);
                          return f.get(null);
                        } catch (NoSuchFieldException | IllegalAccessException e) {
                          throw new Error();
                        }
                      }
                    }
            );
            BYTE_ARRAY_BASE_OFFSET = THE_UNSAFE.arrayBaseOffset(byte[].class);
          }
        }

        ---------- END SOURCE ----------

        FREQUENCY : occasionally


          1. JvmTest.java
            2 kB
          2. hs_err_pid58248.log
            59 kB
          3. replay_pid58248.log
            195 kB

              rraghavan Rahul Raghavan
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              8 Start watching this issue

                Created:
                Updated:
                Resolved: