src/java.base/share/classes/java/io/ObjectInputStream.java

Print this page

        

@@ -38,10 +38,11 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import static java.io.ObjectStreamClass.processQueue;
+import sun.misc.Unsafe;
 import sun.reflect.misc.ReflectUtil;
 
 /**
  * An ObjectInputStream deserializes primitive data and objects previously
  * written using an ObjectOutputStream.

@@ -266,10 +267,20 @@
      * Null when not during readObject upcall.
      */
     private SerialCallbackContext curContext;
 
     /**
+     * Tracks whether deserialization of an object in the graph may have set
+     * a final field, and therefore requires a freeze action before returning
+     * the graph of objects from the topmost readObject call.
+     * Nested calls to readObject do not issue freeze actions because the
+     * sub-graph returned from a nested call is not guaranteed to be fully
+     * initialized yet (possible cycles).
+     */
+    private boolean requiresFreeze;
+
+    /**
      * Creates an ObjectInputStream that reads from the specified InputStream.
      * A serialization stream header is read from the stream and verified.
      * This constructor will block until the corresponding ObjectOutputStream
      * has written and flushed the header.
      *

@@ -373,10 +384,13 @@
             if (ex != null) {
                 throw ex;
             }
             if (depth == 0) {
                 vlist.doCallbacks();
+                if (requiresFreeze) {
+                    freeze();
+                }
             }
             return obj;
         } finally {
             passHandle = outerHandle;
             if (closed && depth == 0) {

@@ -463,10 +477,13 @@
             if (ex != null) {
                 throw ex;
             }
             if (depth == 0) {
                 vlist.doCallbacks();
+                if (requiresFreeze) {
+                    freeze();
+                }
             }
             return obj;
         } finally {
             passHandle = outerHandle;
             if (closed && depth == 0) {

@@ -1876,12 +1893,16 @@
      */
     private void readSerialData(Object obj, ObjectStreamClass desc)
         throws IOException
     {
         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
+        boolean freeze = false;
         for (int i = 0; i < slots.length; i++) {
             ObjectStreamClass slotDesc = slots[i].desc;
+            if (!freeze && slotDesc.hasFinalField()) {
+                freeze = true;
+            }
 
             if (slots[i].hasData) {
                 if (obj != null &&
                     slotDesc.hasReadObjectMethod() &&
                     handles.lookupException(passHandle) == null)

@@ -1928,10 +1949,11 @@
                 {
                     slotDesc.invokeReadObjectNoData(obj);
                 }
             }
         }
+        requiresFreeze = freeze;
     }
 
     /**
      * Skips over all block data and objects until TC_ENDBLOCKDATA is
      * encountered.

@@ -2355,10 +2377,19 @@
         public void close() throws IOException {
             in.close();
         }
     }
 
+    private static final Unsafe UNSAFE = Unsafe.getUnsafe();
+
+    private void freeze() {
+        // Issue a storestore|storeload fence, which is at least sufficient
+        // to provide final-freeze semantics. 
+        UNSAFE.storeFence();
+        requiresFreeze = false;
+    }
+
     /**
      * Input stream with two modes: in default mode, inputs data written in the
      * same format as DataOutputStream; in "block data" mode, inputs data
      * bracketed by block data markers (see object serialization specification
      * for details).  Buffering depends on block data mode: when in default