9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.io;
27
28 import java.io.ObjectInputFilter.Config;
29 import java.io.ObjectStreamClass.RecordSupport;
30 import java.lang.System.Logger;
31 import java.lang.invoke.MethodHandle;
32 import java.lang.reflect.Array;
33 import java.lang.reflect.InvocationHandler;
34 import java.lang.reflect.Modifier;
35 import java.lang.reflect.Proxy;
36 import java.nio.charset.StandardCharsets;
37 import java.util.Arrays;
38 import java.util.Objects;
39
40 import jdk.internal.access.JavaLangAccess;
41 import jdk.internal.access.SharedSecrets;
42 import jdk.internal.event.DeserializationEvent;
43 import jdk.internal.misc.Unsafe;
44 import jdk.internal.util.ByteArray;
45
46 /**
47 * An ObjectInputStream deserializes primitive data and objects previously
48 * written using an ObjectOutputStream.
49 *
50 * <p><strong>Warning: Deserialization of untrusted data is inherently dangerous
51 * and should be avoided. Untrusted data should be carefully validated according to the
52 * "Serialization and Deserialization" section of the
53 * {@extLink secure_coding_guidelines_javase Secure Coding Guidelines for Java SE}.
54 * {@extLink serialization_filter_guide Serialization Filtering} describes best
55 * practices for defensive use of serial filters.
56 * </strong></p>
57 *
193 * <p>Serialization does not read or assign values to the fields of any object
194 * that does not implement the java.io.Serializable interface. Subclasses of
195 * Objects that are not serializable can be serializable. In this case the
196 * non-serializable class must have a no-arg constructor to allow its fields to
197 * be initialized. In this case it is the responsibility of the subclass to
198 * save and restore the state of the non-serializable class. It is frequently
199 * the case that the fields of that class are accessible (public, package, or
200 * protected) or that there are get and set methods that can be used to restore
201 * the state.
202 *
203 * <p>Any exception that occurs while deserializing an object will be caught by
204 * the ObjectInputStream and abort the reading process.
205 *
206 * <p>Implementing the Externalizable interface allows the object to assume
207 * complete control over the contents and format of the object's serialized
208 * form. The methods of the Externalizable interface, writeExternal and
209 * readExternal, are called to save and restore the objects state. When
210 * implemented by a class they can write and read their own state using all of
211 * the methods of ObjectOutput and ObjectInput. It is the responsibility of
212 * the objects to handle any versioning that occurs.
213 *
214 * <p>Enum constants are deserialized differently than ordinary serializable or
215 * externalizable objects. The serialized form of an enum constant consists
216 * solely of its name; field values of the constant are not transmitted. To
217 * deserialize an enum constant, ObjectInputStream reads the constant name from
218 * the stream; the deserialized constant is then obtained by calling the static
219 * method {@code Enum.valueOf(Class, String)} with the enum constant's
220 * base type and the received constant name as arguments. Like other
221 * serializable or externalizable objects, enum constants can function as the
222 * targets of back references appearing subsequently in the serialization
223 * stream. The process by which enum constants are deserialized cannot be
224 * customized: any class-specific readObject, readObjectNoData, and readResolve
225 * methods defined by enum types are ignored during deserialization.
226 * Similarly, any serialPersistentFields or serialVersionUID field declarations
227 * are also ignored--all enum types have a fixed serialVersionUID of 0L.
228 *
229 * <a id="record-serialization"></a>
230 * <p>Records are serialized differently than ordinary serializable or externalizable
231 * objects. During deserialization the record's canonical constructor is invoked
232 * to construct the record object. Certain serialization-related methods, such
233 * as readObject and writeObject, are ignored for serializable records. See
234 * <a href="{@docRoot}/../specs/serialization/serial-arch.html#serialization-of-records">
235 * <cite>Java Object Serialization Specification,</cite> Section 1.13,
236 * "Serialization of Records"</a> for additional information.
237 *
238 * @spec serialization/index.html Java Object Serialization Specification
239 * @author Mike Warres
240 * @author Roger Riggs
241 * @see java.io.DataInput
242 * @see java.io.ObjectOutputStream
243 * @see java.io.Serializable
244 * @see <a href="{@docRoot}/../specs/serialization/input.html">
245 * <cite>Java Object Serialization Specification,</cite> Section 3, "Object Input Classes"</a>
246 * @since 1.1
247 */
248 public class ObjectInputStream
249 extends InputStream implements ObjectInput, ObjectStreamConstants
250 {
251 /** handle value representing null */
252 private static final int NULL_HANDLE = -1;
253
254 /** marker for unshared objects in internal handle table */
255 private static final Object unsharedMarker = new Object();
256
257 private static class Caches {
258 /** cache of subclass security audit results */
259 static final ClassValue<Boolean> subclassAudits =
260 new ClassValue<>() {
261 @Override
262 protected Boolean computeValue(Class<?> type) {
263 return auditSubclass(type);
264 }
265 };
266
267 /**
268 * Property to permit setting a filter after objects
269 * have been read.
270 * See {@link #setObjectInputFilter(ObjectInputFilter)}
416
417 /**
418 * Read an object from the ObjectInputStream. The class of the object, the
419 * signature of the class, and the values of the non-transient and
420 * non-static fields of the class and all of its supertypes are read.
421 * Default deserializing for a class can be overridden using the writeObject
422 * and readObject methods. Objects referenced by this object are read
423 * transitively so that a complete equivalent graph of objects is
424 * reconstructed by readObject.
425 *
426 * <p>The root object is completely restored when all of its fields and the
427 * objects it references are completely restored. At this point the object
428 * validation callbacks are executed in order based on their registered
429 * priorities. The callbacks are registered by objects (in the readObject
430 * special methods) as they are individually restored.
431 *
432 * <p>The deserialization filter, when not {@code null}, is invoked for
433 * each object (regular or class) read to reconstruct the root object.
434 * See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
435 *
436 * <p>Exceptions are thrown for problems with the InputStream and for
437 * classes that should not be deserialized. All exceptions are fatal to
438 * the InputStream and leave it in an indeterminate state; it is up to the
439 * caller to ignore or recover the stream state.
440 *
441 * @throws ClassNotFoundException Class of a serialized object cannot be
442 * found.
443 * @throws InvalidClassException Something is wrong with a class used by
444 * deserialization.
445 * @throws StreamCorruptedException Control information in the
446 * stream is inconsistent.
447 * @throws OptionalDataException Primitive data was found in the
448 * stream instead of objects.
449 * @throws IOException Any of the usual Input/Output related exceptions.
450 */
451 public final Object readObject()
452 throws IOException, ClassNotFoundException {
453 return readObject(Object.class);
454 }
455
2123 * class is unresolvable (in which case a ClassNotFoundException will be
2124 * associated with object's handle). Sets passHandle to object's assigned
2125 * handle.
2126 */
2127 private Object readOrdinaryObject(boolean unshared)
2128 throws IOException
2129 {
2130 if (bin.readByte() != TC_OBJECT) {
2131 throw new InternalError();
2132 }
2133
2134 ObjectStreamClass desc = readClassDesc(false);
2135 desc.checkDeserialize();
2136
2137 Class<?> cl = desc.forClass();
2138 if (cl == String.class || cl == Class.class
2139 || cl == ObjectStreamClass.class) {
2140 throw new InvalidClassException("invalid class descriptor");
2141 }
2142
2143 Object obj;
2144 try {
2145 obj = desc.isInstantiable() ? desc.newInstance() : null;
2146 } catch (Exception ex) {
2147 throw new InvalidClassException(desc.forClass().getName(),
2148 "unable to create instance", ex);
2149 }
2150
2151 passHandle = handles.assign(unshared ? unsharedMarker : obj);
2152 ClassNotFoundException resolveEx = desc.getResolveException();
2153 if (resolveEx != null) {
2154 handles.markException(passHandle, resolveEx);
2155 }
2156
2157 final boolean isRecord = desc.isRecord();
2158 if (isRecord) {
2159 assert obj == null;
2160 obj = readRecord(desc);
2161 if (!unshared)
2162 handles.setObject(passHandle, obj);
2163 } else if (desc.isExternalizable()) {
2164 readExternalData((Externalizable) obj, desc);
2165 } else {
2166 readSerialData(obj, desc);
2167 }
2168
2169 handles.finish(passHandle);
2170
2171 if (obj != null &&
2172 handles.lookupException(passHandle) == null &&
2173 desc.hasReadResolveMethod())
2174 {
2175 Object rep = desc.invokeReadResolve(obj);
2176 if (unshared && rep.getClass().isArray()) {
2177 rep = cloneArray(rep);
2178 }
2179 if (rep != obj) {
2180 // Filter the replacement object
2181 if (rep != null) {
2182 if (rep.getClass().isArray()) {
2183 filterCheck(rep.getClass(), Array.getLength(rep));
2184 } else {
2185 filterCheck(rep.getClass(), -1);
2186 }
2187 }
2188 handles.setObject(passHandle, obj = rep);
2189 }
2190 }
2191
2192 return obj;
2193 }
2194
2195 /**
2196 * If obj is non-null, reads externalizable data by invoking readExternal()
2197 * method of obj; otherwise, attempts to skip over externalizable data.
2198 * Expects that passHandle is set to obj's handle before this method is
2199 * called.
2200 */
2201 private void readExternalData(Externalizable obj, ObjectStreamClass desc)
2202 throws IOException
2203 {
2204 SerialCallbackContext oldContext = curContext;
2205 if (oldContext != null)
2206 oldContext.check();
2207 curContext = null;
2208 try {
2209 boolean blocked = desc.hasBlockExternalData();
2210 if (blocked) {
2211 bin.setBlockDataMode(true);
2212 }
2213 if (obj != null) {
2214 try {
2215 obj.readExternal(this);
2216 } catch (ClassNotFoundException ex) {
2217 /*
2218 * In most cases, the handle table has already propagated
2219 * a CNFException to passHandle at this point; this mark
2220 * call is included to address cases where the readExternal
2221 * method has cons'ed and thrown a new CNFException of its
2222 * own.
2223 */
2227 if (blocked) {
2228 skipCustomData();
2229 }
2230 } finally {
2231 if (oldContext != null)
2232 oldContext.check();
2233 curContext = oldContext;
2234 }
2235 /*
2236 * At this point, if the externalizable data was not written in
2237 * block-data form and either the externalizable class doesn't exist
2238 * locally (i.e., obj == null) or readExternal() just threw a
2239 * CNFException, then the stream is probably in an inconsistent state,
2240 * since some (or all) of the externalizable data may not have been
2241 * consumed. Since there's no "correct" action to take in this case,
2242 * we mimic the behavior of past serialization implementations and
2243 * blindly hope that the stream is in sync; if it isn't and additional
2244 * externalizable data remains in the stream, a subsequent read will
2245 * most likely throw a StreamCorruptedException.
2246 */
2247 }
2248
2249 /**
2250 * Reads and returns a record.
2251 * If an exception is marked for any of the fields, the dependency
2252 * mechanism marks the record as having an exception.
2253 * Null is returned from readRecord and later the exception is thrown at
2254 * the exit of {@link #readObject(Class)}.
2255 **/
2256 private Object readRecord(ObjectStreamClass desc) throws IOException {
2257 ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
2258 if (slots.length != 1) {
2259 // skip any superclass stream field values
2260 for (int i = 0; i < slots.length-1; i++) {
2261 if (slots[i].hasData) {
2262 new FieldValues(slots[i].desc, true);
2263 }
2264 }
2265 }
2266
2267 FieldValues fieldValues = new FieldValues(desc, true);
2268 if (handles.lookupException(passHandle) != null) {
2269 return null; // slot marked with exception, don't create record
2270 }
2271
2272 // get canonical record constructor adapted to take two arguments:
2273 // - byte[] primValues
2274 // - Object[] objValues
2275 // and return Object
2276 MethodHandle ctrMH = RecordSupport.deserializationCtr(desc);
2277
2278 try {
2279 return (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
2280 } catch (Exception e) {
2281 throw new InvalidObjectException(e.getMessage(), e);
2282 } catch (Error e) {
2283 throw e;
2284 } catch (Throwable t) {
2285 throw new InvalidObjectException("ReflectiveOperationException " +
2286 "during deserialization", t);
2287 }
2288 }
2289
2290 /**
2291 * Reads (or attempts to skip, if obj is null or is tagged with a
2292 * ClassNotFoundException) instance data for each serializable class of
2293 * object in stream, from superclass to subclass. Expects that passHandle
2294 * is set to obj's handle before this method is called.
2295 */
2296 private void readSerialData(Object obj, ObjectStreamClass desc)
2297 throws IOException
2298 {
2299 ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
2300 // Best effort Failure Atomicity; slotValues will be non-null if field
2301 // values can be set after reading all field data in the hierarchy.
2302 // Field values can only be set after reading all data if there are no
2303 // user observable methods in the hierarchy, readObject(NoData). The
2304 // top most Serializable class in the hierarchy can be skipped.
2305 FieldValues[] slotValues = null;
2306
2307 boolean hasSpecialReadMethod = false;
2308 for (int i = 1; i < slots.length; i++) {
2309 ObjectStreamClass slotDesc = slots[i].desc;
2310 if (slotDesc.hasReadObjectMethod()
2311 || slotDesc.hasReadObjectNoDataMethod()) {
2312 hasSpecialReadMethod = true;
2313 break;
2314 }
2315 }
2316 // No special read methods, can store values and defer setting.
2317 if (!hasSpecialReadMethod)
2318 slotValues = new FieldValues[slots.length];
2319
2320 for (int i = 0; i < slots.length; i++) {
2321 ObjectStreamClass slotDesc = slots[i].desc;
2322
2323 if (slots[i].hasData) {
2324 if (obj == null || handles.lookupException(passHandle) != null) {
2325 // Read fields of the current descriptor into a new FieldValues and discard
2326 new FieldValues(slotDesc, true);
2327 } else if (slotDesc.hasReadObjectMethod()) {
2328 SerialCallbackContext oldContext = curContext;
2329 if (oldContext != null)
2330 oldContext.check();
2331 try {
2332 curContext = new SerialCallbackContext(obj, slotDesc);
2333
2334 bin.setBlockDataMode(true);
2335 slotDesc.invokeReadObject(obj, this);
2336 } catch (ClassNotFoundException ex) {
2337 /*
2338 * In most cases, the handle table has already
2339 * propagated a CNFException to passHandle at this
2340 * point; this mark call is included to address cases
2341 * where the custom readObject method has cons'ed and
2342 * thrown a new CNFException of its own.
2343 */
2344 handles.markException(passHandle, ex);
2345 } finally {
2346 curContext.setUsed();
2347 if (oldContext!= null)
2348 oldContext.check();
2349 curContext = oldContext;
2350 }
2351
2352 /*
2353 * defaultDataEnd may have been set indirectly by custom
2354 * readObject() method when calling defaultReadObject() or
2355 * readFields(); clear it to restore normal read behavior.
2356 */
2357 defaultDataEnd = false;
2358 } else {
2359 // Read fields of the current descriptor into a new FieldValues
2360 FieldValues values = new FieldValues(slotDesc, true);
2361 if (slotValues != null) {
2362 slotValues[i] = values;
2363 } else if (obj != null) {
2364 if (handles.lookupException(passHandle) == null) {
2365 // passHandle NOT marked with an exception; set field values
2366 values.defaultCheckFieldValues(obj);
2367 values.defaultSetFieldValues(obj);
2368 }
2369 }
2370 }
2371
2372 if (slotDesc.hasWriteObjectData()) {
2373 skipCustomData();
2374 } else {
2375 bin.setBlockDataMode(false);
2376 }
2377 } else {
2378 if (obj != null &&
2379 slotDesc.hasReadObjectNoDataMethod() &&
2380 handles.lookupException(passHandle) == null)
2381 {
2382 slotDesc.invokeReadObjectNoData(obj);
2383 }
2384 }
2385 }
2386
2387 if (obj != null && slotValues != null && handles.lookupException(passHandle) == null) {
2388 // passHandle NOT marked with an exception
2389 // Check that the non-primitive types are assignable for all slots
2390 // before assigning.
2391 for (int i = 0; i < slots.length; i++) {
2392 if (slotValues[i] != null)
2393 slotValues[i].defaultCheckFieldValues(obj);
2394 }
2395 for (int i = 0; i < slots.length; i++) {
2396 if (slotValues[i] != null)
2397 slotValues[i].defaultSetFieldValues(obj);
2398 }
2399 }
2400 }
2401
2402 /**
2403 * Skips over all block data and objects until TC_ENDBLOCKDATA is
2404 * encountered.
2405 */
2406 private void skipCustomData() throws IOException {
2407 int oldHandle = passHandle;
2408 for (;;) {
2409 if (bin.getBlockDataMode()) {
2410 bin.skipBlockData();
2411 bin.setBlockDataMode(false);
2412 }
2413 switch (bin.peekByte()) {
2414 case TC_BLOCKDATA:
2415 case TC_BLOCKDATALONG:
2416 bin.setBlockDataMode(true);
2417 break;
2418
2474 /**
2475 * Default GetField implementation.
2476 */
2477 private final class FieldValues extends GetField {
2478
2479 /** class descriptor describing serializable fields */
2480 private final ObjectStreamClass desc;
2481 /** primitive field values */
2482 final byte[] primValues;
2483 /** object field values */
2484 final Object[] objValues;
2485 /** object field value handles */
2486 private final int[] objHandles;
2487
2488 /**
2489 * Creates FieldValues object for reading fields defined in given
2490 * class descriptor.
2491 * @param desc the ObjectStreamClass to read
2492 * @param recordDependencies if true, record the dependencies
2493 * from current PassHandle and the object's read.
2494 */
2495 FieldValues(ObjectStreamClass desc, boolean recordDependencies) throws IOException {
2496 this.desc = desc;
2497
2498 int primDataSize = desc.getPrimDataSize();
2499 primValues = (primDataSize > 0) ? new byte[primDataSize] : null;
2500 if (primDataSize > 0) {
2501 bin.readFully(primValues, 0, primDataSize, false);
2502 }
2503
2504 int numObjFields = desc.getNumObjFields();
2505 objValues = (numObjFields > 0) ? new Object[numObjFields] : null;
2506 objHandles = (numObjFields > 0) ? new int[numObjFields] : null;
2507 if (numObjFields > 0) {
2508 int objHandle = passHandle;
2509 ObjectStreamField[] fields = desc.getFields(false);
2510 int numPrimFields = fields.length - objValues.length;
2511 for (int i = 0; i < objValues.length; i++) {
2512 ObjectStreamField f = fields[numPrimFields + i];
2513 objValues[i] = readObject0(Object.class, f.isUnshared());
2514 objHandles[i] = passHandle;
2515 if (recordDependencies && f.getField() != null) {
2516 handles.markDependency(objHandle, passHandle);
2517 }
2518 }
2519 passHandle = objHandle;
2520 }
2521 }
2522
2523 public ObjectStreamClass getObjectStreamClass() {
2524 return desc;
2525 }
2526
2527 public boolean defaulted(String name) {
2528 return (getFieldOffset(name, null) < 0);
2529 }
2530
2531 public boolean get(String name, boolean val) {
2532 int off = getFieldOffset(name, Boolean.TYPE);
2533 return (off >= 0) ? ByteArray.getBoolean(primValues, off) : val;
2534 }
2535
2536 public byte get(String name, byte val) {
2537 int off = getFieldOffset(name, Byte.TYPE);
2538 return (off >= 0) ? primValues[off] : val;
2539 }
|
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.io;
27
28 import java.io.ObjectInputFilter.Config;
29 import java.io.ObjectStreamClass.ConstructorSupport;
30 import java.io.ObjectStreamClass.ClassDataSlot;
31 import java.lang.System.Logger;
32 import java.lang.invoke.MethodHandle;
33 import java.lang.reflect.Array;
34 import java.lang.reflect.InvocationHandler;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Modifier;
37 import java.lang.reflect.Proxy;
38 import java.nio.charset.StandardCharsets;
39 import java.util.Arrays;
40 import java.util.List;
41 import java.util.Locale;
42 import java.util.Objects;
43
44 import jdk.internal.access.JavaLangAccess;
45 import jdk.internal.access.SharedSecrets;
46 import jdk.internal.event.DeserializationEvent;
47 import jdk.internal.misc.Unsafe;
48 import jdk.internal.util.ByteArray;
49
50 /**
51 * An ObjectInputStream deserializes primitive data and objects previously
52 * written using an ObjectOutputStream.
53 *
54 * <p><strong>Warning: Deserialization of untrusted data is inherently dangerous
55 * and should be avoided. Untrusted data should be carefully validated according to the
56 * "Serialization and Deserialization" section of the
57 * {@extLink secure_coding_guidelines_javase Secure Coding Guidelines for Java SE}.
58 * {@extLink serialization_filter_guide Serialization Filtering} describes best
59 * practices for defensive use of serial filters.
60 * </strong></p>
61 *
197 * <p>Serialization does not read or assign values to the fields of any object
198 * that does not implement the java.io.Serializable interface. Subclasses of
199 * Objects that are not serializable can be serializable. In this case the
200 * non-serializable class must have a no-arg constructor to allow its fields to
201 * be initialized. In this case it is the responsibility of the subclass to
202 * save and restore the state of the non-serializable class. It is frequently
203 * the case that the fields of that class are accessible (public, package, or
204 * protected) or that there are get and set methods that can be used to restore
205 * the state.
206 *
207 * <p>Any exception that occurs while deserializing an object will be caught by
208 * the ObjectInputStream and abort the reading process.
209 *
210 * <p>Implementing the Externalizable interface allows the object to assume
211 * complete control over the contents and format of the object's serialized
212 * form. The methods of the Externalizable interface, writeExternal and
213 * readExternal, are called to save and restore the objects state. When
214 * implemented by a class they can write and read their own state using all of
215 * the methods of ObjectOutput and ObjectInput. It is the responsibility of
216 * the objects to handle any versioning that occurs.
217 * Value objects cannot be `java.io.Externalizable` because value objects are
218 * immutable and `Externalizable.readExternal` is unable to modify the fields of the value.
219 *
220 * <p>Enum constants are deserialized differently than ordinary serializable or
221 * externalizable objects. The serialized form of an enum constant consists
222 * solely of its name; field values of the constant are not transmitted. To
223 * deserialize an enum constant, ObjectInputStream reads the constant name from
224 * the stream; the deserialized constant is then obtained by calling the static
225 * method {@code Enum.valueOf(Class, String)} with the enum constant's
226 * base type and the received constant name as arguments. Like other
227 * serializable or externalizable objects, enum constants can function as the
228 * targets of back references appearing subsequently in the serialization
229 * stream. The process by which enum constants are deserialized cannot be
230 * customized: any class-specific readObject, readObjectNoData, and readResolve
231 * methods defined by enum types are ignored during deserialization.
232 * Similarly, any serialPersistentFields or serialVersionUID field declarations
233 * are also ignored--all enum types have a fixed serialVersionUID of 0L.
234 *
235 * <a id="record-serialization"></a>
236 * <p>Records are serialized differently than ordinary serializable or externalizable
237 * objects. During deserialization the record's canonical constructor is invoked
238 * to construct the record object. Certain serialization-related methods, such
239 * as readObject and writeObject, are ignored for serializable records. See
240 * <a href="{@docRoot}/../specs/serialization/serial-arch.html#serialization-of-records">
241 * <cite>Java Object Serialization Specification,</cite> Section 1.13,
242 * "Serialization of Records"</a> for additional information.
243 *
244 * <p>Value classes are {@linkplain Serializable} through the use of the serialization proxy pattern.
245 * See {@linkplain ObjectOutputStream##valueclass-serialization value class serialization} for details.
246 * When the proxy is deserialized it re-constructs and returns the value object.
247 *
248 * @spec serialization/index.html Java Object Serialization Specification
249 * @author Mike Warres
250 * @author Roger Riggs
251 * @see java.io.DataInput
252 * @see java.io.ObjectOutputStream
253 * @see java.io.Serializable
254 * @see <a href="{@docRoot}/../specs/serialization/input.html">
255 * <cite>Java Object Serialization Specification,</cite> Section 3, "Object Input Classes"</a>
256 * @since 1.1
257 */
258 public class ObjectInputStream
259 extends InputStream implements ObjectInput, ObjectStreamConstants
260 {
261 private static final String TRACE_DEST =
262 System.getProperty("TRACE");
263
264 static void TRACE(String format, Object... args) {
265 if (TRACE_DEST != null) {
266 var ps = "OUT".equals(TRACE_DEST.toUpperCase(Locale.ROOT)) ? System.out : System.err;
267 ps.println(("TRACE " + format).formatted(args));
268 }
269 }
270
271 /** handle value representing null */
272 private static final int NULL_HANDLE = -1;
273
274 /** marker for unshared objects in internal handle table */
275 private static final Object unsharedMarker = new Object();
276
277 private static class Caches {
278 /** cache of subclass security audit results */
279 static final ClassValue<Boolean> subclassAudits =
280 new ClassValue<>() {
281 @Override
282 protected Boolean computeValue(Class<?> type) {
283 return auditSubclass(type);
284 }
285 };
286
287 /**
288 * Property to permit setting a filter after objects
289 * have been read.
290 * See {@link #setObjectInputFilter(ObjectInputFilter)}
436
437 /**
438 * Read an object from the ObjectInputStream. The class of the object, the
439 * signature of the class, and the values of the non-transient and
440 * non-static fields of the class and all of its supertypes are read.
441 * Default deserializing for a class can be overridden using the writeObject
442 * and readObject methods. Objects referenced by this object are read
443 * transitively so that a complete equivalent graph of objects is
444 * reconstructed by readObject.
445 *
446 * <p>The root object is completely restored when all of its fields and the
447 * objects it references are completely restored. At this point the object
448 * validation callbacks are executed in order based on their registered
449 * priorities. The callbacks are registered by objects (in the readObject
450 * special methods) as they are individually restored.
451 *
452 * <p>The deserialization filter, when not {@code null}, is invoked for
453 * each object (regular or class) read to reconstruct the root object.
454 * See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details.
455 *
456 * <p>Serialization and deserialization of value classes is described in
457 * {@linkplain ObjectOutputStream##valueclass-serialization value class serialization}.
458 *
459 * @implSpec
460 * When enabled with {@code --enable-preview}, serialization and deserialization of
461 * Core Library value classes migrated from pre-JEP 401 identity classes is
462 * implementation specific.
463 *
464 * <p>Exceptions are thrown for problems with the InputStream and for
465 * classes that should not be deserialized. All exceptions are fatal to
466 * the InputStream and leave it in an indeterminate state; it is up to the
467 * caller to ignore or recover the stream state.
468 *
469 * @throws ClassNotFoundException Class of a serialized object cannot be
470 * found.
471 * @throws InvalidClassException Something is wrong with a class used by
472 * deserialization.
473 * @throws StreamCorruptedException Control information in the
474 * stream is inconsistent.
475 * @throws OptionalDataException Primitive data was found in the
476 * stream instead of objects.
477 * @throws IOException Any of the usual Input/Output related exceptions.
478 */
479 public final Object readObject()
480 throws IOException, ClassNotFoundException {
481 return readObject(Object.class);
482 }
483
2151 * class is unresolvable (in which case a ClassNotFoundException will be
2152 * associated with object's handle). Sets passHandle to object's assigned
2153 * handle.
2154 */
2155 private Object readOrdinaryObject(boolean unshared)
2156 throws IOException
2157 {
2158 if (bin.readByte() != TC_OBJECT) {
2159 throw new InternalError();
2160 }
2161
2162 ObjectStreamClass desc = readClassDesc(false);
2163 desc.checkDeserialize();
2164
2165 Class<?> cl = desc.forClass();
2166 if (cl == String.class || cl == Class.class
2167 || cl == ObjectStreamClass.class) {
2168 throw new InvalidClassException("invalid class descriptor");
2169 }
2170
2171 // Assign the handle and initially set to null or the unsharedMarker
2172 passHandle = handles.assign(unshared ? unsharedMarker : null);
2173 ClassNotFoundException resolveEx = desc.getResolveException();
2174 if (resolveEx != null) {
2175 handles.markException(passHandle, resolveEx);
2176 }
2177
2178 try {
2179 // Dispatch on the factory mode to read an object from the stream.
2180 Object obj = switch (desc.factoryMode()) {
2181 case READ_OBJECT_DEFAULT -> readSerialDefaultObject(desc, unshared);
2182 case READ_OBJECT_CUSTOM -> readSerialCustomData(desc, unshared);
2183 case READ_RECORD -> readRecord(desc, unshared);
2184 case READ_EXTERNALIZABLE -> readExternalObject(desc, unshared);
2185 case READ_OBJECT_VALUE -> readObjectValue(desc, unshared);
2186 case READ_NO_LOCAL_CLASS -> readAbsentLocalClass(desc, unshared);
2187 case null -> throw new AssertionError("Unknown factoryMode for: " + desc.getName(),
2188 resolveEx);
2189 };
2190
2191 handles.finish(passHandle);
2192
2193 if (obj != null &&
2194 handles.lookupException(passHandle) == null &&
2195 desc.hasReadResolveMethod())
2196 {
2197 Object rep = desc.invokeReadResolve(obj);
2198 if (unshared && rep.getClass().isArray()) {
2199 rep = cloneArray(rep);
2200 }
2201 if (rep != obj) {
2202 // Filter the replacement object
2203 if (rep != null) {
2204 if (rep.getClass().isArray()) {
2205 filterCheck(rep.getClass(), Array.getLength(rep));
2206 } else {
2207 filterCheck(rep.getClass(), -1);
2208 }
2209 }
2210 handles.setObject(passHandle, obj = rep);
2211 }
2212 }
2213
2214 return obj;
2215 } catch (UncheckedIOException uioe) {
2216 // Consistent re-throw for nested UncheckedIOExceptions
2217 throw uioe.getCause();
2218 }
2219 }
2220
2221 /**
2222 * {@return a value class instance by invoking its constructor with field values read from the stream.
2223 * The fields of the class in the stream are matched to the local fields and applied to
2224 * the constructor.
2225 * If the stream contains superclasses with serializable fields,
2226 * an InvalidClassException is thrown with an incompatible class change message.
2227 *
2228 * @param desc the class descriptor read from the stream, the local class is a value class
2229 * @param unshared if the object is not to be shared
2230 * @throws InvalidClassException if the stream contains a superclass with serializable fields.
2231 * @throws IOException if there are I/O errors while reading from the
2232 * underlying {@code InputStream}
2233 */
2234 private Object readObjectValue(ObjectStreamClass desc, boolean unshared) throws IOException {
2235 final ObjectStreamClass localDesc = desc.getLocalDesc();
2236 TRACE("readObjectValue: %s, local class: %s", desc.getName(), localDesc.getName());
2237 // Check for un-expected fields in superclasses
2238 List<ClassDataSlot> slots = desc.getClassDataLayout();
2239 for (int i = 0; i < slots.size()-1; i++) {
2240 ClassDataSlot slot = slots.get(i);
2241 if (slot.hasData && slot.desc.getFields(false).length > 0) {
2242 throw new InvalidClassException("incompatible class change to value class: " +
2243 "stream class has non-empty super type: " + desc.getName());
2244 }
2245 }
2246 // Read values for the value class fields
2247 FieldValues fieldValues = new FieldValues(desc, true);
2248
2249 // Get value object constructor adapted to take primitive value buffer and object array.
2250 MethodHandle consMH = ConstructorSupport.deserializationValueCons(desc);
2251 try {
2252 Object obj = (Object) consMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
2253 if (!unshared)
2254 handles.setObject(passHandle, obj);
2255 return obj;
2256 } catch (Exception e) {
2257 throw new InvalidObjectException(e.getMessage(), e);
2258 } catch (Error e) {
2259 throw e;
2260 } catch (Throwable t) {
2261 throw new InvalidObjectException("ReflectiveOperationException " +
2262 "during deserialization", t);
2263 }
2264 }
2265
2266 /**
2267 * Creates a new object and invokes its readExternal method to read its contents.
2268 *
2269 * If the class is instantiable, read externalizable data by invoking readExternal()
2270 * method of obj; otherwise, attempts to skip over externalizable data.
2271 * Expects that passHandle is set to obj's handle before this method is
2272 * called. The new object is entered in the handle table immediately,
2273 * allowing it to leak before it is completely read.
2274 */
2275 private Object readExternalObject(ObjectStreamClass desc, boolean unshared)
2276 throws IOException
2277 {
2278 TRACE("readExternalObject: %s", desc.getName());
2279
2280 // For Externalizable objects,
2281 // create the instance, publish the ref, and read the data
2282 Externalizable obj = null;
2283 try {
2284 if (desc.isInstantiable()) {
2285 obj = (Externalizable) desc.newInstance();
2286 }
2287 } catch (Exception ex) {
2288 throw new InvalidClassException(desc.getName(),
2289 "unable to create instance", ex);
2290 }
2291
2292 if (!unshared)
2293 handles.setObject(passHandle, obj);
2294
2295 SerialCallbackContext oldContext = curContext;
2296 if (oldContext != null)
2297 oldContext.check();
2298 curContext = null;
2299 try {
2300 boolean blocked = desc.hasBlockExternalData();
2301 if (blocked) {
2302 bin.setBlockDataMode(true);
2303 }
2304 if (obj != null) {
2305 try {
2306 obj.readExternal(this);
2307 } catch (ClassNotFoundException ex) {
2308 /*
2309 * In most cases, the handle table has already propagated
2310 * a CNFException to passHandle at this point; this mark
2311 * call is included to address cases where the readExternal
2312 * method has cons'ed and thrown a new CNFException of its
2313 * own.
2314 */
2318 if (blocked) {
2319 skipCustomData();
2320 }
2321 } finally {
2322 if (oldContext != null)
2323 oldContext.check();
2324 curContext = oldContext;
2325 }
2326 /*
2327 * At this point, if the externalizable data was not written in
2328 * block-data form and either the externalizable class doesn't exist
2329 * locally (i.e., obj == null) or readExternal() just threw a
2330 * CNFException, then the stream is probably in an inconsistent state,
2331 * since some (or all) of the externalizable data may not have been
2332 * consumed. Since there's no "correct" action to take in this case,
2333 * we mimic the behavior of past serialization implementations and
2334 * blindly hope that the stream is in sync; if it isn't and additional
2335 * externalizable data remains in the stream, a subsequent read will
2336 * most likely throw a StreamCorruptedException.
2337 */
2338 return obj;
2339 }
2340
2341 /**
2342 * Reads and returns a record.
2343 * If an exception is marked for any of the fields, the dependency
2344 * mechanism marks the record as having an exception.
2345 * Null is returned from readRecord and later the exception is thrown at
2346 * the exit of {@link #readObject(Class)}.
2347 */
2348 private Object readRecord(ObjectStreamClass desc, boolean unshared) throws IOException {
2349 TRACE("invoking readRecord: %s", desc.getName());
2350 List<ClassDataSlot> slots = desc.getClassDataLayout();
2351 if (slots.size() != 1) {
2352 // skip any superclass stream field values
2353 for (int i = 0; i < slots.size()-1; i++) {
2354 if (slots.get(i).hasData) {
2355 new FieldValues(slots.get(i).desc, true);
2356 }
2357 }
2358 }
2359
2360 FieldValues fieldValues = new FieldValues(desc, true);
2361 if (handles.lookupException(passHandle) != null) {
2362 return null; // slot marked with exception, don't create record
2363 }
2364
2365 // get canonical record constructor adapted to take two arguments:
2366 // - byte[] primValues
2367 // - Object[] objValues
2368 // and return Object
2369 MethodHandle ctrMH = ConstructorSupport.deserializationCtr(desc);
2370
2371 try {
2372 Object obj = (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues);
2373 if (!unshared)
2374 handles.setObject(passHandle, obj);
2375 return obj;
2376 } catch (Exception e) {
2377 throw new InvalidObjectException(e.getMessage(), e);
2378 } catch (Error e) {
2379 throw e;
2380 } catch (Throwable t) {
2381 throw new InvalidObjectException("ReflectiveOperationException " +
2382 "during deserialization", t);
2383 }
2384 }
2385
2386 /**
2387 * Construct an object from the stream for a class that has only default read object behaviors.
2388 * For each object, the fields are read before any are assigned.
2389 * The new instance is entered in the handle table if it is unshared,
2390 * allowing it to escape before it is initialized.
2391 * The `readObject` and `readObjectNoData` methods are not present and are not called.
2392 *
2393 * @param desc the class descriptor
2394 * @param unshared true if the object should be shared
2395 * @return the object constructed from the stream data
2396 * @throws IOException if there are I/O errors while reading from the
2397 * underlying {@code InputStream}
2398 * @throws InvalidClassException if the instance creation fails
2399 */
2400 private Object readSerialDefaultObject(ObjectStreamClass desc, boolean unshared)
2401 throws IOException, InvalidClassException {
2402 if (!desc.isInstantiable()) {
2403 // No local class to create, read and discard
2404 return readAbsentLocalClass(desc, unshared);
2405 }
2406 TRACE("readSerialDefaultObject: %s", desc.getName());
2407 try {
2408 final Object obj = desc.newInstance();
2409 if (!unshared)
2410 handles.setObject(passHandle, obj);
2411
2412 // Best effort Failure Atomicity; slotValues will be non-null if field
2413 // values can be set after reading all field data in the hierarchy.
2414 List<FieldValues> slotValues = desc.getClassDataLayout().stream()
2415 .filter(s -> s.hasData)
2416 .map(s1 -> {
2417 var values = new FieldValues(s1.desc, true);
2418 finishBlockData(s1.desc);
2419 return values;
2420 })
2421 .toList();
2422
2423 if (handles.lookupException(passHandle) != null) {
2424 return null; // some exception for a class, do not return the object
2425 }
2426
2427 // Check that the types are assignable for all slots before assigning.
2428 slotValues.forEach(v -> v.defaultCheckFieldValues(obj));
2429 slotValues.forEach(v -> v.defaultSetFieldValues(obj));
2430 return obj;
2431 } catch (InstantiationException | InvocationTargetException ex) {
2432 throw new InvalidClassException(desc.forClass().getName(),
2433 "unable to create instance", ex);
2434 }
2435 }
2436
2437
2438 /**
2439 * Reads (or attempts to skip, if not instantiatable or is tagged with a
2440 * ClassNotFoundException) instance data for each serializable class of
2441 * object in stream, from superclass to subclass.
2442 * Expects that passHandle is set to current handle before this method is called.
2443 */
2444 private Object readSerialCustomData(ObjectStreamClass desc, boolean unshared)
2445 throws IOException
2446 {
2447 if (!desc.isInstantiable()) {
2448 // No local class to create, read and discard
2449 return readAbsentLocalClass(desc, unshared);
2450 }
2451
2452 TRACE("readSerialCustomData: %s, ex: %s", desc.getName(), handles.lookupException(passHandle));
2453 try {
2454 Object obj = desc.newInstance();
2455 if (!unshared)
2456 handles.setObject(passHandle, obj);
2457 // Read data into each of the slots for the class
2458 return readSerialCustomSlots(obj, desc.getClassDataLayout());
2459 } catch (InstantiationException | InvocationTargetException ex) {
2460 throw new InvalidClassException(desc.forClass().getName(),
2461 "unable to create instance", ex);
2462 }
2463 }
2464
2465 /**
2466 * Reads from the stream using custom or default readObject methods appropriate.
2467 * For each slot, either the custom readObject method or the default reader of fields
2468 * is invoked. Unused slot specific custom data is discarded.
2469 * This function is used by {@link #readSerialCustomData}.
2470 *
2471 * @param obj the object to assign the values to
2472 * @param slots a list of slots to read from the stream
2473 * @return the object being initialized
2474 * @throws IOException if there are I/O errors while reading from the
2475 * underlying {@code InputStream}
2476 */
2477 private Object readSerialCustomSlots(Object obj, List<ClassDataSlot> slots) throws IOException {
2478 TRACE(" readSerialCustomSlots: %s", slots);
2479
2480 for (ClassDataSlot slot : slots) {
2481 ObjectStreamClass slotDesc = slot.desc;
2482 if (slot.hasData) {
2483 if (slotDesc.hasReadObjectMethod() &&
2484 handles.lookupException(passHandle) == null) {
2485 // Invoke slot custom readObject method
2486 readSlotViaReadObject(obj, slotDesc);
2487 } else {
2488 // Read fields of the current descriptor into a new FieldValues
2489 FieldValues values = new FieldValues(slotDesc, true);
2490 if (handles.lookupException(passHandle) == null) {
2491 // Set the instance fields if no previous exception
2492 values.defaultCheckFieldValues(obj);
2493 values.defaultSetFieldValues(obj);
2494 }
2495 finishBlockData(slotDesc);
2496 }
2497 } else {
2498 if (slotDesc.hasReadObjectNoDataMethod() &&
2499 handles.lookupException(passHandle) == null) {
2500 slotDesc.invokeReadObjectNoData(obj);
2501 }
2502 }
2503 }
2504 return obj;
2505 }
2506
2507 /**
2508 * Invoke the readObject method of the class to read and store the state from the stream.
2509 *
2510 * @param obj an instance of the class being created, only partially initialized.
2511 * @param slotDesc the ObjectStreamDescriptor for the current class
2512 * @throws IOException if there are I/O errors while reading from the
2513 * underlying {@code InputStream}
2514 */
2515 private void readSlotViaReadObject(Object obj, ObjectStreamClass slotDesc) throws IOException {
2516 TRACE("readSlotViaReadObject: %s", slotDesc.getName());
2517 assert obj != null : "readSlotViaReadObject called when obj == null";
2518
2519 SerialCallbackContext oldContext = curContext;
2520 if (oldContext != null)
2521 oldContext.check();
2522 try {
2523 curContext = new SerialCallbackContext(obj, slotDesc);
2524
2525 bin.setBlockDataMode(true);
2526 slotDesc.invokeReadObject(obj, this);
2527 } catch (ClassNotFoundException ex) {
2528 /*
2529 * In most cases, the handle table has already
2530 * propagated a CNFException to passHandle at this
2531 * point; this mark call is included to address cases
2532 * where the custom readObject method has cons'ed and
2533 * thrown a new CNFException of its own.
2534 */
2535 handles.markException(passHandle, ex);
2536 } finally {
2537 curContext.setUsed();
2538 if (oldContext!= null)
2539 oldContext.check();
2540 curContext = oldContext;
2541 }
2542
2543 /*
2544 * defaultDataEnd may have been set indirectly by custom
2545 * readObject() method when calling defaultReadObject() or
2546 * readFields(); clear it to restore normal read behavior.
2547 */
2548 defaultDataEnd = false;
2549
2550 finishBlockData(slotDesc);
2551 }
2552
2553
2554 /**
2555 * Read and discard an entire object, leaving a null reference in the HandleTable.
2556 * The descriptor of the class in the stream is used to read the fields from the stream.
2557 * There is no instance in which to store the field values.
2558 * Custom data following the fields of any slot is read and discarded.
2559 * References to nested objects are read and retained in the
2560 * handle table using the regular mechanism.
2561 * Handles later in the stream may refer to the nested objects.
2562 *
2563 * @param desc the stream class descriptor
2564 * @param unshared the unshared flag, ignored since no object is created
2565 * @return null, no object is created
2566 * @throws IOException if there are I/O errors while reading from the
2567 * underlying {@code InputStream}
2568 */
2569 private Object readAbsentLocalClass(ObjectStreamClass desc, boolean unshared)
2570 throws IOException {
2571 TRACE("readAbsentLocalClass: %s", desc.getName());
2572 desc.getClassDataLayout().stream()
2573 .filter(s -> s.hasData)
2574 .forEach(s2 -> {new FieldValues(s2.desc, true); finishBlockData(s2.desc);});
2575 return null;
2576 }
2577
2578 // Finish handling of block data by skipping any remaining and setting BlockDataMode = false
2579 private void finishBlockData(ObjectStreamClass slotDesc) throws UncheckedIOException {
2580 try {
2581 if (slotDesc.hasWriteObjectData()) {
2582 skipCustomData();
2583 } else {
2584 bin.setBlockDataMode(false);
2585 }
2586 } catch (IOException ioe) {
2587 throw new UncheckedIOException(ioe);
2588 }
2589 }
2590
2591 /**
2592 * Skips over all block data and objects until TC_ENDBLOCKDATA is
2593 * encountered.
2594 */
2595 private void skipCustomData() throws IOException {
2596 int oldHandle = passHandle;
2597 for (;;) {
2598 if (bin.getBlockDataMode()) {
2599 bin.skipBlockData();
2600 bin.setBlockDataMode(false);
2601 }
2602 switch (bin.peekByte()) {
2603 case TC_BLOCKDATA:
2604 case TC_BLOCKDATALONG:
2605 bin.setBlockDataMode(true);
2606 break;
2607
2663 /**
2664 * Default GetField implementation.
2665 */
2666 private final class FieldValues extends GetField {
2667
2668 /** class descriptor describing serializable fields */
2669 private final ObjectStreamClass desc;
2670 /** primitive field values */
2671 final byte[] primValues;
2672 /** object field values */
2673 final Object[] objValues;
2674 /** object field value handles */
2675 private final int[] objHandles;
2676
2677 /**
2678 * Creates FieldValues object for reading fields defined in given
2679 * class descriptor.
2680 * @param desc the ObjectStreamClass to read
2681 * @param recordDependencies if true, record the dependencies
2682 * from current PassHandle and the object's read.
2683 * @throws UncheckedIOException if any IOException occurs
2684 */
2685 FieldValues(ObjectStreamClass desc, boolean recordDependencies) throws UncheckedIOException {
2686 try {
2687 this.desc = desc;
2688 TRACE(" reading FieldValues: %s", desc.getName());
2689 int primDataSize = desc.getPrimDataSize();
2690 primValues = (primDataSize > 0) ? new byte[primDataSize] : null;
2691 if (primDataSize > 0) {
2692 bin.readFully(primValues, 0, primDataSize, false);
2693 }
2694
2695
2696 int numObjFields = desc.getNumObjFields();
2697 objValues = (numObjFields > 0) ? new Object[numObjFields] : null;
2698 objHandles = (numObjFields > 0) ? new int[numObjFields] : null;
2699 if (numObjFields > 0) {
2700 int objHandle = passHandle;
2701 ObjectStreamField[] fields = desc.getFields(false);
2702 int numPrimFields = fields.length - objValues.length;
2703 for (int i = 0; i < objValues.length; i++) {
2704 ObjectStreamField f = fields[numPrimFields + i];
2705 objValues[i] = readObject0(Object.class, f.isUnshared());
2706 objHandles[i] = passHandle;
2707 if (recordDependencies && f.getField() != null) {
2708 handles.markDependency(objHandle, passHandle);
2709 }
2710 }
2711 passHandle = objHandle;
2712 }
2713 } catch (IOException ioe) {
2714 throw new UncheckedIOException(ioe);
2715 }
2716 }
2717
2718 public ObjectStreamClass getObjectStreamClass() {
2719 return desc;
2720 }
2721
2722 public boolean defaulted(String name) {
2723 return (getFieldOffset(name, null) < 0);
2724 }
2725
2726 public boolean get(String name, boolean val) {
2727 int off = getFieldOffset(name, Boolean.TYPE);
2728 return (off >= 0) ? ByteArray.getBoolean(primValues, off) : val;
2729 }
2730
2731 public byte get(String name, byte val) {
2732 int off = getFieldOffset(name, Byte.TYPE);
2733 return (off >= 0) ? primValues[off] : val;
2734 }
|