commit-classpath
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

FYI: Serialization


From: Guilhem Lavaux
Subject: FYI: Serialization
Date: Sun, 28 Dec 2003 18:16:18 +0100
User-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.5) Gecko/20031007

Hi,

Here is a patch which merges kaffe serialization code into classpath. The next one will reindent the code for clarity.

Guilhem.
Index: ChangeLog
===================================================================
RCS file: /cvsroot/classpath/classpath/ChangeLog,v
retrieving revision 1.1731
diff -u -r1.1731 ChangeLog
--- ChangeLog   28 Dec 2003 13:45:15 -0000      1.1731
+++ ChangeLog   28 Dec 2003 17:13:48 -0000
@@ -1,5 +1,27 @@
 2003-12-28  Guilhem Lavaux <address@hidden>
 
+       * java/io/ObjectOutputStream.java
+       (writeClassDescriptor): Externalizable class must also write their
+       hierarchy.
+       (putFields): Added some text message for an exception.
+
+       * java/io/ObjectInputStream.java
+       (readClassDescriptor): Documented. Better field checking.
+       (defaultReadObject): Readjusted messages in exceptions.
+       (currentLoader): Documented.
+       (inputGetObjectStreamClasses): Changed indentation.
+       (readFields): Documented. Changed logic to improve error reporting and
+       behaviour compared to the JDK.
+       (readFields): Fixed behaviour.
+       (getField): Documented. Fixed behaviour.
+       (callReadMethod): Invalidate read fields.
+
+       * java/io/ObjectStreamClass.java
+       (setFields): Fix to avoid a NullPointerException.
+       (getSerialPersistentFields): Documented. Improved modifier checking.
+
+2003-12-28  Guilhem Lavaux <address@hidden>
+
        * java/io/LineNumberReader.java
        (countLines): Removed.
        (fill): New private method.
Index: java/io/ObjectInputStream.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/ObjectInputStream.java,v
retrieving revision 1.33
diff -u -r1.33 ObjectInputStream.java
--- java/io/ObjectInputStream.java      2 Dec 2003 18:35:51 -0000       1.33
+++ java/io/ObjectInputStream.java      28 Dec 2003 17:13:49 -0000
@@ -47,6 +47,7 @@
 import java.util.Hashtable;
 import java.util.Vector;
 
+
 import gnu.java.io.ObjectIdentityWrapper;
 import gnu.java.lang.reflect.TypeSignature;
 import java.lang.reflect.Field;
@@ -417,6 +418,22 @@
     return ret_val;
   }
 
+  /**
+   * This method reads a class descriptor from the real input stream
+   * and use these data to create a new instance of ObjectStreamClass.
+   * Fields are sorted and ordered for the real read which occurs for
+   * each instance of the described class. Be aware that if you call that
+   * method you must ensure that the stream is synchronized, in the other
+   * case it may be completely desynchronized.
+   *
+   * @return A new instance of ObjectStreamClass containing the freshly
+   * created descriptor.
+   * @throws ClassNotFoundException if the required class to build the
+   * descriptor has not been found in the system.
+   * @throws IOException An input/output error occured.
+   * @throws InvalidClassException If there was a compatibility problem
+   * between the class present in the system and the serialized class.
+   */
   protected ObjectStreamClass readClassDescriptor ()
     throws ClassNotFoundException, IOException
   {
@@ -443,22 +460,44 @@
        dumpElementln (field_name);
        String class_name;
                  
+       // If the type code is an array or an object we must
+       // decode a String here. In the other case we convert
+       // the type code and pass it to ObjectStreamField.
+       // Type codes are decoded by gnu.java.lang.reflect.TypeSignature.
        if (type_code == 'L' || type_code == '[')
          class_name = (String)readObject ();
        else
          class_name = String.valueOf (type_code);
                  
-       // There're many cases you can't get java.lang.Class from
-       // typename if your context class loader can't load it,
-       // then use typename to construct the field
        fields[i] =
-         new ObjectStreamField (field_name, class_name);
+         new ObjectStreamField(field_name, class_name, currentLoader());
       }
              
-    Class clazz = resolveClass (osc);
+    /* Now that fields have been read we may resolve the class
+     * (and read annotation if needed). */
+    Class clazz = resolveClass(osc);
+    
+    for (int i = 0; i < field_count; i++)
+      {
+       Field f;
+       
+       try
+         {
+           f = clazz.getDeclaredField(fields[i].getName());
+           if (f != null && !f.getType().equals(fields[i].getType()))
+             throw new InvalidClassException
+               ("invalid field type for " + fields[i].getName() + " in class "
+                + name + " (requested was \"" + fields[i].getType()
+                + " and found \"" + f.getType() + "\")"); 
+         }
+       catch (NoSuchFieldException _)
+         {
+         }
+      }
+
     boolean oldmode = setBlockDataMode (true);
-    osc.setClass (clazz, lookupClass (clazz.getSuperclass()));
-    classLookupTable.put (clazz, osc);
+    osc.setClass (clazz, lookupClass(clazz.getSuperclass()));
+    classLookupTable.put(clazz, osc);
     setBlockDataMode (oldmode);
              
     return osc;
@@ -487,10 +526,13 @@
     throws ClassNotFoundException, IOException, NotActiveException
   {
     if (this.currentObject == null || this.currentObjectStreamClass == null)
-      throw new NotActiveException ("defaultReadObject called by non-active 
class and/or object");
+      throw new NotActiveException("defaultReadObject called by non-active"
+                                  + " class and/or object");
 
     if (fieldsAlreadyRead)
-      throw new NotActiveException ("defaultReadObject called but fields 
already read from stream (by defaultReadObject or readFields)");
+      throw new NotActiveException("defaultReadObject called but fields "
+                                  + "already read from stream (by "
+                                  + "defaultReadObject or readFields)");
 
     boolean oldmode = setBlockDataMode(false);
     readFields (this.currentObject, this.currentObjectStreamClass);
@@ -523,10 +565,12 @@
     throws InvalidObjectException, NotActiveException
   {
     if (this.currentObject == null || this.currentObjectStreamClass == null)
-      throw new NotActiveException ("registerValidation called by non-active 
class and/or object");
+      throw new NotActiveException ("registerValidation called by non-active "
+                                   +"class and/or object");
 
     if (validator == null)
-      throw new InvalidObjectException ("attempt to add a null 
ObjectInputValidation object");
+      throw new InvalidObjectException ("attempt to add a null "
+                                       +"ObjectInputValidation object");
 
     this.validators.addElement (new ValidatorAndPriority (validator,
                                                          priority));
@@ -552,16 +596,24 @@
   protected Class resolveClass (ObjectStreamClass osc)
     throws ClassNotFoundException, IOException
   {
-    return Class.forName (osc.getName(), true, currentLoader()); 
+    return Class.forName(osc.getName(), true, currentLoader());
   }
-  
-  private ClassLoader currentLoader ()
+
+  /**
+   * This method invokes the method currentClassLoader for the
+   * current security manager (or build an empty one if it is not
+   * present).
+   *
+   * @return The most recent non-system ClassLoader on the execution stack.
+   * @see java.lang.SecurityManager#currentClassLoader()
+   */
+  private ClassLoader currentLoader()
   {
     SecurityManager sm = System.getSecurityManager ();
     if (sm == null)
       sm = new SecurityManager () {};
- 
-    return currentClassLoader (sm);
+    
+    return currentClassLoader(sm);
   }
 
   /**
@@ -571,14 +623,14 @@
    * the behaviour detected in the JDK by Kaffe's team.
    *
    * @param clazz Class to lookup in the hash table or for which
-   * we must build a descriptor. 
+   * we must build a descriptor.
    * @return A valid instance of ObjectStreamClass corresponding
    * to the specified class.
    */
   private ObjectStreamClass lookupClass (Class clazz)
   {
     ObjectStreamClass oclazz;
-    
+
     oclazz = (ObjectStreamClass) classLookupTable.get(clazz);
     if (oclazz == null)
       return ObjectStreamClass.lookup (clazz);
@@ -590,8 +642,8 @@
    * Reconstruct class hierarchy the same way
    * address@hidden 
java.io.ObjectStreamClass.getObjectStreamClasses(java.lang.Class)} does
    * but using lookupClass instead of ObjectStreamClass.lookup. This
-   * dup is necessary localize the lookup table. Hopefully some future 
rewritings will
-   * be able to prevent this.
+   * dup is necessary localize the lookup table. Hopefully some future
+   * rewritings will be able to prevent this.
    *
    * @param clazz This is the class for which we want the hierarchy.
    *
@@ -607,23 +659,23 @@
     if (osc == null)
       return new ObjectStreamClass[0];
     else
-    {
-      Vector oscs = new Vector ();
-
-      while (osc != null)
       {
-       oscs.addElement (osc);
-       osc = osc.getSuper ();
-      }
+        Vector oscs = new Vector();
 
-      int count = oscs.size ();
-      ObjectStreamClass[] sorted_oscs = new ObjectStreamClass[ count ];
+        while (osc != null)
+          {
+            oscs.addElement(osc);
+            osc = osc.getSuper();
+         }
 
-      for (int i = count - 1; i >= 0; i--)
-       sorted_oscs[ count - i - 1 ] = (ObjectStreamClass)oscs.elementAt (i);
+        int count = oscs.size();
+       ObjectStreamClass[] sorted_oscs = new ObjectStreamClass[count];
 
-      return sorted_oscs;
-    }
+        for (int i = count - 1; i >= 0; i--)
+          sorted_oscs[count - i - 1] = (ObjectStreamClass) oscs.elementAt(i);
+
+        return sorted_oscs;
+      }
   }
 
   /**
@@ -983,14 +1035,31 @@
       throws IOException, IllegalArgumentException;
   }
 
+  /**
+   * This method should be called by a method called 'readObject' in the
+   * deserializing class (if present). It cannot (and should not)be called
+   * outside of it. Its goal is to read all fields in the real input stream
+   * and keep them accessible through the address@hidden #GetField} class. 
Calling
+   * this method will not alterate the deserializing object.
+   *
+   * @return A valid freshly created 'GetField' instance to get access to
+   * the deserialized stream.
+   * @throws IOException An input/output exception occured. 
+   * @throws ClassNotFoundException 
+   * @throws NotActiveException
+   */
   public GetField readFields ()
     throws IOException, ClassNotFoundException, NotActiveException
   {
     if (this.currentObject == null || this.currentObjectStreamClass == null)
       throw new NotActiveException ("readFields called by non-active class 
and/or object");
 
+    if (prereadFields != null)
+      return prereadFields;
+
     if (fieldsAlreadyRead)
-      throw new NotActiveException ("readFields called but fields already read 
from stream (by defaultReadObject or readFields)");
+      throw new NotActiveException ("readFields called but fields already read 
from"
+                                   + " stream (by defaultReadObject or 
readFields)");
 
     final ObjectStreamClass clazz = this.currentObjectStreamClass;
     final byte[] prim_field_data = new byte[clazz.primFieldSize];
@@ -1005,7 +1074,7 @@
       objs[i] = readObject ();
     setBlockDataMode (oldmode);
 
-    return new GetField ()
+    prereadFields = new GetField()
       {
        public ObjectStreamClass getObjectStreamClass ()
        {
@@ -1015,7 +1084,31 @@
        public boolean defaulted (String name)
          throws IOException, IllegalArgumentException
        {
-         return clazz.getField (name) == null;
+         ObjectStreamField f = clazz.getField(name);
+         
+         /* First if we have a serialized field use the descriptor */
+         if (f != null)
+           {
+             /* It is in serialPersistentFields but setClass tells us
+              * it should not be set. This value is defaulted.
+              */
+             if (f.isPersistent() && !f.isToSet())
+               return true;
+             
+             return false;
+           }
+
+         /* This is not a serialized field. There should be
+          * a default value only if the field really exists.
+          */
+         try
+           {
+             return (clazz.forClass().getDeclaredField (name) != null);
+           }
+         catch (NoSuchFieldException e)
+           {
+             throw new IllegalArgumentException (e.getMessage());
+           }
        }
 
        public boolean get (String name, boolean defvalue)
@@ -1157,24 +1250,76 @@
          throws IllegalArgumentException
        {
          ObjectStreamField field = clazz.getField (name);
+         boolean illegal = false;
 
-         if (field == null)
-           return null;
-
-         Class field_type = field.getType ();
-
-         if (type == field_type ||
-             (type == null && ! field_type.isPrimitive ()))
-           return field;
-
-         throw new IllegalArgumentException ("Field requested is of type "
-                                             + field_type.getName ()
-                                             + ", but requested type was "
-                                             + (type == null ?
-                                                "Object" : type.getName ()));
+         try
+           {
+             try
+               {
+                 Class field_type = field.getType();
+                 
+                 if (type == field_type ||
+                     (type == null && !field_type.isPrimitive()))
+                   {
+                     /* See defaulted */
+                     return field;
+                   }
+        
+                 illegal = true;
+                 throw new IllegalArgumentException
+                   ("Field requested is of type "
+                    + field_type.getName()
+                    + ", but requested type was "
+                    + (type == null ?  "Object" : type.getName()));
+               }
+             catch (NullPointerException _)
+               {
+                 /* Here we catch NullPointerException, because it may
+                    only come from the call 'field.getType()'. If field
+                    is null, we have to return null and classpath ethic
+                    say we must try to avoid 'if (xxx == null)'.
+                 */
+               }
+             catch (IllegalArgumentException e)
+               {
+                 throw e;
+               }
+             
+             return null;
+           }
+         finally
+           {
+             /* If this is an unassigned field we should return
+              * the default value.
+              */
+             if (!illegal && field != null && !field.isToSet() && 
field.isPersistent())
+               return null;
+
+             /* We do not want to modify transient fields. They should
+              * be left to 0.
+              */
+             try
+               {
+                 Field f = clazz.forClass().getDeclaredField (name);
+                 if (Modifier.isTransient(f.getModifiers()))
+                   throw new IllegalArgumentException
+                     ("no such field (non transient) " + name);
+                 if (field == null && f.getType() != type)
+                   throw new IllegalArgumentException
+                     ("Invalid requested type for field " + name);
+               }
+             catch (NoSuchFieldException e)
+               {
+                 if (field == null)
+                   throw new IllegalArgumentException(e.getMessage());
+               }
+              
+           }
        }
       };
 
+    fieldsAlreadyRead = true;
+    return prereadFields;
   }
 
   /**
@@ -1369,7 +1514,7 @@
   {
     ObjectStreamField[] stream_fields = stream_osc.fields;
     ObjectStreamField[] real_fields =
-      lookupClass (stream_osc.forClass ()).fields;
+      lookupClass(stream_osc.forClass()).fields;
 
     boolean default_initialize, set_value;
     String field_name = null;
@@ -1424,6 +1569,15 @@
              }
          }
 
+       if (stream_field.getOffset() < 0)
+         {
+           default_initialize = true;
+           set_value = false;
+         }
+       
+       if (!stream_field.isToSet()) 
+         set_value = false;
+
        try
          {
            if (type == Boolean.TYPE)
@@ -1571,10 +1725,23 @@
    */
   private static native ClassLoader currentClassLoader (SecurityManager sm);
 
-  private static Field getField (Class klass, String name)
+  /**
+   * This method tries to access a precise field called in the
+   * specified class. Before accessing the field, it tries to
+   * gain control on this field. If the field is either declared as 
+   * not persistent or transient then it returns null
+   * immediately.
+   *
+   * @param klass Class to get the field from.
+   * @param name Name of the field to access.
+   * @return Field instance representing the requested field.
+   * @throws NoSuchFieldException if the field does not exist.
+   */
+  private Field getField(Class klass, String name)
     throws java.lang.NoSuchFieldException
   {
     final Field f = klass.getDeclaredField(name);
+    ObjectStreamField sf = lookupClass(klass).getField(name);
     
     AccessController.doPrivileged(new PrivilegedAction()
       {
@@ -1584,6 +1751,14 @@
          return null;
        }
       });
+
+    /* We do not want to modify transient fields. They should
+     * be left to 0.
+     * N.B.: Not valid if the field is in serialPersistentFields. 
+     */
+    if (Modifier.isTransient(f.getModifiers()) && !sf.isPersistent())
+      return null;
+   
     return f;
   }
 
@@ -1633,6 +1808,9 @@
        throw new IOException ("Failure invoking readObject() on " +
                               klass + ": " + x.getClass().getName());
       }
+
+    // Invalidate fields which has been read through readFields.
+    prereadFields = null;
   }
     
   private native Object allocateObject (Class clazz)
@@ -1916,6 +2094,7 @@
   private boolean fieldsAlreadyRead;
   private Vector validators;
   private Hashtable classLookupTable;
+  private GetField prereadFields;
 
   private static boolean dump;
 
Index: java/io/ObjectOutputStream.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/ObjectOutputStream.java,v
retrieving revision 1.40
diff -u -r1.40 ObjectOutputStream.java
--- java/io/ObjectOutputStream.java     2 Dec 2003 18:35:52 -0000       1.40
+++ java/io/ObjectOutputStream.java     28 Dec 2003 17:13:50 -0000
@@ -410,8 +410,8 @@
     annotateClass (osc.forClass ());
     setBlockDataMode (oldmode);
     realOutput.writeByte (TC_ENDBLOCKDATA);
-    
-    if (osc.isSerializable ())
+
+    if (osc.isSerializable () || osc.isExternalizable())
       writeObject (osc.getSuper ());
     else
       writeObject (null);
@@ -989,8 +989,9 @@
          ObjectStreamField field = getField (name);
 
          if (value != null &&
-             ! field.getType ().isAssignableFrom (value.getClass ()))
-           throw new IllegalArgumentException ();
+             ! field.getType ().isAssignableFrom (value.getClass ()))      
+           throw new IllegalArgumentException ("Class " + value.getClass() +
+                                               " cannot be cast to " + 
field.getType());
          objs[field.getOffset ()] = value;
        }
 
Index: java/io/ObjectStreamClass.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/ObjectStreamClass.java,v
retrieving revision 1.25
diff -u -r1.25 ObjectStreamClass.java
--- java/io/ObjectStreamClass.java      2 Dec 2003 18:35:52 -0000       1.25
+++ java/io/ObjectStreamClass.java      28 Dec 2003 17:13:50 -0000
@@ -518,9 +518,12 @@
          && Modifier.isPrivate (modifiers))
       {
        fields = getSerialPersistentFields (cl);
-       Arrays.sort (fields);
-       calculateOffsets ();
-       return;
+       if (fields != null)
+         {
+           Arrays.sort (fields);
+           calculateOffsets ();
+           return;
+         }
       }
     }
     catch (NoSuchFieldException ignore)
@@ -701,18 +704,41 @@
     }
   }
 
-  // Returns the value of CLAZZ's private static final field named
-  // `serialPersistentFields'.
+  /**
+   *  Returns the value of CLAZZ's private static final field named
+   * `serialPersistentFields'. It performs some sanity checks before
+   * returning the real array. Besides, the returned array is a clean
+   * copy of the original. So it can be modified.
+   *
+   * @param clazz Class to retrieve 'serialPersistentFields' from.
+   * @return The content of 'serialPersistentFields'.
+   */
   private ObjectStreamField[] getSerialPersistentFields (Class clazz) 
     throws NoSuchFieldException, IllegalAccessException
   {
-    ObjectStreamField[] o = null;
+    ObjectStreamField[] fieldsArray = null;
+    ObjectStreamField[] o;
 
     // Use getDeclaredField rather than getField for the same reason
     // as above in getDefinedSUID.
     Field f = clazz.getDeclaredField ("serialPersistentFields");
     f.setAccessible(true);
-    return (ObjectStreamField[])f.get (null);
+
+    int modifiers = f.getModifiers();
+    if (!(Modifier.isStatic (modifiers) &&
+       Modifier.isFinal (modifiers) &&
+       Modifier.isPrivate (modifiers)))
+      return null;
+    
+    o = (ObjectStreamField[])f.get (null);
+    
+    if (o == null)
+      return null;
+
+    fieldsArray = new ObjectStreamField[ o.length ];
+    System.arraycopy(o, 0, fieldsArray, 0, o.length);
+    
+    return fieldsArray;
   }
 
   public static final ObjectStreamField[] NO_FIELDS = {};

reply via email to

[Prev in Thread] Current Thread [Next in Thread]