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.lang.invoke.MethodHandle;
29 import java.lang.invoke.MethodHandles;
30 import java.lang.invoke.MethodType;
31 import java.lang.reflect.Constructor;
32 import java.lang.reflect.Field;
33 import java.lang.reflect.InvocationTargetException;
34 import java.lang.reflect.RecordComponent;
35 import java.lang.reflect.Member;
36 import java.lang.reflect.Method;
37 import java.lang.reflect.Modifier;
38 import java.lang.reflect.Proxy;
39 import java.security.MessageDigest;
40 import java.security.NoSuchAlgorithmException;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.Comparator;
45 import java.util.HashSet;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.concurrent.ConcurrentHashMap;
49
50 import jdk.internal.event.SerializationMisdeclarationEvent;
51 import jdk.internal.misc.Unsafe;
52 import jdk.internal.reflect.ReflectionFactory;
53 import jdk.internal.util.ByteArray;
54
55 /**
56 * Serialization's descriptor for classes. It contains the name and
57 * serialVersionUID of the class. The ObjectStreamClass for a specific class
58 * loaded in this Java VM can be found/created using the lookup method.
59 *
60 * <p>The algorithm to compute the SerialVersionUID is described in
61 * <a href="{@docRoot}/../specs/serialization/class.html#stream-unique-identifiers">
62 * <cite>Java Object Serialization Specification,</cite> Section 4.6, "Stream Unique Identifiers"</a>.
63 *
64 * @spec serialization/index.html Java Object Serialization Specification
65 * @author Mike Warres
66 * @author Roger Riggs
67 * @see ObjectStreamField
68 * @see <a href="{@docRoot}/../specs/serialization/class.html">
69 * <cite>Java Object Serialization Specification,</cite> Section 4, "Class Descriptors"</a>
70 * @since 1.1
71 */
72 public final class ObjectStreamClass implements Serializable {
73
74 /** serialPersistentFields value indicating no serializable fields */
75 public static final ObjectStreamField[] NO_FIELDS =
76 new ObjectStreamField[0];
77
78 @java.io.Serial
79 private static final long serialVersionUID = -6120832682080437368L;
80 /**
81 * {@code ObjectStreamClass} has no fields for default serialization.
82 */
83 @java.io.Serial
84 private static final ObjectStreamField[] serialPersistentFields =
85 NO_FIELDS;
86
87 private static class Caches {
88 /** cache mapping local classes -> descriptors */
89 static final ClassCache<ObjectStreamClass> localDescs =
90 new ClassCache<>() {
91 @Override
92 protected ObjectStreamClass computeValue(Class<?> type) {
93 return new ObjectStreamClass(type);
94 }
95 };
96
97 /** cache mapping field group/local desc pairs -> field reflectors */
98 static final ClassCache<Map<FieldReflectorKey, FieldReflector>> reflectors =
99 new ClassCache<>() {
100 @Override
101 protected Map<FieldReflectorKey, FieldReflector> computeValue(Class<?> type) {
102 return new ConcurrentHashMap<>();
103 }
104 };
105 }
106
107 /** class associated with this descriptor (if any) */
108 private Class<?> cl;
109 /** name of class represented by this descriptor */
110 private String name;
111 /** serialVersionUID of represented class (null if not computed yet) */
112 private volatile Long suid;
113
114 /** true if represents dynamic proxy class */
115 private boolean isProxy;
116 /** true if represents enum type */
117 private boolean isEnum;
118 /** true if represents record type */
119 private boolean isRecord;
120 /** true if represented class implements Serializable */
121 private boolean serializable;
122 /** true if represented class implements Externalizable */
123 private boolean externalizable;
124 /** true if desc has data written by class-defined writeObject method */
125 private boolean hasWriteObjectData;
126 /**
127 * true if desc has externalizable data written in block data format; this
128 * must be true by default to accommodate ObjectInputStream subclasses which
129 * override readClassDescriptor() to return class descriptors obtained from
130 * ObjectStreamClass.lookup() (see 4461737)
131 */
132 private boolean hasBlockExternalData = true;
133
134 /**
135 * Contains information about InvalidClassException instances to be thrown
136 * when attempting operations on an invalid class. Note that instances of
137 * this class are immutable and are potentially shared among
138 * ObjectStreamClass instances.
139 */
157 }
158
159 /** exception (if any) thrown while attempting to resolve class */
160 private ClassNotFoundException resolveEx;
161 /** exception (if any) to throw if non-enum deserialization attempted */
162 private ExceptionInfo deserializeEx;
163 /** exception (if any) to throw if non-enum serialization attempted */
164 private ExceptionInfo serializeEx;
165 /** exception (if any) to throw if default serialization attempted */
166 private ExceptionInfo defaultSerializeEx;
167
168 /** serializable fields */
169 private ObjectStreamField[] fields;
170 /** aggregate marshalled size of primitive fields */
171 private int primDataSize;
172 /** number of non-primitive fields */
173 private int numObjFields;
174 /** reflector for setting/getting serializable field values */
175 private FieldReflector fieldRefl;
176 /** data layout of serialized objects described by this class desc */
177 private volatile ClassDataSlot[] dataLayout;
178
179 /** serialization-appropriate constructor, or null if none */
180 private Constructor<?> cons;
181 /** record canonical constructor (shared among OSCs for same class), or null */
182 private MethodHandle canonicalCtr;
183 /** cache of record deserialization constructors per unique set of stream fields
184 * (shared among OSCs for same class), or null */
185 private DeserializationConstructorsCache deserializationCtrs;
186 /** session-cache of record deserialization constructor
187 * (in de-serialized OSC only), or null */
188 private MethodHandle deserializationCtr;
189
190 /** class-defined writeObject method, or null if none */
191 private Method writeObjectMethod;
192 /** class-defined readObject method, or null if none */
193 private Method readObjectMethod;
194 /** class-defined readObjectNoData method, or null if none */
195 private Method readObjectNoDataMethod;
196 /** class-defined writeReplace method, or null if none */
197 private Method writeReplaceMethod;
321 * @param cl class to look up
322 * @param all if true, return descriptors for all classes; if false, only
323 * return descriptors for serializable classes
324 */
325 static ObjectStreamClass lookup(Class<?> cl, boolean all) {
326 if (!(all || Serializable.class.isAssignableFrom(cl))) {
327 return null;
328 }
329 return Caches.localDescs.get(cl);
330 }
331
332 /**
333 * Creates local class descriptor representing given class.
334 */
335 private ObjectStreamClass(final Class<?> cl) {
336 this.cl = cl;
337 name = cl.getName();
338 isProxy = Proxy.isProxyClass(cl);
339 isEnum = Enum.class.isAssignableFrom(cl);
340 isRecord = cl.isRecord();
341 serializable = Serializable.class.isAssignableFrom(cl);
342 externalizable = Externalizable.class.isAssignableFrom(cl);
343
344 Class<?> superCl = cl.getSuperclass();
345 superDesc = (superCl != null) ? lookup(superCl, false) : null;
346 localDesc = this;
347
348 if (serializable) {
349 if (isEnum) {
350 suid = 0L;
351 fields = NO_FIELDS;
352 } else if (cl.isArray()) {
353 fields = NO_FIELDS;
354 } else {
355 suid = getDeclaredSUID(cl);
356 try {
357 fields = getSerialFields(cl);
358 computeFieldOffsets();
359 } catch (InvalidClassException e) {
360 serializeEx = deserializeEx =
361 new ExceptionInfo(e.classname, e.getMessage());
362 fields = NO_FIELDS;
363 }
364
365 if (isRecord) {
366 canonicalCtr = canonicalRecordCtr(cl);
367 deserializationCtrs = new DeserializationConstructorsCache();
368 } else if (externalizable) {
369 cons = getExternalizableConstructor(cl);
370 } else {
371 cons = getSerializableConstructor(cl);
372 writeObjectMethod = getPrivateMethod(cl, "writeObject",
373 new Class<?>[]{ObjectOutputStream.class},
374 Void.TYPE);
375 readObjectMethod = getPrivateMethod(cl, "readObject",
376 new Class<?>[]{ObjectInputStream.class},
377 Void.TYPE);
378 readObjectNoDataMethod = getPrivateMethod(
379 cl, "readObjectNoData", null, Void.TYPE);
380 hasWriteObjectData = (writeObjectMethod != null);
381 }
382 writeReplaceMethod = getInheritableMethod(
383 cl, "writeReplace", null, Object.class);
384 readResolveMethod = getInheritableMethod(
385 cl, "readResolve", null, Object.class);
386 }
387 } else {
388 suid = 0L;
389 fields = NO_FIELDS;
390 }
391
392 try {
393 fieldRefl = getReflector(fields, this);
394 } catch (InvalidClassException ex) {
395 // field mismatches impossible when matching local fields vs. self
396 throw new InternalError(ex);
397 }
398
399 if (deserializeEx == null) {
400 if (isEnum) {
401 deserializeEx = new ExceptionInfo(name, "enum type");
402 } else if (cons == null && !isRecord) {
403 deserializeEx = new ExceptionInfo(name, "no valid constructor");
404 }
405 }
406 if (isRecord && canonicalCtr == null) {
407 deserializeEx = new ExceptionInfo(name, "record canonical constructor not found");
408 } else {
409 for (int i = 0; i < fields.length; i++) {
410 if (fields[i].getField() == null) {
411 defaultSerializeEx = new ExceptionInfo(
412 name, "unmatched serializable field(s) declared");
413 }
414 }
415 }
416 initialized = true;
417
418 if (SerializationMisdeclarationEvent.enabled() && serializable) {
419 SerializationMisdeclarationChecker.checkMisdeclarations(cl);
420 }
421 }
422
441 if (!osc.isProxy) {
442 throw new InvalidClassException(
443 "cannot bind proxy descriptor to a non-proxy class");
444 }
445 }
446 this.cl = cl;
447 this.resolveEx = resolveEx;
448 this.superDesc = superDesc;
449 isProxy = true;
450 serializable = true;
451 suid = 0L;
452 fields = NO_FIELDS;
453 if (osc != null) {
454 localDesc = osc;
455 name = localDesc.name;
456 externalizable = localDesc.externalizable;
457 writeReplaceMethod = localDesc.writeReplaceMethod;
458 readResolveMethod = localDesc.readResolveMethod;
459 deserializeEx = localDesc.deserializeEx;
460 cons = localDesc.cons;
461 }
462 fieldRefl = getReflector(fields, localDesc);
463 initialized = true;
464 }
465
466 /**
467 * Initializes class descriptor representing a non-proxy class.
468 */
469 void initNonProxy(ObjectStreamClass model,
470 Class<?> cl,
471 ClassNotFoundException resolveEx,
472 ObjectStreamClass superDesc)
473 throws InvalidClassException
474 {
475 long suid = model.getSerialVersionUID();
476 ObjectStreamClass osc = null;
477 if (cl != null) {
478 osc = lookup(cl, true);
479 if (osc.isProxy) {
480 throw new InvalidClassException(
519 }
520
521 this.cl = cl;
522 this.resolveEx = resolveEx;
523 this.superDesc = superDesc;
524 name = model.name;
525 this.suid = suid;
526 isProxy = false;
527 isEnum = model.isEnum;
528 serializable = model.serializable;
529 externalizable = model.externalizable;
530 hasBlockExternalData = model.hasBlockExternalData;
531 hasWriteObjectData = model.hasWriteObjectData;
532 fields = model.fields;
533 primDataSize = model.primDataSize;
534 numObjFields = model.numObjFields;
535
536 if (osc != null) {
537 localDesc = osc;
538 isRecord = localDesc.isRecord;
539 // canonical record constructor is shared
540 canonicalCtr = localDesc.canonicalCtr;
541 // cache of deserialization constructors is shared
542 deserializationCtrs = localDesc.deserializationCtrs;
543 writeObjectMethod = localDesc.writeObjectMethod;
544 readObjectMethod = localDesc.readObjectMethod;
545 readObjectNoDataMethod = localDesc.readObjectNoDataMethod;
546 writeReplaceMethod = localDesc.writeReplaceMethod;
547 readResolveMethod = localDesc.readResolveMethod;
548 if (deserializeEx == null) {
549 deserializeEx = localDesc.deserializeEx;
550 }
551 assert cl.isRecord() ? localDesc.cons == null : true;
552 cons = localDesc.cons;
553 }
554
555 fieldRefl = getReflector(fields, localDesc);
556 // reassign to matched fields so as to reflect local unshared settings
557 fields = fieldRefl.getFields();
558
559 initialized = true;
560 }
561
562 /**
563 * Reads non-proxy class descriptor information from given input stream.
564 * The resulting class descriptor is not fully functional; it can only be
565 * used as input to the ObjectInputStream.resolveClass() and
566 * ObjectStreamClass.initNonProxy() methods.
567 */
568 void readNonProxy(ObjectInputStream in)
569 throws IOException, ClassNotFoundException
570 {
571 name = in.readUTF();
572 suid = in.readLong();
588 serializable = externalizable || sflag;
589 isEnum = ((flags & ObjectStreamConstants.SC_ENUM) != 0);
590 if (isEnum && suid.longValue() != 0L) {
591 throw new InvalidClassException(name,
592 "enum descriptor has non-zero serialVersionUID: " + suid);
593 }
594
595 int numFields = in.readShort();
596 if (isEnum && numFields != 0) {
597 throw new InvalidClassException(name,
598 "enum descriptor has non-zero field count: " + numFields);
599 }
600 fields = (numFields > 0) ?
601 new ObjectStreamField[numFields] : NO_FIELDS;
602 for (int i = 0; i < numFields; i++) {
603 char tcode = (char) in.readByte();
604 String fname = in.readUTF();
605 String signature = ((tcode == 'L') || (tcode == '[')) ?
606 in.readTypeString() : String.valueOf(tcode);
607 try {
608 fields[i] = new ObjectStreamField(fname, signature, false);
609 } catch (RuntimeException e) {
610 throw new InvalidClassException(name,
611 "invalid descriptor for field " +
612 fname, e);
613 }
614 }
615 computeFieldOffsets();
616 }
617
618 /**
619 * Writes non-proxy class descriptor information to given output stream.
620 */
621 void writeNonProxy(ObjectOutputStream out) throws IOException {
622 out.writeUTF(name);
623 out.writeLong(getSerialVersionUID());
624
625 byte flags = 0;
626 if (externalizable) {
627 flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
628 int protocol = out.getProtocolVersion();
799 }
800
801 /**
802 * Returns true if represented class implements Externalizable, false
803 * otherwise.
804 */
805 boolean isExternalizable() {
806 requireInitialized();
807 return externalizable;
808 }
809
810 /**
811 * Returns true if represented class implements Serializable, false
812 * otherwise.
813 */
814 boolean isSerializable() {
815 requireInitialized();
816 return serializable;
817 }
818
819 /**
820 * Returns true if class descriptor represents externalizable class that
821 * has written its data in 1.2 (block data) format, false otherwise.
822 */
823 boolean hasBlockExternalData() {
824 requireInitialized();
825 return hasBlockExternalData;
826 }
827
828 /**
829 * Returns true if class descriptor represents serializable (but not
830 * externalizable) class which has written its data via a custom
831 * writeObject() method, false otherwise.
832 */
833 boolean hasWriteObjectData() {
834 requireInitialized();
835 return hasWriteObjectData;
836 }
837
838 /**
839 * Returns true if represented class is serializable/externalizable and can
840 * be instantiated by the serialization runtime--i.e., if it is
841 * externalizable and defines a public no-arg constructor, or if it is
842 * non-externalizable and its first non-serializable superclass defines an
843 * accessible no-arg constructor. Otherwise, returns false.
844 */
845 boolean isInstantiable() {
846 requireInitialized();
847 return (cons != null);
848 }
849
850 /**
851 * Returns true if represented class is serializable (but not
852 * externalizable) and defines a conformant writeObject method. Otherwise,
853 * returns false.
854 */
855 boolean hasWriteObjectMethod() {
856 requireInitialized();
857 return (writeObjectMethod != null);
858 }
859
860 /**
861 * Returns true if represented class is serializable (but not
862 * externalizable) and defines a conformant readObject method. Otherwise,
863 * returns false.
864 */
865 boolean hasReadObjectMethod() {
866 requireInitialized();
867 return (readObjectMethod != null);
1084 /**
1085 * Class representing the portion of an object's serialized form allotted
1086 * to data described by a given class descriptor. If "hasData" is false,
1087 * the object's serialized form does not contain data associated with the
1088 * class descriptor.
1089 */
1090 static class ClassDataSlot {
1091
1092 /** class descriptor "occupying" this slot */
1093 final ObjectStreamClass desc;
1094 /** true if serialized form includes data for this slot's descriptor */
1095 final boolean hasData;
1096
1097 ClassDataSlot(ObjectStreamClass desc, boolean hasData) {
1098 this.desc = desc;
1099 this.hasData = hasData;
1100 }
1101 }
1102
1103 /**
1104 * Returns array of ClassDataSlot instances representing the data layout
1105 * (including superclass data) for serialized objects described by this
1106 * class descriptor. ClassDataSlots are ordered by inheritance with those
1107 * containing "higher" superclasses appearing first. The final
1108 * ClassDataSlot contains a reference to this descriptor.
1109 */
1110 ClassDataSlot[] getClassDataLayout() throws InvalidClassException {
1111 // REMIND: synchronize instead of relying on volatile?
1112 if (dataLayout == null) {
1113 dataLayout = getClassDataLayout0();
1114 }
1115 return dataLayout;
1116 }
1117
1118 private ClassDataSlot[] getClassDataLayout0()
1119 throws InvalidClassException
1120 {
1121 ArrayList<ClassDataSlot> slots = new ArrayList<>();
1122 Class<?> start = cl, end = cl;
1123
1124 // locate closest non-serializable superclass
1125 while (end != null && Serializable.class.isAssignableFrom(end)) {
1126 end = end.getSuperclass();
1127 }
1128
1129 HashSet<String> oscNames = new HashSet<>(3);
1130
1131 for (ObjectStreamClass d = this; d != null; d = d.superDesc) {
1132 if (oscNames.contains(d.name)) {
1133 throw new InvalidClassException("Circular reference.");
1134 } else {
1135 oscNames.add(d.name);
1136 }
1137
1138 // search up inheritance hierarchy for class with matching name
1139 String searchName = (d.cl != null) ? d.cl.getName() : d.name;
1140 Class<?> match = null;
1149 if (match != null) {
1150 for (Class<?> c = start; c != match; c = c.getSuperclass()) {
1151 slots.add(new ClassDataSlot(
1152 ObjectStreamClass.lookup(c, true), false));
1153 }
1154 start = match.getSuperclass();
1155 }
1156
1157 // record descriptor/class pairing
1158 slots.add(new ClassDataSlot(d.getVariantFor(match), true));
1159 }
1160
1161 // add "no data" slot for any leftover unmatched classes
1162 for (Class<?> c = start; c != end; c = c.getSuperclass()) {
1163 slots.add(new ClassDataSlot(
1164 ObjectStreamClass.lookup(c, true), false));
1165 }
1166
1167 // order slots from superclass -> subclass
1168 Collections.reverse(slots);
1169 return slots.toArray(new ClassDataSlot[slots.size()]);
1170 }
1171
1172 /**
1173 * Returns aggregate size (in bytes) of marshalled primitive field values
1174 * for represented class.
1175 */
1176 int getPrimDataSize() {
1177 return primDataSize;
1178 }
1179
1180 /**
1181 * Returns number of non-primitive serializable fields of represented
1182 * class.
1183 */
1184 int getNumObjFields() {
1185 return numObjFields;
1186 }
1187
1188 /**
1189 * Fetches the serializable primitive field values of object obj and
1277 /**
1278 * If given class is the same as the class associated with this class
1279 * descriptor, returns reference to this class descriptor. Otherwise,
1280 * returns variant of this class descriptor bound to given class.
1281 */
1282 private ObjectStreamClass getVariantFor(Class<?> cl)
1283 throws InvalidClassException
1284 {
1285 if (this.cl == cl) {
1286 return this;
1287 }
1288 ObjectStreamClass desc = new ObjectStreamClass();
1289 if (isProxy) {
1290 desc.initProxy(cl, null, superDesc);
1291 } else {
1292 desc.initNonProxy(this, cl, null, superDesc);
1293 }
1294 return desc;
1295 }
1296
1297 /**
1298 * Returns public no-arg constructor of given class, or null if none found.
1299 * Access checks are disabled on the returned constructor (if any), since
1300 * the defining class may still be non-public.
1301 */
1302 private static Constructor<?> getExternalizableConstructor(Class<?> cl) {
1303 try {
1304 Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
1305 cons.setAccessible(true);
1306 return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
1307 cons : null;
1308 } catch (NoSuchMethodException ex) {
1309 return null;
1310 }
1311 }
1312
1313 /**
1314 * Returns subclass-accessible no-arg constructor of first non-serializable
1315 * superclass, or null if none found. Access checks are disabled on the
1316 * returned constructor (if any).
1317 */
1318 private static Constructor<?> getSerializableConstructor(Class<?> cl) {
1319 return ReflectionFactory.getReflectionFactory().newConstructorForSerialization(cl);
1320 }
1321
1322 /**
1323 * Returns the canonical constructor for the given record class, or null if
1324 * the not found ( which should never happen for correctly generated record
1325 * classes ).
1326 */
1327 private static MethodHandle canonicalRecordCtr(Class<?> cls) {
1328 assert cls.isRecord() : "Expected record, got: " + cls;
1519
1520 ObjectStreamField[] boundFields =
1521 new ObjectStreamField[serialPersistentFields.length];
1522 Set<String> fieldNames = HashSet.newHashSet(serialPersistentFields.length);
1523
1524 for (int i = 0; i < serialPersistentFields.length; i++) {
1525 ObjectStreamField spf = serialPersistentFields[i];
1526
1527 String fname = spf.getName();
1528 if (fieldNames.contains(fname)) {
1529 throw new InvalidClassException(
1530 "multiple serializable fields named " + fname);
1531 }
1532 fieldNames.add(fname);
1533
1534 try {
1535 Field f = cl.getDeclaredField(fname);
1536 if ((f.getType() == spf.getType()) &&
1537 ((f.getModifiers() & Modifier.STATIC) == 0))
1538 {
1539 boundFields[i] =
1540 new ObjectStreamField(f, spf.isUnshared(), true);
1541 }
1542 } catch (NoSuchFieldException ex) {
1543 }
1544 if (boundFields[i] == null) {
1545 boundFields[i] = new ObjectStreamField(
1546 fname, spf.getType(), spf.isUnshared());
1547 }
1548 }
1549 return boundFields;
1550 }
1551
1552 /**
1553 * Returns array of ObjectStreamFields corresponding to all non-static
1554 * non-transient fields declared by given class. Each ObjectStreamField
1555 * contains a Field object for the field it represents. If no default
1556 * serializable fields exist, NO_FIELDS is returned.
1557 */
1558 private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
1559 Field[] clFields = cl.getDeclaredFields();
1560 ArrayList<ObjectStreamField> list = new ArrayList<>();
1561 int mask = Modifier.STATIC | Modifier.TRANSIENT;
1562
1563 for (int i = 0; i < clFields.length; i++) {
1564 if ((clFields[i].getModifiers() & mask) == 0) {
1565 list.add(new ObjectStreamField(clFields[i], false, true));
1566 }
1567 }
1568 int size = list.size();
1569 return (size == 0) ? NO_FIELDS :
1570 list.toArray(new ObjectStreamField[size]);
1571 }
1572
1573 /**
1574 * Returns explicit serial version UID value declared by given class, or
1575 * null if none.
1576 */
1577 private static Long getDeclaredSUID(Class<?> cl) {
1578 try {
1579 Field f = cl.getDeclaredField("serialVersionUID");
1580 int mask = Modifier.STATIC | Modifier.FINAL;
1581 if ((f.getModifiers() & mask) == mask) {
1582 f.setAccessible(true);
1583 return f.getLong(null);
1584 }
1585 } catch (Exception ex) {
1897 case 'D' -> UNSAFE.putDouble(obj, key, ByteArray.getDouble(buf, off));
1898 default -> throw new InternalError();
1899 }
1900 }
1901 }
1902
1903 /**
1904 * Fetches the serializable object field values of object obj and
1905 * stores them in array vals starting at offset 0. The caller is
1906 * responsible for ensuring that obj is of the proper type.
1907 */
1908 void getObjFieldValues(Object obj, Object[] vals) {
1909 if (obj == null) {
1910 throw new NullPointerException();
1911 }
1912 /* assuming checkDefaultSerialize() has been called on the class
1913 * descriptor this FieldReflector was obtained from, no field keys
1914 * in array should be equal to Unsafe.INVALID_FIELD_OFFSET.
1915 */
1916 for (int i = numPrimFields; i < fields.length; i++) {
1917 vals[offsets[i]] = switch (typeCodes[i]) {
1918 case 'L', '[' -> UNSAFE.getReference(obj, readKeys[i]);
1919 default -> throw new InternalError();
1920 };
1921 }
1922 }
1923
1924 /**
1925 * Checks that the given values, from array vals starting at offset 0,
1926 * are assignable to the given serializable object fields.
1927 * @throws ClassCastException if any value is not assignable
1928 */
1929 void checkObjectFieldValueTypes(Object obj, Object[] vals) {
1930 setObjFieldValues(obj, vals, true);
1931 }
1932
1933 /**
1934 * Sets the serializable object fields of object obj using values from
1935 * array vals starting at offset 0. The caller is responsible for
1936 * ensuring that obj is of the proper type; however, attempts to set a
1937 * field with a value of the wrong type will trigger an appropriate
1938 * ClassCastException.
1939 */
1940 void setObjFieldValues(Object obj, Object[] vals) {
1941 setObjFieldValues(obj, vals, false);
1942 }
1943
1944 private void setObjFieldValues(Object obj, Object[] vals, boolean dryRun) {
1945 if (obj == null) {
1946 throw new NullPointerException();
1947 }
1948 for (int i = numPrimFields; i < fields.length; i++) {
1949 long key = writeKeys[i];
1950 if (key == Unsafe.INVALID_FIELD_OFFSET) {
1951 continue; // discard value
1952 }
1953 switch (typeCodes[i]) {
1954 case 'L', '[' -> {
1955 Object val = vals[offsets[i]];
1956 if (val != null &&
1957 !types[i - numPrimFields].isInstance(val))
1958 {
1959 Field f = fields[i].getField();
1960 throw new ClassCastException(
1961 "cannot assign instance of " +
1962 val.getClass().getName() + " to field " +
1963 f.getDeclaringClass().getName() + "." +
1964 f.getName() + " of type " +
1965 f.getType().getName() + " in instance of " +
1966 obj.getClass().getName());
1967 }
1968 if (!dryRun)
1969 UNSAFE.putReference(obj, key, val);
1970 }
1971 default -> throw new InternalError();
1972 }
1973 }
1974 }
1975 }
1976
1977 /**
1978 * Matches given set of serializable fields with serializable fields
1979 * described by the given local class descriptor, and returns a
1980 * FieldReflector instance capable of setting/getting values from the
1981 * subset of fields that match (non-matching fields are treated as filler,
1982 * for which get operations return default values and set operations
1983 * discard given values). Throws InvalidClassException if unresolvable
1984 * type conflicts exist between the two sets of fields.
1985 */
1986 private static FieldReflector getReflector(ObjectStreamField[] fields,
1987 ObjectStreamClass localDesc)
1988 throws InvalidClassException
1989 {
2061 * a non-local class descriptor. To preserve this (questionable)
2062 * behavior, the ObjectStreamField instances returned by matchFields
2063 * cannot report non-primitive types other than Object.class; hence
2064 * localFields cannot be returned directly.
2065 */
2066
2067 ObjectStreamField[] matches = new ObjectStreamField[fields.length];
2068 for (int i = 0; i < fields.length; i++) {
2069 ObjectStreamField f = fields[i], m = null;
2070 for (int j = 0; j < localFields.length; j++) {
2071 ObjectStreamField lf = localFields[j];
2072 if (f.getName().equals(lf.getName())) {
2073 if ((f.isPrimitive() || lf.isPrimitive()) &&
2074 f.getTypeCode() != lf.getTypeCode())
2075 {
2076 throw new InvalidClassException(localDesc.name,
2077 "incompatible types for field " + f.getName());
2078 }
2079 if (lf.getField() != null) {
2080 m = new ObjectStreamField(
2081 lf.getField(), lf.isUnshared(), false);
2082 } else {
2083 m = new ObjectStreamField(
2084 lf.getName(), lf.getSignature(), lf.isUnshared());
2085 }
2086 }
2087 }
2088 if (m == null) {
2089 m = new ObjectStreamField(
2090 f.getName(), f.getSignature(), false);
2091 }
2092 m.setOffset(f.getOffset());
2093 matches[i] = m;
2094 }
2095 return matches;
2096 }
2097
2098 /**
2099 * A LRA cache of record deserialization constructors.
2100 */
2101 @SuppressWarnings("serial")
2102 private static final class DeserializationConstructorsCache
2103 extends ConcurrentHashMap<DeserializationConstructorsCache.Key, MethodHandle> {
2104
2105 // keep max. 10 cached entries - when the 11th element is inserted the oldest
2106 // is removed and 10 remains - 11 is the biggest map size where internal
2107 // table of 16 elements is sufficient (inserting 12th element would resize it to 32)
2108 private static final int MAX_SIZE = 10;
2109 private Key.Impl first, last; // first and last in FIFO queue
2110
2192 this.fieldTypes = new Class<?>[fields.length];
2193 for (int i = 0; i < fields.length; i++) {
2194 fieldNames[i] = fields[i].getName();
2195 fieldTypes[i] = fields[i].getType();
2196 }
2197 }
2198
2199 @Override
2200 int length() { return fieldNames.length; }
2201
2202 @Override
2203 String fieldName(int i) { return fieldNames[i]; }
2204
2205 @Override
2206 Class<?> fieldType(int i) { return fieldTypes[i]; }
2207 }
2208 }
2209 }
2210
2211 /** Record specific support for retrieving and binding stream field values. */
2212 static final class RecordSupport {
2213 /**
2214 * Returns canonical record constructor adapted to take two arguments:
2215 * {@code (byte[] primValues, Object[] objValues)}
2216 * and return
2217 * {@code Object}
2218 */
2219 static MethodHandle deserializationCtr(ObjectStreamClass desc) {
2220 // check the cached value 1st
2221 MethodHandle mh = desc.deserializationCtr;
2222 if (mh != null) return mh;
2223 mh = desc.deserializationCtrs.get(desc.getFields(false));
2224 if (mh != null) return desc.deserializationCtr = mh;
2225
2226 // retrieve record components
2227 RecordComponent[] recordComponents = desc.forClass().getRecordComponents();
2228
2229 // retrieve the canonical constructor
2230 // (T1, T2, ..., Tn):TR
2231 mh = desc.getRecordConstructor();
2232
2241 for (int i = recordComponents.length-1; i >= 0; i--) {
2242 String name = recordComponents[i].getName();
2243 Class<?> type = recordComponents[i].getType();
2244 // obtain stream field extractor that extracts argument at
2245 // position i (Ti+1) from primValues and objValues arrays
2246 // (byte[], Object[]):Ti+1
2247 MethodHandle combiner = streamFieldExtractor(name, type, desc);
2248 // fold byte[] privValues and Object[] objValues into argument at position i (Ti+1)
2249 // (..., Ti, Ti+1, byte[], Object[]):Object -> (..., Ti, byte[], Object[]):Object
2250 mh = MethodHandles.foldArguments(mh, i, combiner);
2251 }
2252 // what we are left with is a MethodHandle taking just the primValues
2253 // and objValues arrays and returning the constructed record instance
2254 // (byte[], Object[]):Object
2255
2256 // store it into cache and return the 1st value stored
2257 return desc.deserializationCtr =
2258 desc.deserializationCtrs.putIfAbsentAndGet(desc.getFields(false), mh);
2259 }
2260
2261 /** Returns the number of primitive fields for the given descriptor. */
2262 private static int numberPrimValues(ObjectStreamClass desc) {
2263 ObjectStreamField[] fields = desc.getFields();
2264 int primValueCount = 0;
2265 for (int i = 0; i < fields.length; i++) {
2266 if (fields[i].isPrimitive())
2267 primValueCount++;
2268 else
2269 break; // can be no more
2270 }
2271 return primValueCount;
2272 }
2273
2274 /**
2275 * Returns extractor MethodHandle taking the primValues and objValues arrays
2276 * and extracting the argument of canonical constructor with given name and type
2277 * or producing default value for the given type if the field is absent.
2278 */
2279 private static MethodHandle streamFieldExtractor(String pName,
2280 Class<?> pType,
|
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.lang.invoke.MethodHandle;
29 import java.lang.invoke.MethodHandles;
30 import java.lang.invoke.MethodType;
31 import java.lang.reflect.Constructor;
32 import java.lang.reflect.Field;
33 import java.lang.reflect.InaccessibleObjectException;
34 import java.lang.reflect.InvocationTargetException;
35 import java.lang.reflect.RecordComponent;
36 import java.lang.reflect.Member;
37 import java.lang.reflect.Method;
38 import java.lang.reflect.Modifier;
39 import java.lang.reflect.Proxy;
40 import java.security.MessageDigest;
41 import java.security.NoSuchAlgorithmException;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.Comparator;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.concurrent.ConcurrentHashMap;
51 import java.util.stream.Stream;
52
53 import jdk.internal.MigratedValueClass;
54 import jdk.internal.event.SerializationMisdeclarationEvent;
55 import jdk.internal.misc.Unsafe;
56 import jdk.internal.reflect.ReflectionFactory;
57 import jdk.internal.util.ByteArray;
58 import jdk.internal.value.DeserializeConstructor;
59
60 import static java.io.ObjectInputStream.TRACE;
61
62 /**
63 * Serialization's descriptor for classes. It contains the name and
64 * serialVersionUID of the class. The ObjectStreamClass for a specific class
65 * loaded in this Java VM can be found/created using the lookup method.
66 *
67 * <p>The algorithm to compute the SerialVersionUID is described in
68 * <a href="{@docRoot}/../specs/serialization/class.html#stream-unique-identifiers">
69 * <cite>Java Object Serialization Specification</cite>, Section 4.6, "Stream Unique Identifiers"</a>.
70 *
71 * @spec serialization/index.html Java Object Serialization Specification
72 * @author Mike Warres
73 * @author Roger Riggs
74 * @see ObjectStreamField
75 * @see <a href="{@docRoot}/../specs/serialization/class.html">
76 * <cite>Java Object Serialization Specification,</cite> Section 4, "Class Descriptors"</a>
77 * @since 1.1
78 */
79 public final class ObjectStreamClass implements Serializable {
80
81 /** serialPersistentFields value indicating no serializable fields */
82 public static final ObjectStreamField[] NO_FIELDS =
83 new ObjectStreamField[0];
84
85 @java.io.Serial
86 private static final long serialVersionUID = -6120832682080437368L;
87 /**
88 * {@code ObjectStreamClass} has no fields for default serialization.
89 */
90 @java.io.Serial
91 private static final ObjectStreamField[] serialPersistentFields =
92 NO_FIELDS;
93
94 /**
95 * The mode of deserialization for a class depending on its type and interfaces.
96 * The markers used are {@linkplain java.io.Serializable}, {@linkplain java.io.Externalizable},
97 * Class.isRecord(), Class.isValue(), constructors, and
98 * the presence of methods `readObject`, `writeObject`, `readObjectNoData`, `writeObject`.
99 * ObjectInputStream dispatches on the mode to construct objects from the stream.
100 */
101 enum DeserializationMode {
102 /**
103 * Construct an object from the stream for a class that has only default read object behaviors.
104 * All classes and superclasses use defaultReadObject; no custom readObject or readObjectNoData.
105 * The new instance is entered in the handle table if it is unshared,
106 * allowing it to escape before it is initialized.
107 * For each object, all the fields are read before any are assigned.
108 * The `readObject` and `readObjectNoData` methods are not present and are not called.
109 */
110 READ_OBJECT_DEFAULT,
111 /**
112 * Creates a new object and invokes its readExternal method to read its contents.
113 * If the class is instantiable, read externalizable data by invoking readExternal()
114 * method of obj; otherwise, attempts to skip over externalizable data.
115 * Expects that passHandle is set to obj's handle before this method is
116 * called. The new object is entered in the handle table immediately,
117 * allowing it to leak before it is completely read.
118 */
119 READ_EXTERNALIZABLE,
120 /**
121 * Read all the record fields and invoke its canonical constructor.
122 * Construct the record using its canonical constructor.
123 * The new record is entered in the handle table only after the constructor returns.
124 */
125 READ_RECORD,
126 /**
127 * Fully custom read from the stream to create an instance.
128 * If the class is not instantiatable or is tagged with ClassNotFoundException
129 * the data in the stream for the class is read and discarded. {@link #READ_NO_LOCAL_CLASS}
130 * The instance is created and set in the handle table, allowing it to leak before it is initialized.
131 * For each serializable class in the stream, from superclass to subclass the
132 * stream values are read by the `readObject` method, if present, or defaultReadObject.
133 * Custom inline data is discarded if not consumed by the class `readObject` method.
134 */
135 READ_OBJECT_CUSTOM,
136 /**
137 * Construct an object by reading the values of all fields and
138 * invoking a constructor or static factory method.
139 * The constructor or static factory method is selected by matching its parameters with the
140 * sequence of field types of the serializable fields of the local class and superclasses.
141 * Invoke the constructor with all the values from the stream, inserting
142 * defaults and dropping extra values as necessary.
143 * This is very similar to the reading of records, except for the identification of
144 * the constructor or static factory.
145 */
146 READ_OBJECT_VALUE,
147 /**
148 * Read and discard an entire object, leaving a null reference in the HandleTable.
149 * The descriptor of the class in the stream is used to read the fields from the stream.
150 * There is no instance in which to store the field values.
151 * Custom data following the fields of any slot is read and discarded.
152 * References to nested objects are read and retained in the
153 * handle table using the regular mechanism.
154 * Handles later in the stream may refer to the nested objects.
155 */
156 READ_NO_LOCAL_CLASS,
157 }
158
159 private static class Caches {
160 /** cache mapping local classes -> descriptors */
161 static final ClassCache<ObjectStreamClass> localDescs =
162 new ClassCache<>() {
163 @Override
164 protected ObjectStreamClass computeValue(Class<?> type) {
165 return new ObjectStreamClass(type);
166 }
167 };
168
169 /** cache mapping field group/local desc pairs -> field reflectors */
170 static final ClassCache<Map<FieldReflectorKey, FieldReflector>> reflectors =
171 new ClassCache<>() {
172 @Override
173 protected Map<FieldReflectorKey, FieldReflector> computeValue(Class<?> type) {
174 return new ConcurrentHashMap<>();
175 }
176 };
177 }
178
179 /** class associated with this descriptor (if any) */
180 private Class<?> cl;
181 /** name of class represented by this descriptor */
182 private String name;
183 /** serialVersionUID of represented class (null if not computed yet) */
184 private volatile Long suid;
185
186 /** true if represents dynamic proxy class */
187 private boolean isProxy;
188 /** true if represents enum type */
189 private boolean isEnum;
190 /** true if represents record type */
191 private boolean isRecord;
192 /** true if represents a value class */
193 private boolean isValue;
194 /** The DeserializationMode for this class. */
195 private DeserializationMode factoryMode;
196 /** true if represented class implements Serializable */
197 private boolean serializable;
198 /** true if represented class implements Externalizable */
199 private boolean externalizable;
200 /** true if desc has data written by class-defined writeObject method */
201 private boolean hasWriteObjectData;
202 /**
203 * true if desc has externalizable data written in block data format; this
204 * must be true by default to accommodate ObjectInputStream subclasses which
205 * override readClassDescriptor() to return class descriptors obtained from
206 * ObjectStreamClass.lookup() (see 4461737)
207 */
208 private boolean hasBlockExternalData = true;
209
210 /**
211 * Contains information about InvalidClassException instances to be thrown
212 * when attempting operations on an invalid class. Note that instances of
213 * this class are immutable and are potentially shared among
214 * ObjectStreamClass instances.
215 */
233 }
234
235 /** exception (if any) thrown while attempting to resolve class */
236 private ClassNotFoundException resolveEx;
237 /** exception (if any) to throw if non-enum deserialization attempted */
238 private ExceptionInfo deserializeEx;
239 /** exception (if any) to throw if non-enum serialization attempted */
240 private ExceptionInfo serializeEx;
241 /** exception (if any) to throw if default serialization attempted */
242 private ExceptionInfo defaultSerializeEx;
243
244 /** serializable fields */
245 private ObjectStreamField[] fields;
246 /** aggregate marshalled size of primitive fields */
247 private int primDataSize;
248 /** number of non-primitive fields */
249 private int numObjFields;
250 /** reflector for setting/getting serializable field values */
251 private FieldReflector fieldRefl;
252 /** data layout of serialized objects described by this class desc */
253 private volatile List<ClassDataSlot> dataLayout;
254
255 /** serialization-appropriate constructor, or null if none */
256 private Constructor<?> cons;
257 /** record canonical constructor (shared among OSCs for same class), or null */
258 private MethodHandle canonicalCtr;
259 /** cache of record deserialization constructors per unique set of stream fields
260 * (shared among OSCs for same class), or null */
261 private DeserializationConstructorsCache deserializationCtrs;
262 /** session-cache of record deserialization constructor
263 * (in de-serialized OSC only), or null */
264 private MethodHandle deserializationCtr;
265
266 /** class-defined writeObject method, or null if none */
267 private Method writeObjectMethod;
268 /** class-defined readObject method, or null if none */
269 private Method readObjectMethod;
270 /** class-defined readObjectNoData method, or null if none */
271 private Method readObjectNoDataMethod;
272 /** class-defined writeReplace method, or null if none */
273 private Method writeReplaceMethod;
397 * @param cl class to look up
398 * @param all if true, return descriptors for all classes; if false, only
399 * return descriptors for serializable classes
400 */
401 static ObjectStreamClass lookup(Class<?> cl, boolean all) {
402 if (!(all || Serializable.class.isAssignableFrom(cl))) {
403 return null;
404 }
405 return Caches.localDescs.get(cl);
406 }
407
408 /**
409 * Creates local class descriptor representing given class.
410 */
411 private ObjectStreamClass(final Class<?> cl) {
412 this.cl = cl;
413 name = cl.getName();
414 isProxy = Proxy.isProxyClass(cl);
415 isEnum = Enum.class.isAssignableFrom(cl);
416 isRecord = cl.isRecord();
417 isValue = cl.isValue();
418 serializable = Serializable.class.isAssignableFrom(cl);
419 externalizable = Externalizable.class.isAssignableFrom(cl);
420
421 Class<?> superCl = cl.getSuperclass();
422 superDesc = (superCl != null) ? lookup(superCl, false) : null;
423 localDesc = this;
424
425 if (serializable) {
426 if (isEnum) {
427 suid = 0L;
428 fields = NO_FIELDS;
429 } else if (cl.isArray()) {
430 fields = NO_FIELDS;
431 } else {
432 suid = getDeclaredSUID(cl);
433 try {
434 fields = getSerialFields(cl);
435 computeFieldOffsets();
436 } catch (InvalidClassException e) {
437 serializeEx = deserializeEx =
438 new ExceptionInfo(e.classname, e.getMessage());
439 fields = NO_FIELDS;
440 }
441
442 if (isRecord) {
443 factoryMode = DeserializationMode.READ_RECORD;
444 canonicalCtr = canonicalRecordCtr(cl);
445 deserializationCtrs = new DeserializationConstructorsCache();
446 } else if (externalizable) {
447 factoryMode = DeserializationMode.READ_EXTERNALIZABLE;
448 if (cl.isIdentity()) {
449 cons = getExternalizableConstructor(cl);
450 } else {
451 serializeEx = deserializeEx = new ExceptionInfo(cl.getName(),
452 "Externalizable not valid for value class");
453 }
454 } else if (cl.isValue()) {
455 factoryMode = DeserializationMode.READ_OBJECT_VALUE;
456 if (!cl.isAnnotationPresent(MigratedValueClass.class)) {
457 serializeEx = deserializeEx = new ExceptionInfo(cl.getName(),
458 "Value class serialization is only supported with `writeReplace`");
459 } else if (Modifier.isAbstract(cl.getModifiers())) {
460 serializeEx = deserializeEx = new ExceptionInfo(cl.getName(),
461 "value class is abstract");
462 } else {
463 // Value classes should have constructor(s) annotated with {@link DeserializeConstructor}
464 canonicalCtr = getDeserializingValueCons(cl, fields);
465 deserializationCtrs = new DeserializationConstructorsCache(); factoryMode = DeserializationMode.READ_OBJECT_VALUE;
466 if (canonicalCtr == null) {
467 serializeEx = deserializeEx = new ExceptionInfo(cl.getName(),
468 "no constructor or factory found for migrated value class");
469 }
470 }
471 } else {
472 cons = getSerializableConstructor(cl);
473 writeObjectMethod = getPrivateMethod(cl, "writeObject",
474 new Class<?>[]{ObjectOutputStream.class},
475 Void.TYPE);
476 readObjectMethod = getPrivateMethod(cl, "readObject",
477 new Class<?>[]{ObjectInputStream.class},
478 Void.TYPE);
479 readObjectNoDataMethod = getPrivateMethod(
480 cl, "readObjectNoData", null, Void.TYPE);
481 hasWriteObjectData = (writeObjectMethod != null);
482 factoryMode = ((superDesc == null || superDesc.factoryMode() == DeserializationMode.READ_OBJECT_DEFAULT)
483 && readObjectMethod == null && readObjectNoDataMethod == null)
484 ? DeserializationMode.READ_OBJECT_DEFAULT
485 : DeserializationMode.READ_OBJECT_CUSTOM;
486 }
487 writeReplaceMethod = getInheritableMethod(
488 cl, "writeReplace", null, Object.class);
489 readResolveMethod = getInheritableMethod(
490 cl, "readResolve", null, Object.class);
491 }
492 } else {
493 suid = 0L;
494 fields = NO_FIELDS;
495 }
496
497 try {
498 fieldRefl = getReflector(fields, this);
499 } catch (InvalidClassException ex) {
500 // field mismatches impossible when matching local fields vs. self
501 throw new InternalError(ex);
502 }
503
504 if (deserializeEx == null) {
505 if (isEnum) {
506 deserializeEx = new ExceptionInfo(name, "enum type");
507 } else if (cons == null && !(isRecord | isValue)) {
508 deserializeEx = new ExceptionInfo(name, "no valid constructor");
509 }
510 }
511 if (isRecord && canonicalCtr == null) {
512 deserializeEx = new ExceptionInfo(name, "record canonical constructor not found");
513 } else {
514 for (int i = 0; i < fields.length; i++) {
515 if (fields[i].getField() == null) {
516 defaultSerializeEx = new ExceptionInfo(
517 name, "unmatched serializable field(s) declared");
518 }
519 }
520 }
521 initialized = true;
522
523 if (SerializationMisdeclarationEvent.enabled() && serializable) {
524 SerializationMisdeclarationChecker.checkMisdeclarations(cl);
525 }
526 }
527
546 if (!osc.isProxy) {
547 throw new InvalidClassException(
548 "cannot bind proxy descriptor to a non-proxy class");
549 }
550 }
551 this.cl = cl;
552 this.resolveEx = resolveEx;
553 this.superDesc = superDesc;
554 isProxy = true;
555 serializable = true;
556 suid = 0L;
557 fields = NO_FIELDS;
558 if (osc != null) {
559 localDesc = osc;
560 name = localDesc.name;
561 externalizable = localDesc.externalizable;
562 writeReplaceMethod = localDesc.writeReplaceMethod;
563 readResolveMethod = localDesc.readResolveMethod;
564 deserializeEx = localDesc.deserializeEx;
565 cons = localDesc.cons;
566 factoryMode = localDesc.factoryMode;
567 } else {
568 factoryMode = DeserializationMode.READ_OBJECT_DEFAULT;
569 }
570 fieldRefl = getReflector(fields, localDesc);
571 initialized = true;
572 }
573
574 /**
575 * Initializes class descriptor representing a non-proxy class.
576 */
577 void initNonProxy(ObjectStreamClass model,
578 Class<?> cl,
579 ClassNotFoundException resolveEx,
580 ObjectStreamClass superDesc)
581 throws InvalidClassException
582 {
583 long suid = model.getSerialVersionUID();
584 ObjectStreamClass osc = null;
585 if (cl != null) {
586 osc = lookup(cl, true);
587 if (osc.isProxy) {
588 throw new InvalidClassException(
627 }
628
629 this.cl = cl;
630 this.resolveEx = resolveEx;
631 this.superDesc = superDesc;
632 name = model.name;
633 this.suid = suid;
634 isProxy = false;
635 isEnum = model.isEnum;
636 serializable = model.serializable;
637 externalizable = model.externalizable;
638 hasBlockExternalData = model.hasBlockExternalData;
639 hasWriteObjectData = model.hasWriteObjectData;
640 fields = model.fields;
641 primDataSize = model.primDataSize;
642 numObjFields = model.numObjFields;
643
644 if (osc != null) {
645 localDesc = osc;
646 isRecord = localDesc.isRecord;
647 isValue = localDesc.isValue;
648 // canonical record constructor is shared
649 canonicalCtr = localDesc.canonicalCtr;
650 // cache of deserialization constructors is shared
651 deserializationCtrs = localDesc.deserializationCtrs;
652 writeObjectMethod = localDesc.writeObjectMethod;
653 readObjectMethod = localDesc.readObjectMethod;
654 readObjectNoDataMethod = localDesc.readObjectNoDataMethod;
655 writeReplaceMethod = localDesc.writeReplaceMethod;
656 readResolveMethod = localDesc.readResolveMethod;
657 if (deserializeEx == null) {
658 deserializeEx = localDesc.deserializeEx;
659 }
660 assert cl.isRecord() ? localDesc.cons == null : true;
661 cons = localDesc.cons;
662 factoryMode = localDesc.factoryMode;
663 } else {
664 // No local class, read data using only the schema from the stream
665 factoryMode = (externalizable)
666 ? DeserializationMode.READ_EXTERNALIZABLE
667 : DeserializationMode.READ_NO_LOCAL_CLASS;
668 }
669
670 fieldRefl = getReflector(fields, localDesc);
671 // reassign to matched fields so as to reflect local unshared settings
672 fields = fieldRefl.getFields();
673
674 initialized = true;
675 }
676
677 /**
678 * Reads non-proxy class descriptor information from given input stream.
679 * The resulting class descriptor is not fully functional; it can only be
680 * used as input to the ObjectInputStream.resolveClass() and
681 * ObjectStreamClass.initNonProxy() methods.
682 */
683 void readNonProxy(ObjectInputStream in)
684 throws IOException, ClassNotFoundException
685 {
686 name = in.readUTF();
687 suid = in.readLong();
703 serializable = externalizable || sflag;
704 isEnum = ((flags & ObjectStreamConstants.SC_ENUM) != 0);
705 if (isEnum && suid.longValue() != 0L) {
706 throw new InvalidClassException(name,
707 "enum descriptor has non-zero serialVersionUID: " + suid);
708 }
709
710 int numFields = in.readShort();
711 if (isEnum && numFields != 0) {
712 throw new InvalidClassException(name,
713 "enum descriptor has non-zero field count: " + numFields);
714 }
715 fields = (numFields > 0) ?
716 new ObjectStreamField[numFields] : NO_FIELDS;
717 for (int i = 0; i < numFields; i++) {
718 char tcode = (char) in.readByte();
719 String fname = in.readUTF();
720 String signature = ((tcode == 'L') || (tcode == '[')) ?
721 in.readTypeString() : String.valueOf(tcode);
722 try {
723 fields[i] = new ObjectStreamField(fname, signature, false, -1);
724 } catch (RuntimeException e) {
725 throw new InvalidClassException(name,
726 "invalid descriptor for field " +
727 fname, e);
728 }
729 }
730 computeFieldOffsets();
731 }
732
733 /**
734 * Writes non-proxy class descriptor information to given output stream.
735 */
736 void writeNonProxy(ObjectOutputStream out) throws IOException {
737 out.writeUTF(name);
738 out.writeLong(getSerialVersionUID());
739
740 byte flags = 0;
741 if (externalizable) {
742 flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
743 int protocol = out.getProtocolVersion();
914 }
915
916 /**
917 * Returns true if represented class implements Externalizable, false
918 * otherwise.
919 */
920 boolean isExternalizable() {
921 requireInitialized();
922 return externalizable;
923 }
924
925 /**
926 * Returns true if represented class implements Serializable, false
927 * otherwise.
928 */
929 boolean isSerializable() {
930 requireInitialized();
931 return serializable;
932 }
933
934 /**
935 * {@return {code true} if the class is a value class, {@code false} otherwise}
936 */
937 boolean isValue() {
938 requireInitialized();
939 return isValue;
940 }
941
942 /**
943 * {@return the factory mode for deserialization}
944 */
945 DeserializationMode factoryMode() {
946 requireInitialized();
947 return factoryMode;
948 }
949
950 /**
951 * Returns true if class descriptor represents externalizable class that
952 * has written its data in 1.2 (block data) format, false otherwise.
953 */
954 boolean hasBlockExternalData() {
955 requireInitialized();
956 return hasBlockExternalData;
957 }
958
959 /**
960 * Returns true if class descriptor represents serializable (but not
961 * externalizable) class which has written its data via a custom
962 * writeObject() method, false otherwise.
963 */
964 boolean hasWriteObjectData() {
965 requireInitialized();
966 return hasWriteObjectData;
967 }
968
969 /**
970 * Returns true if represented class is serializable/externalizable and can
971 * be instantiated by the serialization runtime--i.e., if it is
972 * externalizable and defines a public no-arg constructor, if it is
973 * non-externalizable and its first non-serializable superclass defines an
974 * accessible no-arg constructor, or if the class is a value class with a @DeserializeConstructor
975 * constructor or static factory.
976 * Otherwise, returns false.
977 */
978 boolean isInstantiable() {
979 requireInitialized();
980 return (cons != null || (isValue() && canonicalCtr != null));
981 }
982
983 /**
984 * Returns true if represented class is serializable (but not
985 * externalizable) and defines a conformant writeObject method. Otherwise,
986 * returns false.
987 */
988 boolean hasWriteObjectMethod() {
989 requireInitialized();
990 return (writeObjectMethod != null);
991 }
992
993 /**
994 * Returns true if represented class is serializable (but not
995 * externalizable) and defines a conformant readObject method. Otherwise,
996 * returns false.
997 */
998 boolean hasReadObjectMethod() {
999 requireInitialized();
1000 return (readObjectMethod != null);
1217 /**
1218 * Class representing the portion of an object's serialized form allotted
1219 * to data described by a given class descriptor. If "hasData" is false,
1220 * the object's serialized form does not contain data associated with the
1221 * class descriptor.
1222 */
1223 static class ClassDataSlot {
1224
1225 /** class descriptor "occupying" this slot */
1226 final ObjectStreamClass desc;
1227 /** true if serialized form includes data for this slot's descriptor */
1228 final boolean hasData;
1229
1230 ClassDataSlot(ObjectStreamClass desc, boolean hasData) {
1231 this.desc = desc;
1232 this.hasData = hasData;
1233 }
1234 }
1235
1236 /**
1237 * Returns a List of ClassDataSlot instances representing the data layout
1238 * (including superclass data) for serialized objects described by this
1239 * class descriptor. ClassDataSlots are ordered by inheritance with those
1240 * containing "higher" superclasses appearing first. The final
1241 * ClassDataSlot contains a reference to this descriptor.
1242 */
1243 List<ClassDataSlot> getClassDataLayout() throws InvalidClassException {
1244 // REMIND: synchronize instead of relying on volatile?
1245 List<ClassDataSlot> layout = dataLayout;
1246 if (layout != null)
1247 return layout;
1248
1249 ArrayList<ClassDataSlot> slots = new ArrayList<>();
1250 Class<?> start = cl, end = cl;
1251
1252 // locate closest non-serializable superclass
1253 while (end != null && Serializable.class.isAssignableFrom(end)) {
1254 end = end.getSuperclass();
1255 }
1256
1257 HashSet<String> oscNames = new HashSet<>(3);
1258
1259 for (ObjectStreamClass d = this; d != null; d = d.superDesc) {
1260 if (oscNames.contains(d.name)) {
1261 throw new InvalidClassException("Circular reference.");
1262 } else {
1263 oscNames.add(d.name);
1264 }
1265
1266 // search up inheritance hierarchy for class with matching name
1267 String searchName = (d.cl != null) ? d.cl.getName() : d.name;
1268 Class<?> match = null;
1277 if (match != null) {
1278 for (Class<?> c = start; c != match; c = c.getSuperclass()) {
1279 slots.add(new ClassDataSlot(
1280 ObjectStreamClass.lookup(c, true), false));
1281 }
1282 start = match.getSuperclass();
1283 }
1284
1285 // record descriptor/class pairing
1286 slots.add(new ClassDataSlot(d.getVariantFor(match), true));
1287 }
1288
1289 // add "no data" slot for any leftover unmatched classes
1290 for (Class<?> c = start; c != end; c = c.getSuperclass()) {
1291 slots.add(new ClassDataSlot(
1292 ObjectStreamClass.lookup(c, true), false));
1293 }
1294
1295 // order slots from superclass -> subclass
1296 Collections.reverse(slots);
1297 dataLayout = slots;
1298 return slots;
1299 }
1300
1301 /**
1302 * Returns aggregate size (in bytes) of marshalled primitive field values
1303 * for represented class.
1304 */
1305 int getPrimDataSize() {
1306 return primDataSize;
1307 }
1308
1309 /**
1310 * Returns number of non-primitive serializable fields of represented
1311 * class.
1312 */
1313 int getNumObjFields() {
1314 return numObjFields;
1315 }
1316
1317 /**
1318 * Fetches the serializable primitive field values of object obj and
1406 /**
1407 * If given class is the same as the class associated with this class
1408 * descriptor, returns reference to this class descriptor. Otherwise,
1409 * returns variant of this class descriptor bound to given class.
1410 */
1411 private ObjectStreamClass getVariantFor(Class<?> cl)
1412 throws InvalidClassException
1413 {
1414 if (this.cl == cl) {
1415 return this;
1416 }
1417 ObjectStreamClass desc = new ObjectStreamClass();
1418 if (isProxy) {
1419 desc.initProxy(cl, null, superDesc);
1420 } else {
1421 desc.initNonProxy(this, cl, null, superDesc);
1422 }
1423 return desc;
1424 }
1425
1426 /**
1427 * Return a method handle for the static method or constructor(s) that matches the
1428 * serializable fields and annotated with {@link DeserializeConstructor}.
1429 * The descriptor for the class is still being initialized, so is passed the fields needed.
1430 * @param clazz The class to query
1431 * @param fields the serializable fields of the class
1432 * @return a MethodHandle, null if none found
1433 */
1434 @SuppressWarnings("unchecked")
1435 private static MethodHandle getDeserializingValueCons(Class<?> clazz,
1436 ObjectStreamField[] fields) {
1437 // Search for annotated static factory in methods or constructors
1438 MethodHandles.Lookup lookup = MethodHandles.lookup();
1439 MethodHandle mh = Stream.concat(
1440 Arrays.stream(clazz.getDeclaredMethods()).filter(m -> Modifier.isStatic(m.getModifiers())),
1441 Arrays.stream(clazz.getDeclaredConstructors()))
1442 .filter(m -> m.isAnnotationPresent(DeserializeConstructor.class))
1443 .map(m -> {
1444 try {
1445 m.setAccessible(true);
1446 return (m instanceof Constructor<?> cons)
1447 ? lookup.unreflectConstructor(cons)
1448 : lookup.unreflect(((Method) m));
1449 } catch (IllegalAccessException iae) {
1450 throw new InternalError(iae); // should not occur after setAccessible
1451 }})
1452 .filter(m -> matchFactoryParamTypes(clazz, m, fields))
1453 .findFirst().orElse(null);
1454 TRACE("DeserializeConstructor for %s, mh: %s", clazz, mh);
1455 return mh;
1456 }
1457
1458 /**
1459 * Check that the parameters of the factory method match the fields of this class.
1460 *
1461 * @param mh a MethodHandle for a constructor or factory
1462 * @return true if all fields match the parameters, false if not
1463 */
1464 private static boolean matchFactoryParamTypes(Class<?> clazz,
1465 MethodHandle mh,
1466 ObjectStreamField[] fields) {
1467 TRACE(" matchFactoryParams checking class: %s, mh: %s", clazz, mh);
1468 var params = mh.type().parameterList();
1469 if (params.size() != fields.length) {
1470 TRACE(" matchFactoryParams %s, arg count mismatch %d params != %d fields",
1471 clazz, params.size(), fields.length);
1472 return false; // Mismatch in count of fields and parameters
1473 }
1474 for (ObjectStreamField field : fields) {
1475 int argIndex = field.getArgIndex();
1476 final Class<?> paramtype = params.get(argIndex);
1477 if (!field.getType().equals(paramtype)) {
1478 TRACE(" matchFactoryParams %s: argIndex: %d type mismatch field: %s != param: %s",
1479 clazz, argIndex, field.getType(), paramtype);
1480 return false;
1481 }
1482 }
1483 return true;
1484 }
1485
1486 /**
1487 * Returns public no-arg constructor of given class, or null if none found.
1488 * Access checks are disabled on the returned constructor (if any), since
1489 * the defining class may still be non-public.
1490 */
1491 private static Constructor<?> getExternalizableConstructor(Class<?> cl) {
1492 try {
1493 Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
1494 cons.setAccessible(true);
1495 return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
1496 cons : null;
1497 } catch (NoSuchMethodException | InaccessibleObjectException ex) {
1498 return null;
1499 }
1500 }
1501
1502 /**
1503 * Returns subclass-accessible no-arg constructor of first non-serializable
1504 * superclass, or null if none found. Access checks are disabled on the
1505 * returned constructor (if any).
1506 */
1507 private static Constructor<?> getSerializableConstructor(Class<?> cl) {
1508 return ReflectionFactory.getReflectionFactory().newConstructorForSerialization(cl);
1509 }
1510
1511 /**
1512 * Returns the canonical constructor for the given record class, or null if
1513 * the not found ( which should never happen for correctly generated record
1514 * classes ).
1515 */
1516 private static MethodHandle canonicalRecordCtr(Class<?> cls) {
1517 assert cls.isRecord() : "Expected record, got: " + cls;
1708
1709 ObjectStreamField[] boundFields =
1710 new ObjectStreamField[serialPersistentFields.length];
1711 Set<String> fieldNames = HashSet.newHashSet(serialPersistentFields.length);
1712
1713 for (int i = 0; i < serialPersistentFields.length; i++) {
1714 ObjectStreamField spf = serialPersistentFields[i];
1715
1716 String fname = spf.getName();
1717 if (fieldNames.contains(fname)) {
1718 throw new InvalidClassException(
1719 "multiple serializable fields named " + fname);
1720 }
1721 fieldNames.add(fname);
1722
1723 try {
1724 Field f = cl.getDeclaredField(fname);
1725 if ((f.getType() == spf.getType()) &&
1726 ((f.getModifiers() & Modifier.STATIC) == 0))
1727 {
1728 boundFields[i] = new ObjectStreamField(f, spf.isUnshared(), true, i);
1729 }
1730 } catch (NoSuchFieldException ex) {
1731 }
1732 if (boundFields[i] == null) {
1733 boundFields[i] = new ObjectStreamField(fname, spf.getType(), spf.isUnshared(), i);
1734 }
1735 }
1736 return boundFields;
1737 }
1738
1739 /**
1740 * Returns array of ObjectStreamFields corresponding to all non-static
1741 * non-transient fields declared by given class. Each ObjectStreamField
1742 * contains a Field object for the field it represents. If no default
1743 * serializable fields exist, NO_FIELDS is returned.
1744 */
1745 private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
1746 Field[] clFields = cl.getDeclaredFields();
1747 ArrayList<ObjectStreamField> list = new ArrayList<>();
1748 int mask = Modifier.STATIC | Modifier.TRANSIENT;
1749
1750 for (int i = 0, argIndex = 0; i < clFields.length; i++) {
1751 if ((clFields[i].getModifiers() & mask) == 0) {
1752 list.add(new ObjectStreamField(clFields[i], false, true, argIndex++));
1753 }
1754 }
1755 int size = list.size();
1756 return (size == 0) ? NO_FIELDS :
1757 list.toArray(new ObjectStreamField[size]);
1758 }
1759
1760 /**
1761 * Returns explicit serial version UID value declared by given class, or
1762 * null if none.
1763 */
1764 private static Long getDeclaredSUID(Class<?> cl) {
1765 try {
1766 Field f = cl.getDeclaredField("serialVersionUID");
1767 int mask = Modifier.STATIC | Modifier.FINAL;
1768 if ((f.getModifiers() & mask) == mask) {
1769 f.setAccessible(true);
1770 return f.getLong(null);
1771 }
1772 } catch (Exception ex) {
2084 case 'D' -> UNSAFE.putDouble(obj, key, ByteArray.getDouble(buf, off));
2085 default -> throw new InternalError();
2086 }
2087 }
2088 }
2089
2090 /**
2091 * Fetches the serializable object field values of object obj and
2092 * stores them in array vals starting at offset 0. The caller is
2093 * responsible for ensuring that obj is of the proper type.
2094 */
2095 void getObjFieldValues(Object obj, Object[] vals) {
2096 if (obj == null) {
2097 throw new NullPointerException();
2098 }
2099 /* assuming checkDefaultSerialize() has been called on the class
2100 * descriptor this FieldReflector was obtained from, no field keys
2101 * in array should be equal to Unsafe.INVALID_FIELD_OFFSET.
2102 */
2103 for (int i = numPrimFields; i < fields.length; i++) {
2104 Field f = fields[i].getField();
2105 vals[offsets[i]] = switch (typeCodes[i]) {
2106 case 'L', '[' ->
2107 UNSAFE.isFlatField(f)
2108 ? UNSAFE.getFlatValue(obj, readKeys[i], UNSAFE.fieldLayout(f), f.getType())
2109 : UNSAFE.getReference(obj, readKeys[i]);
2110 default -> throw new InternalError();
2111 };
2112 }
2113 }
2114
2115 /**
2116 * Checks that the given values, from array vals starting at offset 0,
2117 * are assignable to the given serializable object fields.
2118 * @throws ClassCastException if any value is not assignable
2119 */
2120 void checkObjectFieldValueTypes(Object obj, Object[] vals) {
2121 setObjFieldValues(obj, vals, true);
2122 }
2123
2124 /**
2125 * Sets the serializable object fields of object obj using values from
2126 * array vals starting at offset 0. The caller is responsible for
2127 * ensuring that obj is of the proper type; however, attempts to set a
2128 * field with a value of the wrong type will trigger an appropriate
2129 * ClassCastException.
2130 */
2131 void setObjFieldValues(Object obj, Object[] vals) {
2132 setObjFieldValues(obj, vals, false);
2133 }
2134
2135 private void setObjFieldValues(Object obj, Object[] vals, boolean dryRun) {
2136 if (obj == null && !dryRun) {
2137 throw new NullPointerException();
2138 }
2139 for (int i = numPrimFields; i < fields.length; i++) {
2140 long key = writeKeys[i];
2141 if (key == Unsafe.INVALID_FIELD_OFFSET) {
2142 continue; // discard value
2143 }
2144 switch (typeCodes[i]) {
2145 case 'L', '[' -> {
2146 Field f = fields[i].getField();
2147 Object val = vals[offsets[i]];
2148 if (val != null &&
2149 !types[i - numPrimFields].isInstance(val))
2150 {
2151 throw new ClassCastException(
2152 "cannot assign instance of " +
2153 val.getClass().getName() + " to field " +
2154 f.getDeclaringClass().getName() + "." +
2155 f.getName() + " of type " +
2156 f.getType().getName() + " in instance of " +
2157 obj.getClass().getName());
2158 }
2159 if (!dryRun) {
2160 if (UNSAFE.isFlatField(f)) {
2161 UNSAFE.putFlatValue(obj, key, UNSAFE.fieldLayout(f), f.getType(), val);
2162 } else {
2163 UNSAFE.putReference(obj, key, val);
2164 }
2165 }
2166 }
2167 default -> throw new InternalError();
2168 }
2169 }
2170 }
2171 }
2172
2173 /**
2174 * Matches given set of serializable fields with serializable fields
2175 * described by the given local class descriptor, and returns a
2176 * FieldReflector instance capable of setting/getting values from the
2177 * subset of fields that match (non-matching fields are treated as filler,
2178 * for which get operations return default values and set operations
2179 * discard given values). Throws InvalidClassException if unresolvable
2180 * type conflicts exist between the two sets of fields.
2181 */
2182 private static FieldReflector getReflector(ObjectStreamField[] fields,
2183 ObjectStreamClass localDesc)
2184 throws InvalidClassException
2185 {
2257 * a non-local class descriptor. To preserve this (questionable)
2258 * behavior, the ObjectStreamField instances returned by matchFields
2259 * cannot report non-primitive types other than Object.class; hence
2260 * localFields cannot be returned directly.
2261 */
2262
2263 ObjectStreamField[] matches = new ObjectStreamField[fields.length];
2264 for (int i = 0; i < fields.length; i++) {
2265 ObjectStreamField f = fields[i], m = null;
2266 for (int j = 0; j < localFields.length; j++) {
2267 ObjectStreamField lf = localFields[j];
2268 if (f.getName().equals(lf.getName())) {
2269 if ((f.isPrimitive() || lf.isPrimitive()) &&
2270 f.getTypeCode() != lf.getTypeCode())
2271 {
2272 throw new InvalidClassException(localDesc.name,
2273 "incompatible types for field " + f.getName());
2274 }
2275 if (lf.getField() != null) {
2276 m = new ObjectStreamField(
2277 lf.getField(), lf.isUnshared(), true, lf.getArgIndex()); // Don't hide type
2278 } else {
2279 m = new ObjectStreamField(
2280 lf.getName(), lf.getSignature(), lf.isUnshared(), lf.getArgIndex());
2281 }
2282 }
2283 }
2284 if (m == null) {
2285 m = new ObjectStreamField(
2286 f.getName(), f.getSignature(), false, -1);
2287 }
2288 m.setOffset(f.getOffset());
2289 matches[i] = m;
2290 }
2291 return matches;
2292 }
2293
2294 /**
2295 * A LRA cache of record deserialization constructors.
2296 */
2297 @SuppressWarnings("serial")
2298 private static final class DeserializationConstructorsCache
2299 extends ConcurrentHashMap<DeserializationConstructorsCache.Key, MethodHandle> {
2300
2301 // keep max. 10 cached entries - when the 11th element is inserted the oldest
2302 // is removed and 10 remains - 11 is the biggest map size where internal
2303 // table of 16 elements is sufficient (inserting 12th element would resize it to 32)
2304 private static final int MAX_SIZE = 10;
2305 private Key.Impl first, last; // first and last in FIFO queue
2306
2388 this.fieldTypes = new Class<?>[fields.length];
2389 for (int i = 0; i < fields.length; i++) {
2390 fieldNames[i] = fields[i].getName();
2391 fieldTypes[i] = fields[i].getType();
2392 }
2393 }
2394
2395 @Override
2396 int length() { return fieldNames.length; }
2397
2398 @Override
2399 String fieldName(int i) { return fieldNames[i]; }
2400
2401 @Override
2402 Class<?> fieldType(int i) { return fieldTypes[i]; }
2403 }
2404 }
2405 }
2406
2407 /** Record specific support for retrieving and binding stream field values. */
2408 static final class ConstructorSupport {
2409 /**
2410 * Returns canonical record constructor adapted to take two arguments:
2411 * {@code (byte[] primValues, Object[] objValues)}
2412 * and return
2413 * {@code Object}
2414 */
2415 static MethodHandle deserializationCtr(ObjectStreamClass desc) {
2416 // check the cached value 1st
2417 MethodHandle mh = desc.deserializationCtr;
2418 if (mh != null) return mh;
2419 mh = desc.deserializationCtrs.get(desc.getFields(false));
2420 if (mh != null) return desc.deserializationCtr = mh;
2421
2422 // retrieve record components
2423 RecordComponent[] recordComponents = desc.forClass().getRecordComponents();
2424
2425 // retrieve the canonical constructor
2426 // (T1, T2, ..., Tn):TR
2427 mh = desc.getRecordConstructor();
2428
2437 for (int i = recordComponents.length-1; i >= 0; i--) {
2438 String name = recordComponents[i].getName();
2439 Class<?> type = recordComponents[i].getType();
2440 // obtain stream field extractor that extracts argument at
2441 // position i (Ti+1) from primValues and objValues arrays
2442 // (byte[], Object[]):Ti+1
2443 MethodHandle combiner = streamFieldExtractor(name, type, desc);
2444 // fold byte[] privValues and Object[] objValues into argument at position i (Ti+1)
2445 // (..., Ti, Ti+1, byte[], Object[]):Object -> (..., Ti, byte[], Object[]):Object
2446 mh = MethodHandles.foldArguments(mh, i, combiner);
2447 }
2448 // what we are left with is a MethodHandle taking just the primValues
2449 // and objValues arrays and returning the constructed record instance
2450 // (byte[], Object[]):Object
2451
2452 // store it into cache and return the 1st value stored
2453 return desc.deserializationCtr =
2454 desc.deserializationCtrs.putIfAbsentAndGet(desc.getFields(false), mh);
2455 }
2456
2457 /**
2458 * Returns value object constructor adapted to take two arguments:
2459 * {@code (byte[] primValues, Object[] objValues)} and return {@code Object}
2460 */
2461 static MethodHandle deserializationValueCons(ObjectStreamClass desc) {
2462 // check the cached value 1st
2463 MethodHandle mh = desc.deserializationCtr;
2464 if (mh != null) return mh;
2465 mh = desc.deserializationCtrs.get(desc.getFields(false));
2466 if (mh != null) return desc.deserializationCtr = mh;
2467
2468 // retrieve the selected constructor
2469 // (T1, T2, ..., Tn):TR
2470 ObjectStreamClass localDesc = desc.localDesc;
2471 mh = localDesc.canonicalCtr;
2472 MethodType mt = mh.type();
2473
2474 // change return type to Object
2475 // (T1, T2, ..., Tn):TR -> (T1, T2, ..., Tn):Object
2476 mh = mh.asType(mh.type().changeReturnType(Object.class));
2477
2478 // drop last 2 arguments representing primValues and objValues arrays
2479 // (T1, T2, ..., Tn):Object -> (T1, T2, ..., Tn, byte[], Object[]):Object
2480 mh = MethodHandles.dropArguments(mh, mh.type().parameterCount(), byte[].class, Object[].class);
2481
2482 Class<?>[] params = mt.parameterArray();
2483 for (int i = params.length-1; i >= 0; i--) {
2484 // Get the name from the local descriptor matching the argIndex
2485 var field = getFieldForArgIndex(localDesc, i);
2486 String name = (field == null) ? "" : field.getName(); // empty string to supply default
2487 Class<?> type = params[i];
2488 // obtain stream field extractor that extracts argument at
2489 // position i (Ti+1) from primValues and objValues arrays
2490 // (byte[], Object[]):Ti+1
2491 MethodHandle combiner = streamFieldExtractor(name, type, desc);
2492 // fold byte[] privValues and Object[] objValues into argument at position i (Ti+1)
2493 // (..., Ti, Ti+1, byte[], Object[]):Object -> (..., Ti, byte[], Object[]):Object
2494 mh = MethodHandles.foldArguments(mh, i, combiner);
2495 }
2496 // what we are left with is a MethodHandle taking just the primValues
2497 // and objValues arrays and returning the constructed instance
2498 // (byte[], Object[]):Object
2499
2500 // store it into cache and return the 1st value stored
2501 return desc.deserializationCtr =
2502 desc.deserializationCtrs.putIfAbsentAndGet(desc.getFields(false), mh);
2503 }
2504
2505 // Find the ObjectStreamField for the argument index, otherwise null
2506 private static ObjectStreamField getFieldForArgIndex(ObjectStreamClass desc, int argIndex) {
2507 for (var field : desc.fields) {
2508 if (field.getArgIndex() == argIndex)
2509 return field;
2510 }
2511 TRACE("field for ArgIndex is null: %s, index: %d, fields: %s",
2512 desc, argIndex, Arrays.toString(desc.fields));
2513 return null;
2514 }
2515
2516 /** Returns the number of primitive fields for the given descriptor. */
2517 private static int numberPrimValues(ObjectStreamClass desc) {
2518 ObjectStreamField[] fields = desc.getFields();
2519 int primValueCount = 0;
2520 for (int i = 0; i < fields.length; i++) {
2521 if (fields[i].isPrimitive())
2522 primValueCount++;
2523 else
2524 break; // can be no more
2525 }
2526 return primValueCount;
2527 }
2528
2529 /**
2530 * Returns extractor MethodHandle taking the primValues and objValues arrays
2531 * and extracting the argument of canonical constructor with given name and type
2532 * or producing default value for the given type if the field is absent.
2533 */
2534 private static MethodHandle streamFieldExtractor(String pName,
2535 Class<?> pType,
|