< prev index next >

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

Print this page
*** 24,35 ***
   */
  
  package java.io;
  
  import java.io.ObjectInputFilter.Config;
! import java.io.ObjectStreamClass.RecordSupport;
  import java.lang.System.Logger;
  import java.lang.invoke.MethodHandle;
  import java.lang.reflect.Array;
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Modifier;
  import java.lang.reflect.Proxy;
  import java.nio.charset.StandardCharsets;
  import java.security.AccessControlContext;
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import java.security.PrivilegedActionException;
  import java.security.PrivilegedExceptionAction;
  import java.util.Arrays;
! import java.util.Map;
  import java.util.Objects;
  
  import jdk.internal.access.JavaLangAccess;
  import jdk.internal.access.SharedSecrets;
  import jdk.internal.event.DeserializationEvent;
  import jdk.internal.misc.Unsafe;
  import jdk.internal.util.ByteArray;
  import sun.reflect.misc.ReflectUtil;
  import sun.security.action.GetBooleanAction;
  import sun.security.action.GetIntegerAction;
  
  /**
   * An ObjectInputStream deserializes primitive data and objects previously
   * written using an ObjectOutputStream.
   *
--- 24,39 ---
   */
  
  package java.io;
  
  import java.io.ObjectInputFilter.Config;
! import java.io.ObjectStreamClass.ConstructorSupport;
+ import java.io.ObjectStreamClass.ClassDataSlot;
  import java.lang.System.Logger;
  import java.lang.invoke.MethodHandle;
  import java.lang.reflect.Array;
  import java.lang.reflect.InvocationHandler;
+ import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Modifier;
  import java.lang.reflect.Proxy;
  import java.nio.charset.StandardCharsets;
  import java.security.AccessControlContext;
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import java.security.PrivilegedActionException;
  import java.security.PrivilegedExceptionAction;
  import java.util.Arrays;
! import java.util.List;
+ import java.util.Locale;
  import java.util.Objects;
  
  import jdk.internal.access.JavaLangAccess;
  import jdk.internal.access.SharedSecrets;
  import jdk.internal.event.DeserializationEvent;
  import jdk.internal.misc.Unsafe;
  import jdk.internal.util.ByteArray;
  import sun.reflect.misc.ReflectUtil;
  import sun.security.action.GetBooleanAction;
  import sun.security.action.GetIntegerAction;
+ import sun.security.action.GetPropertyAction;
  
  /**
   * An ObjectInputStream deserializes primitive data and objects previously
   * written using an ObjectOutputStream.
   *

*** 217,10 ***
--- 221,12 ---
   * form.  The methods of the Externalizable interface, writeExternal and
   * readExternal, are called to save and restore the objects state.  When
   * implemented by a class they can write and read their own state using all of
   * the methods of ObjectOutput and ObjectInput.  It is the responsibility of
   * the objects to handle any versioning that occurs.
+  * Value objects cannot be `java.io.Externalizable` because value objects are
+  * immutable and `Externalizable.readExternal` is unable to modify the fields of the value.
   *
   * <p>Enum constants are deserialized differently than ordinary serializable or
   * externalizable objects.  The serialized form of an enum constant consists
   * solely of its name; field values of the constant are not transmitted.  To
   * deserialize an enum constant, ObjectInputStream reads the constant name from

*** 242,10 ***
--- 248,14 ---
   * as readObject and writeObject, are ignored for serializable records. See
   * <a href="{@docRoot}/../specs/serialization/serial-arch.html#serialization-of-records">
   * <cite>Java Object Serialization Specification,</cite> Section 1.13,
   * "Serialization of Records"</a> for additional information.
   *
+  * <p>Value classes are {@linkplain Serializable} through the use of the serialization proxy pattern.
+  * See {@linkplain ObjectOutputStream##valueclass-serialization value class serialization} for details.
+  * When the proxy is deserialized it re-constructs and returns the value object.
+  *
   * @spec serialization/index.html Java Object Serialization Specification
   * @author      Mike Warres
   * @author      Roger Riggs
   * @see java.io.DataInput
   * @see java.io.ObjectOutputStream

*** 255,10 ***
--- 265,20 ---
   * @since   1.1
   */
  public class ObjectInputStream
      extends InputStream implements ObjectInput, ObjectStreamConstants
  {
+     private static final String TRACE_DEST =
+             GetPropertyAction.privilegedGetProperty("TRACE");
+ 
+     static void TRACE(String format, Object... args) {
+         if (TRACE_DEST != null) {
+             var ps = "OUT".equals(TRACE_DEST.toUpperCase(Locale.ROOT)) ? System.out : System.err;
+             ps.println(("TRACE " + format).formatted(args));
+         }
+     }
+ 
      /** handle value representing null */
      private static final int NULL_HANDLE = -1;
  
      /** marker for unshared objects in internal handle table */
      private static final Object unsharedMarker = new Object();

*** 464,10 ***
--- 484,18 ---
       *
       * <p>The deserialization filter, when not {@code null}, is invoked for
       * each object (regular or class) read to reconstruct the root object.
       * See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
       *
+      * <p>Serialization and deserialization of value classes is described in
+      * {@linkplain ObjectOutputStream##valueclass-serialization value class serialization}.
+      *
+      * @implSpec
+      * When enabled with {@code --enable-preview}, serialization and deserialization of
+      * Core Library value classes migrated from pre-JEP 401 identity classes is
+      * implementation specific.
+      *
       * <p>Exceptions are thrown for problems with the InputStream and for
       * classes that should not be deserialized.  All exceptions are fatal to
       * the InputStream and leave it in an indeterminate state; it is up to the
       * caller to ignore or recover the stream state.
       *

*** 597,10 ***
--- 625,13 ---
       *
       * <p>The deserialization filter, when not {@code null}, is invoked for
       * each object (regular or class) read to reconstruct the root object.
       * See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
       *
+      * <p>Serialization and deserialization of value classes is described in
+      * {@linkplain ObjectOutputStream##valueclass-serialization value class serialization}.
+      *
       * <p>ObjectInputStream subclasses which override this method can only be
       * constructed in security contexts possessing the
       * "enableSubclassImplementation" SerializablePermission; any attempt to
       * instantiate such a subclass without this permission will cause a
       * SecurityException to be thrown.

*** 2250,71 ***
          if (cl == String.class || cl == Class.class
                  || cl == ObjectStreamClass.class) {
              throw new InvalidClassException("invalid class descriptor");
          }
  
!         Object obj;
!         try {
-             obj = desc.isInstantiable() ? desc.newInstance() : null;
-         } catch (Exception ex) {
-             throw new InvalidClassException(desc.forClass().getName(),
-                                             "unable to create instance", ex);
-         }
- 
-         passHandle = handles.assign(unshared ? unsharedMarker : obj);
          ClassNotFoundException resolveEx = desc.getResolveException();
          if (resolveEx != null) {
              handles.markException(passHandle, resolveEx);
          }
  
!         final boolean isRecord = desc.isRecord();
!         if (isRecord) {
!             assert obj == null;
!             obj = readRecord(desc);
!             if (!unshared)
!                 handles.setObject(passHandle, obj);
!         } else if (desc.isExternalizable()) {
!             readExternalData((Externalizable) obj, desc);
!         } else {
!             readSerialData(obj, desc);
!         }
  
!         handles.finish(passHandle);
  
!         if (obj != null &&
!             handles.lookupException(passHandle) == null &&
!             desc.hasReadResolveMethod())
!         {
!             Object rep = desc.invokeReadResolve(obj);
!             if (unshared && rep.getClass().isArray()) {
!                 rep = cloneArray(rep);
!             }
!             if (rep != obj) {
!                 // Filter the replacement object
!                 if (rep != null) {
!                     if (rep.getClass().isArray()) {
!                         filterCheck(rep.getClass(), Array.getLength(rep));
!                     } else {
!                         filterCheck(rep.getClass(), -1);
                      }
                  }
!                 handles.setObject(passHandle, obj = rep);
              }
          }
  
!         return obj;
      }
  
      /**
!      * If obj is non-null, reads externalizable data by invoking readExternal()
       * method of obj; otherwise, attempts to skip over externalizable data.
       * Expects that passHandle is set to obj's handle before this method is
!      * called.
       */
!     private void readExternalData(Externalizable obj, ObjectStreamClass desc)
          throws IOException
      {
          SerialCallbackContext oldContext = curContext;
          if (oldContext != null)
              oldContext.check();
          curContext = null;
          try {
--- 2281,134 ---
          if (cl == String.class || cl == Class.class
                  || cl == ObjectStreamClass.class) {
              throw new InvalidClassException("invalid class descriptor");
          }
  
!         // Assign the handle and initially set to null or the unsharedMarker
!         passHandle = handles.assign(unshared ? unsharedMarker : null);
          ClassNotFoundException resolveEx = desc.getResolveException();
          if (resolveEx != null) {
              handles.markException(passHandle, resolveEx);
          }
  
!         try {
!             // Dispatch on the factory mode to read an object from the stream.
!             Object obj = switch (desc.factoryMode()) {
!                 case READ_OBJECT_DEFAULT -> readSerialDefaultObject(desc, unshared);
!                 case READ_OBJECT_CUSTOM -> readSerialCustomData(desc, unshared);
!                 case READ_RECORD -> readRecord(desc, unshared);
!                 case READ_EXTERNALIZABLE -> readExternalObject(desc, unshared);
!                 case READ_OBJECT_VALUE -> readObjectValue(desc, unshared);
!                 case READ_NO_LOCAL_CLASS -> readAbsentLocalClass(desc, unshared);
!                 case null -> throw new AssertionError("Unknown factoryMode for: " + desc.getName(),
!                         resolveEx);
+             };
  
!             handles.finish(passHandle);
  
!             if (obj != null &&
!                 handles.lookupException(passHandle) == null &&
!                 desc.hasReadResolveMethod())
!             {
!                 Object rep = desc.invokeReadResolve(obj);
!                 if (unshared && rep.getClass().isArray()) {
!                     rep = cloneArray(rep);
!                 }
!                 if (rep != obj) {
!                     // Filter the replacement object
!                     if (rep != null) {
!                         if (rep.getClass().isArray()) {
!                             filterCheck(rep.getClass(), Array.getLength(rep));
!                         } else {
!                             filterCheck(rep.getClass(), -1);
+                         }
                      }
+                     handles.setObject(passHandle, obj = rep);
                  }
!             }
+ 
+             return obj;
+         } catch (UncheckedIOException uioe) {
+             // Consistent re-throw for nested UncheckedIOExceptions
+             throw uioe.getCause();
+         }
+     }
+ 
+     /**
+      * {@return a value class instance by invoking its constructor with field values read from the stream.
+      * The fields of the class in the stream are matched to the local fields and applied to
+      * the constructor.
+      * If the stream contains superclasses with serializable fields,
+      * an InvalidClassException is thrown with an incompatible class change message.
+      *
+      * @param desc the class descriptor read from the stream, the local class is a value class
+      * @param unshared if the object is not to be shared
+      * @throws InvalidClassException if the stream contains a superclass with serializable fields.
+      * @throws IOException if there are I/O errors while reading from the
+      *         underlying {@code InputStream}
+      */
+     private Object readObjectValue(ObjectStreamClass desc, boolean unshared) throws IOException {
+         final ObjectStreamClass localDesc = desc.getLocalDesc();
+         TRACE("readObjectValue: %s, local class: %s", desc.getName(), localDesc.getName());
+         // Check for un-expected fields in superclasses
+         List<ClassDataSlot> slots = desc.getClassDataLayout();
+         for (int i = 0; i < slots.size()-1; i++) {
+             ClassDataSlot slot = slots.get(i);
+             if (slot.hasData && slot.desc.getFields(false).length > 0) {
+                 throw new InvalidClassException("incompatible class change to value class: " +
+                         "stream class has non-empty super type: " + desc.getName());
              }
          }
+         // Read values for the value class fields
+         FieldValues fieldValues = new FieldValues(desc, true);
  
!         // Get value object constructor adapted to take primitive value buffer and object array.
+         MethodHandle consMH = ConstructorSupport.deserializationValueCons(desc);
+         try {
+             Object obj = (Object) consMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
+             if (!unshared)
+                 handles.setObject(passHandle, obj);
+             return obj;
+         } catch (Exception e) {
+             throw new InvalidObjectException(e.getMessage(), e);
+         } catch (Error e) {
+             throw e;
+         } catch (Throwable t) {
+             throw new InvalidObjectException("ReflectiveOperationException " +
+                     "during deserialization", t);
+         }
      }
  
      /**
!      * Creates a new object and invokes its readExternal method to read its contents.
+      *
+      * If the class is instantiable, read externalizable data by invoking readExternal()
       * method of obj; otherwise, attempts to skip over externalizable data.
       * Expects that passHandle is set to obj's handle before this method is
!      * called.  The new object is entered in the handle table immediately,
+      * allowing it to leak before it is completely read.
       */
!     private Object readExternalObject(ObjectStreamClass desc, boolean unshared)
          throws IOException
      {
+         TRACE("readExternalObject: %s", desc.getName());
+ 
+         // For Externalizable objects,
+         // create the instance, publish the ref, and read the data
+         Externalizable obj = null;
+         try {
+             if (desc.isInstantiable()) {
+                 obj = (Externalizable) desc.newInstance();
+             }
+         } catch (Exception ex) {
+             throw new InvalidClassException(desc.getName(),
+                     "unable to create instance", ex);
+         }
+ 
+         if (!unshared)
+             handles.setObject(passHandle, obj);
+ 
          SerialCallbackContext oldContext = curContext;
          if (oldContext != null)
              oldContext.check();
          curContext = null;
          try {

*** 2354,26 ***
           * we mimic the behavior of past serialization implementations and
           * blindly hope that the stream is in sync; if it isn't and additional
           * externalizable data remains in the stream, a subsequent read will
           * most likely throw a StreamCorruptedException.
           */
      }
  
      /**
       * Reads and returns a record.
       * If an exception is marked for any of the fields, the dependency
       * mechanism marks the record as having an exception.
       * Null is returned from readRecord and later the exception is thrown at
       * the exit of {@link #readObject(Class)}.
!      **/
!     private Object readRecord(ObjectStreamClass desc) throws IOException {
!         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
!         if (slots.length != 1) {
              // skip any superclass stream field values
!             for (int i = 0; i < slots.length-1; i++) {
!                 if (slots[i].hasData) {
!                     new FieldValues(slots[i].desc, true);
                  }
              }
          }
  
          FieldValues fieldValues = new FieldValues(desc, true);
--- 2448,28 ---
           * we mimic the behavior of past serialization implementations and
           * blindly hope that the stream is in sync; if it isn't and additional
           * externalizable data remains in the stream, a subsequent read will
           * most likely throw a StreamCorruptedException.
           */
+         return obj;
      }
  
      /**
       * Reads and returns a record.
       * If an exception is marked for any of the fields, the dependency
       * mechanism marks the record as having an exception.
       * Null is returned from readRecord and later the exception is thrown at
       * the exit of {@link #readObject(Class)}.
!      */
!     private Object readRecord(ObjectStreamClass desc, boolean unshared) throws IOException {
!         TRACE("invoking readRecord: %s", desc.getName());
!         List<ClassDataSlot> slots = desc.getClassDataLayout();
+         if (slots.size() != 1) {
              // skip any superclass stream field values
!             for (int i = 0; i < slots.size()-1; i++) {
!                 if (slots.get(i).hasData) {
!                     new FieldValues(slots.get(i).desc, true);
                  }
              }
          }
  
          FieldValues fieldValues = new FieldValues(desc, true);

*** 2383,14 ***
  
          // get canonical record constructor adapted to take two arguments:
          // - byte[] primValues
          // - Object[] objValues
          // and return Object
!         MethodHandle ctrMH = RecordSupport.deserializationCtr(desc);
  
          try {
!             return (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
          } catch (Exception e) {
              throw new InvalidObjectException(e.getMessage(), e);
          } catch (Error e) {
              throw e;
          } catch (Throwable t) {
--- 2479,17 ---
  
          // get canonical record constructor adapted to take two arguments:
          // - byte[] primValues
          // - Object[] objValues
          // and return Object
!         MethodHandle ctrMH = ConstructorSupport.deserializationCtr(desc);
  
          try {
!             Object obj = (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
+             if (!unshared)
+                 handles.setObject(passHandle, obj);
+             return obj;
          } catch (Exception e) {
              throw new InvalidObjectException(e.getMessage(), e);
          } catch (Error e) {
              throw e;
          } catch (Throwable t) {

*** 2398,118 ***
                                               "during deserialization", t);
          }
      }
  
      /**
!      * Reads (or attempts to skip, if obj is null or is tagged with a
       * ClassNotFoundException) instance data for each serializable class of
!      * object in stream, from superclass to subclass.  Expects that passHandle
!      * is set to obj's handle before this method is called.
       */
!     private void readSerialData(Object obj, ObjectStreamClass desc)
          throws IOException
      {
!         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
!         // Best effort Failure Atomicity; slotValues will be non-null if field
!         // values can be set after reading all field data in the hierarchy.
-         // Field values can only be set after reading all data if there are no
-         // user observable methods in the hierarchy, readObject(NoData). The
-         // top most Serializable class in the hierarchy can be skipped.
-         FieldValues[] slotValues = null;
- 
-         boolean hasSpecialReadMethod = false;
-         for (int i = 1; i < slots.length; i++) {
-             ObjectStreamClass slotDesc = slots[i].desc;
-             if (slotDesc.hasReadObjectMethod()
-                   || slotDesc.hasReadObjectNoDataMethod()) {
-                 hasSpecialReadMethod = true;
-                 break;
-             }
          }
-         // No special read methods, can store values and defer setting.
-         if (!hasSpecialReadMethod)
-             slotValues = new FieldValues[slots.length];
  
!         for (int i = 0; i < slots.length; i++) {
!             ObjectStreamClass slotDesc = slots[i].desc;
! 
!             if (slots[i].hasData) {
!                 if (obj == null || handles.lookupException(passHandle) != null) {
!                     // Read fields of the current descriptor into a new FieldValues and discard
!                     new FieldValues(slotDesc, true);
!                 } else if (slotDesc.hasReadObjectMethod()) {
!                     SerialCallbackContext oldContext = curContext;
!                     if (oldContext != null)
!                         oldContext.check();
!                     try {
-                         curContext = new SerialCallbackContext(obj, slotDesc);
  
!                         bin.setBlockDataMode(true);
!                         slotDesc.invokeReadObject(obj, this);
!                     } catch (ClassNotFoundException ex) {
!                         /*
!                          * In most cases, the handle table has already
!                          * propagated a CNFException to passHandle at this
!                          * point; this mark call is included to address cases
!                          * where the custom readObject method has cons'ed and
!                          * thrown a new CNFException of its own.
!                          */
!                         handles.markException(passHandle, ex);
!                     } finally {
!                         curContext.setUsed();
!                         if (oldContext!= null)
-                             oldContext.check();
-                         curContext = oldContext;
-                     }
  
!                     /*
!                      * defaultDataEnd may have been set indirectly by custom
!                      * readObject() method when calling defaultReadObject() or
!                      * readFields(); clear it to restore normal read behavior.
!                      */
!                     defaultDataEnd = false;
                  } else {
                      // Read fields of the current descriptor into a new FieldValues
                      FieldValues values = new FieldValues(slotDesc, true);
!                     if (slotValues != null) {
!                         slotValues[i] = values;
!                     } else if (obj != null) {
!                         if (handles.lookupException(passHandle) == null) {
-                             // passHandle NOT marked with an exception; set field values
-                             values.defaultCheckFieldValues(obj);
-                             values.defaultSetFieldValues(obj);
-                         }
                      }
!                 }
- 
-                 if (slotDesc.hasWriteObjectData()) {
-                     skipCustomData();
-                 } else {
-                     bin.setBlockDataMode(false);
                  }
              } else {
!                 if (obj != null &&
!                     slotDesc.hasReadObjectNoDataMethod() &&
-                     handles.lookupException(passHandle) == null)
-                 {
                      slotDesc.invokeReadObjectNoData(obj);
                  }
              }
          }
  
!         if (obj != null && slotValues != null && handles.lookupException(passHandle) == null) {
!             // passHandle NOT marked with an exception
!             // Check that the non-primitive types are assignable for all slots
!             // before assigning.
!             for (int i = 0; i < slots.length; i++) {
!                 if (slotValues[i] != null)
!                     slotValues[i].defaultCheckFieldValues(obj);
!             }
!             for (int i = 0; i < slots.length; i++) {
!                 if (slotValues[i] != null)
!                     slotValues[i].defaultSetFieldValues(obj);
              }
          }
      }
  
      /**
       * Skips over all block data and objects until TC_ENDBLOCKDATA is
--- 2497,211 ---
                                               "during deserialization", t);
          }
      }
  
      /**
!      * Construct an object from the stream for a class that has only default read object behaviors.
+      * For each object, the fields are read before any are assigned.
+      * The new instance is entered in the handle table if it is unshared,
+      * allowing it to escape before it is initialized.
+      * The `readObject` and `readObjectNoData` methods are not present and are not called.
+      *
+      * @param desc the class descriptor
+      * @param unshared true if the object should be shared
+      * @return the object constructed from the stream data
+      * @throws IOException if there are I/O errors while reading from the
+      *         underlying {@code InputStream}
+      * @throws InvalidClassException if the instance creation fails
+      */
+     private Object readSerialDefaultObject(ObjectStreamClass desc, boolean unshared)
+             throws IOException, InvalidClassException {
+         if (!desc.isInstantiable()) {
+             // No local class to create, read and discard
+             return readAbsentLocalClass(desc, unshared);
+         }
+         TRACE("readSerialDefaultObject: %s", desc.getName());
+         try {
+             final Object obj = desc.newInstance();
+             if (!unshared)
+                 handles.setObject(passHandle, obj);
+ 
+             // Best effort Failure Atomicity; slotValues will be non-null if field
+             // values can be set after reading all field data in the hierarchy.
+             List<FieldValues> slotValues = desc.getClassDataLayout().stream()
+                     .filter(s -> s.hasData)
+                     .map(s1 -> {
+                         var values = new FieldValues(s1.desc, true);
+                         finishBlockData(s1.desc);
+                         return values;
+                     })
+                     .toList();
+ 
+             if (handles.lookupException(passHandle) != null) {
+                 return null;    // some exception for a class, do not return the object
+             }
+ 
+             // Check that the types are assignable for all slots before assigning.
+             slotValues.forEach(v -> v.defaultCheckFieldValues(obj));
+             slotValues.forEach(v -> v.defaultSetFieldValues(obj));
+             return obj;
+         } catch (InstantiationException | InvocationTargetException ex) {
+             throw new InvalidClassException(desc.forClass().getName(),
+                     "unable to create instance", ex);
+         }
+     }
+ 
+ 
+     /**
+      * Reads (or attempts to skip, if not instantiatable or is tagged with a
       * ClassNotFoundException) instance data for each serializable class of
!      * object in stream, from superclass to subclass.
!      * Expects that passHandle is set to current handle before this method is called.
       */
!     private Object readSerialCustomData(ObjectStreamClass desc, boolean unshared)
          throws IOException
      {
!         if (!desc.isInstantiable()) {
!             // No local class to create, read and discard
!             return readAbsentLocalClass(desc, unshared);
          }
  
!         TRACE("readSerialCustomData: %s, ex: %s", desc.getName(), handles.lookupException(passHandle));
!         try {
!             Object obj = desc.newInstance();
!             if (!unshared)
!                 handles.setObject(passHandle, obj);
!             // Read data into each of the slots for the class
!             return readSerialCustomSlots(obj, desc.getClassDataLayout());
!         } catch (InstantiationException | InvocationTargetException ex) {
!             throw new InvalidClassException(desc.forClass().getName(),
!                     "unable to create instance", ex);
!         }
!     }
  
!     /**
!      * Reads from the stream using custom or default readObject methods appropriate.
!      * For each slot, either the custom readObject method or the default reader of fields
!      * is invoked. Unused slot specific custom data is discarded.
!      * This function is used by {@link #readSerialCustomData}.
!      *
!      * @param obj the object to assign the values to
!      * @param slots a list of slots to read from the stream
!      * @return the object being initialized
!      * @throws IOException if there are I/O errors while reading from the
!      *         underlying {@code InputStream}
!      */
!     private Object readSerialCustomSlots(Object obj, List<ClassDataSlot> slots) throws IOException {
!         TRACE("    readSerialCustomSlots: %s", slots);
  
!         for (ClassDataSlot slot : slots) {
!             ObjectStreamClass slotDesc = slot.desc;
!             if (slot.hasData) {
!                 if (slotDesc.hasReadObjectMethod() &&
!                         handles.lookupException(passHandle) == null) {
!                     // Invoke slot custom readObject method
+                     readSlotViaReadObject(obj, slotDesc);
                  } else {
                      // Read fields of the current descriptor into a new FieldValues
                      FieldValues values = new FieldValues(slotDesc, true);
!                     if (handles.lookupException(passHandle) == null) {
!                         // Set the instance fields if no previous exception
!                         values.defaultCheckFieldValues(obj);
!                         values.defaultSetFieldValues(obj);
                      }
!                     finishBlockData(slotDesc);
                  }
              } else {
!                 if (slotDesc.hasReadObjectNoDataMethod() &&
!                         handles.lookupException(passHandle) == null) {
                      slotDesc.invokeReadObjectNoData(obj);
                  }
              }
          }
+         return obj;
+     }
  
!     /**
!      * Invoke the readObject method of the class to read and store the state from the stream.
!      *
!      * @param obj an instance of the class being created, only partially initialized.
!      * @param slotDesc the ObjectStreamDescriptor for the current class
!      * @throws IOException if there are I/O errors while reading from the
!      *         underlying {@code InputStream}
!      */
!     private void readSlotViaReadObject(Object obj, ObjectStreamClass slotDesc) throws IOException {
!         TRACE("readSlotViaReadObject: %s", slotDesc.getName());
!         assert obj != null : "readSlotViaReadObject called when obj == null";
+ 
+         SerialCallbackContext oldContext = curContext;
+         if (oldContext != null)
+             oldContext.check();
+         try {
+             curContext = new SerialCallbackContext(obj, slotDesc);
+ 
+             bin.setBlockDataMode(true);
+             slotDesc.invokeReadObject(obj, this);
+         } catch (ClassNotFoundException ex) {
+             /*
+              * In most cases, the handle table has already
+              * propagated a CNFException to passHandle at this
+              * point; this mark call is included to address cases
+              * where the custom readObject method has cons'ed and
+              * thrown a new CNFException of its own.
+              */
+             handles.markException(passHandle, ex);
+         } finally {
+             curContext.setUsed();
+             if (oldContext!= null)
+                 oldContext.check();
+             curContext = oldContext;
+         }
+ 
+         /*
+          * defaultDataEnd may have been set indirectly by custom
+          * readObject() method when calling defaultReadObject() or
+          * readFields(); clear it to restore normal read behavior.
+          */
+         defaultDataEnd = false;
+ 
+         finishBlockData(slotDesc);
+     }
+ 
+ 
+     /**
+      * Read and discard an entire object, leaving a null reference in the HandleTable.
+      * The descriptor of the class in the stream is used to read the fields from the stream.
+      * There is no instance in which to store the field values.
+      * Custom data following the fields of any slot is read and discarded.
+      * References to nested objects are read and retained in the
+      * handle table using the regular mechanism.
+      * Handles later in the stream may refer to the nested objects.
+      *
+      * @param desc the stream class descriptor
+      * @param unshared the unshared flag, ignored since no object is created
+      * @return null, no object is created
+      * @throws IOException if there are I/O errors while reading from the
+      *         underlying {@code InputStream}
+      */
+     private Object readAbsentLocalClass(ObjectStreamClass desc, boolean unshared)
+             throws IOException {
+         TRACE("readAbsentLocalClass: %s", desc.getName());
+         desc.getClassDataLayout().stream()
+                 .filter(s -> s.hasData)
+                 .forEach(s2 -> {new FieldValues(s2.desc, true); finishBlockData(s2.desc);});
+         return null;
+     }
+ 
+     // Finish handling of block data by skipping any remaining and setting BlockDataMode = false
+     private void finishBlockData(ObjectStreamClass slotDesc) throws UncheckedIOException {
+         try {
+             if (slotDesc.hasWriteObjectData()) {
+                 skipCustomData();
+             } else {
+                 bin.setBlockDataMode(false);
              }
+         } catch (IOException ioe) {
+             throw new UncheckedIOException(ioe);
          }
      }
  
      /**
       * Skips over all block data and objects until TC_ENDBLOCKDATA is

*** 2601,36 ***
           * Creates FieldValues object for reading fields defined in given
           * class descriptor.
           * @param desc the ObjectStreamClass to read
           * @param recordDependencies if true, record the dependencies
           *                           from current PassHandle and the object's read.
           */
!         FieldValues(ObjectStreamClass desc, boolean recordDependencies) throws IOException {
!             this.desc = desc;
  
-             int primDataSize = desc.getPrimDataSize();
-             primValues = (primDataSize > 0) ? new byte[primDataSize] : null;
-             if (primDataSize > 0) {
-                 bin.readFully(primValues, 0, primDataSize, false);
-             }
  
!             int numObjFields = desc.getNumObjFields();
!             objValues = (numObjFields > 0) ? new Object[numObjFields] : null;
!             objHandles = (numObjFields > 0) ? new int[numObjFields] : null;
!             if (numObjFields > 0) {
!                 int objHandle = passHandle;
!                 ObjectStreamField[] fields = desc.getFields(false);
!                 int numPrimFields = fields.length - objValues.length;
!                 for (int i = 0; i < objValues.length; i++) {
!                     ObjectStreamField f = fields[numPrimFields + i];
!                     objValues[i] = readObject0(Object.class, f.isUnshared());
!                     objHandles[i] = passHandle;
!                     if (recordDependencies && f.getField() != null) {
!                         handles.markDependency(objHandle, passHandle);
                      }
                  }
!                 passHandle = objHandle;
              }
          }
  
          public ObjectStreamClass getObjectStreamClass() {
              return desc;
--- 2793,42 ---
           * Creates FieldValues object for reading fields defined in given
           * class descriptor.
           * @param desc the ObjectStreamClass to read
           * @param recordDependencies if true, record the dependencies
           *                           from current PassHandle and the object's read.
+          * @throws UncheckedIOException if any IOException occurs
           */
!         FieldValues(ObjectStreamClass desc, boolean recordDependencies) throws UncheckedIOException {
!             try {
+                 this.desc = desc;
+                 TRACE("    reading FieldValues: %s", desc.getName());
+                 int primDataSize = desc.getPrimDataSize();
+                 primValues = (primDataSize > 0) ? new byte[primDataSize] : null;
+                 if (primDataSize > 0) {
+                     bin.readFully(primValues, 0, primDataSize, false);
+                 }
  
  
!                 int numObjFields = desc.getNumObjFields();
!                 objValues = (numObjFields > 0) ? new Object[numObjFields] : null;
!                 objHandles = (numObjFields > 0) ? new int[numObjFields] : null;
!                 if (numObjFields > 0) {
!                     int objHandle = passHandle;
!                     ObjectStreamField[] fields = desc.getFields(false);
!                     int numPrimFields = fields.length - objValues.length;
!                     for (int i = 0; i < objValues.length; i++) {
!                         ObjectStreamField f = fields[numPrimFields + i];
!                         objValues[i] = readObject0(Object.class, f.isUnshared());
!                         objHandles[i] = passHandle;
!                         if (recordDependencies && f.getField() != null) {
!                             handles.markDependency(objHandle, passHandle);
+                         }
                      }
+                     passHandle = objHandle;
                  }
!             } catch (IOException ioe) {
+                 throw new UncheckedIOException(ioe);
              }
          }
  
          public ObjectStreamClass getObjectStreamClass() {
              return desc;
< prev index next >