< prev index next > src/java.base/share/classes/java/io/ObjectInputStream.java
Print this page
*/
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.
*
*/
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.
*
* 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
* 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
* @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();
*
* <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.
*
*
* <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.
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 {
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 {
* 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);
* 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);
// 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) {
// 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) {
"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
"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
* 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;
* 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 >