< prev index next >

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

Print this page
@@ -26,12 +26,14 @@
  package java.io;
  
  import java.lang.invoke.MethodHandle;
  import java.lang.invoke.MethodHandles;
  import java.lang.invoke.MethodType;
+ import java.lang.reflect.AccessFlag;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Field;
+ import java.lang.reflect.InaccessibleObjectException;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.RecordComponent;
  import java.lang.reflect.UndeclaredThrowableException;
  import java.lang.reflect.Member;
  import java.lang.reflect.Method;

@@ -54,10 +56,11 @@
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  
+ import jdk.internal.MigratedValueClass;
  import jdk.internal.event.SerializationMisdeclarationEvent;
  import jdk.internal.misc.Unsafe;
  import jdk.internal.reflect.CallerSensitive;
  import jdk.internal.reflect.Reflection;
  import jdk.internal.reflect.ReflectionFactory;

@@ -71,11 +74,11 @@
   * serialVersionUID of the class.  The ObjectStreamClass for a specific class
   * loaded in this Java VM can be found/created using the lookup method.
   *
   * <p>The algorithm to compute the SerialVersionUID is described in
   * <a href="{@docRoot}/../specs/serialization/class.html#stream-unique-identifiers">
-  *    <cite>Java Object Serialization Specification,</cite> Section 4.6, "Stream Unique Identifiers"</a>.
+  *    <cite>Java Object Serialization Specification</cite>, Section 4.6, "Stream Unique Identifiers"</a>.
   *
   * @spec serialization/index.html Java Object Serialization Specification
   * @author      Mike Warres
   * @author      Roger Riggs
   * @see ObjectStreamField

@@ -135,10 +138,12 @@
      private boolean isProxy;
      /** true if represents enum type */
      private boolean isEnum;
      /** true if represents record type */
      private boolean isRecord;
+     /** true if represents a value class */
+     private boolean isValue;
      /** true if represented class implements Serializable */
      private boolean serializable;
      /** true if represented class implements Externalizable */
      private boolean externalizable;
      /** true if desc has data written by class-defined writeObject method */

@@ -374,10 +379,11 @@
          this.cl = cl;
          name = cl.getName();
          isProxy = Proxy.isProxyClass(cl);
          isEnum = Enum.class.isAssignableFrom(cl);
          isRecord = cl.isRecord();
+         isValue = cl.isValue();
          serializable = Serializable.class.isAssignableFrom(cl);
          externalizable = Externalizable.class.isAssignableFrom(cl);
  
          Class<?> superCl = cl.getSuperclass();
          superDesc = (superCl != null) ? lookup(superCl, false) : null;

@@ -407,10 +413,13 @@
                      }
  
                      if (isRecord) {
                          canonicalCtr = canonicalRecordCtr(cl);
                          deserializationCtrs = new DeserializationConstructorsCache();
+                     } else if (isValue) {
+                         // Value object instance creation is specialized in newInstance()
+                         cons = null;
                      } else if (externalizable) {
                          cons = getExternalizableConstructor(cl);
                      } else {
                          cons = getSerializableConstructor(cl);
                          writeObjectMethod = getPrivateMethod(cl, "writeObject",

@@ -444,11 +453,11 @@
          }
  
          if (deserializeEx == null) {
              if (isEnum) {
                  deserializeEx = new ExceptionInfo(name, "enum type");
-             } else if (cons == null && !isRecord) {
+             } else if (cons == null && !(isRecord | isValue)) {
                  deserializeEx = new ExceptionInfo(name, "no valid constructor");
              }
          }
          if (isRecord && canonicalCtr == null) {
              deserializeEx = new ExceptionInfo(name, "record canonical constructor not found");

@@ -642,10 +651,11 @@
          numObjFields = model.numObjFields;
  
          if (osc != null) {
              localDesc = osc;
              isRecord = localDesc.isRecord;
+             isValue = localDesc.isValue;
              // canonical record constructor is shared
              canonicalCtr = localDesc.canonicalCtr;
              // cache of deserialization constructors is shared
              deserializationCtrs = localDesc.deserializationCtrs;
              writeObjectMethod = localDesc.writeObjectMethod;

@@ -923,10 +933,18 @@
      boolean isSerializable() {
          requireInitialized();
          return serializable;
      }
  
+     /**
+      * {@return {code true} if the class is a value class, {@code false} otherwise}
+      */
+     boolean isValue() {
+         requireInitialized();
+         return isValue;
+     }
+ 
      /**
       * Returns true if class descriptor represents externalizable class that
       * has written its data in 1.2 (block data) format, false otherwise.
       */
      boolean hasBlockExternalData() {

@@ -945,17 +963,19 @@
      }
  
      /**
       * Returns true if represented class is serializable/externalizable and can
       * be instantiated by the serialization runtime--i.e., if it is
-      * externalizable and defines a public no-arg constructor, or if it is
+      * externalizable and defines a public no-arg constructor, if it is
       * non-externalizable and its first non-serializable superclass defines an
-      * accessible no-arg constructor.  Otherwise, returns false.
+      * accessible no-arg constructor, or if the class is a migrated value class.
+      * Otherwise, returns false.
       */
      boolean isInstantiable() {
          requireInitialized();
-         return (cons != null);
+         return (cons != null |
+                 (isValue && cl != null && cl.isAnnotationPresent(jdk.internal.MigratedValueClass.class)));
      }
  
      /**
       * Returns true if represented class is serializable (but not
       * externalizable) and defines a conformant writeObject method.  Otherwise,

@@ -1062,15 +1082,27 @@
              } catch (InstantiationError err) {
                  var ex = new InstantiationException();
                  ex.initCause(err);
                  throw ex;
              }
-         } else {
+         } else if (isValue) {
+             // Start with a buffered default value.
+             return FieldReflector.newValueInstance(cl);
+         }  else {
              throw new UnsupportedOperationException();
          }
      }
  
+     /**
+      * Finish the initialization of a value object.
+      * @param obj an object (larval if a value object)
+      * @return the finished object
+      */
+     Object finishValue(Object obj) {
+         return (isValue) ? FieldReflector.finishValueInstance(obj) : obj;
+     }
+ 
      /**
       * Invokes the writeObject method of the represented serializable class.
       * Throws UnsupportedOperationException if this class descriptor is not
       * associated with a class, or if the class is externalizable,
       * non-serializable or does not define writeObject.

@@ -1441,11 +1473,11 @@
          try {
              Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
              cons.setAccessible(true);
              return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
                  cons : null;
-         } catch (NoSuchMethodException ex) {
+         } catch (NoSuchMethodException | InaccessibleObjectException ex) {
              return null;
          }
      }
  
      /**

@@ -1934,10 +1966,37 @@
          /** field type codes */
          private final char[] typeCodes;
          /** field types */
          private final Class<?>[] types;
  
+         /**
+          * Return a new instance of the class using Unsafe.uninitializedDefaultValue
+          * and buffer it.
+          * @param clazz The value class
+          * @return a buffered default value
+          */
+         static Object newValueInstance(Class<?> clazz) throws InstantiationException{
+             var accessFlags = clazz.accessFlags();
+             if (accessFlags.contains(AccessFlag.ABSTRACT) ||
+                     accessFlags.contains(AccessFlag.IDENTITY)) {
+                 throw new InstantiationException("Value class not instantiable: " + clazz.getName());
+             }
+             // may not be implicitly constructible; so allocate with Unsafe
+             Object obj = UNSAFE.uninitializedDefaultValue(clazz);
+             return UNSAFE.makePrivateBuffer(obj);
+         }
+ 
+         /**
+          * Finish a value object, clear the larval state and returning the value object.
+          * @param obj a buffered value object in a larval state
+          * @return the finished value object
+          */
+         static Object finishValueInstance(Object obj) {
+             assert (obj.getClass().isValue()) : "Should be a value class";
+             return UNSAFE.finishPrivateBuffer(obj);
+         }
+ 
          /**
           * Constructs FieldReflector capable of setting/getting values from the
           * subset of fields whose ObjectStreamFields contain non-null
           * reflective Field objects.  ObjectStreamFields with null Fields are
           * treated as filler, for which get operations return default values

@@ -2054,12 +2113,16 @@
              /* assuming checkDefaultSerialize() has been called on the class
               * descriptor this FieldReflector was obtained from, no field keys
               * in array should be equal to Unsafe.INVALID_FIELD_OFFSET.
               */
              for (int i = numPrimFields; i < fields.length; i++) {
+                 Field f = fields[i].getField();
                  vals[offsets[i]] = switch (typeCodes[i]) {
-                     case 'L', '[' -> UNSAFE.getReference(obj, readKeys[i]);
+                     case 'L', '[' ->
+                             UNSAFE.isFlatField(f)
+                                     ? UNSAFE.getValue(obj, readKeys[i], f.getType())
+                                     : UNSAFE.getReference(obj, readKeys[i]);
                      default       -> throw new InternalError();
                  };
              }
          }
  

@@ -2092,25 +2155,30 @@
                  if (key == Unsafe.INVALID_FIELD_OFFSET) {
                      continue;           // discard value
                  }
                  switch (typeCodes[i]) {
                      case 'L', '[' -> {
+                         Field f = fields[i].getField();
                          Object val = vals[offsets[i]];
                          if (val != null &&
                              !types[i - numPrimFields].isInstance(val))
                          {
-                             Field f = fields[i].getField();
                              throw new ClassCastException(
                                  "cannot assign instance of " +
                                  val.getClass().getName() + " to field " +
                                  f.getDeclaringClass().getName() + "." +
                                  f.getName() + " of type " +
                                  f.getType().getName() + " in instance of " +
                                  obj.getClass().getName());
                          }
-                         if (!dryRun)
-                             UNSAFE.putReference(obj, key, val);
+                         if (!dryRun) {
+                             if (UNSAFE.isFlatField(f)) {
+                                 UNSAFE.putValue(obj, key, f.getType(), val);
+                             } else {
+                                 UNSAFE.putReference(obj, key, val);
+                             }
+                         }
                      }
                      default -> throw new InternalError();
                  }
              }
          }
< prev index next >