2003-12-23 Guilhem Lavaux <guilhem@kaffe.org>
authorGuilhem Lavaux <guilhem@kaffe.org>
Tue, 23 Dec 2003 22:06:01 +0000 (22:06 +0000)
committerMichael Koch <mkoch@gcc.gnu.org>
Tue, 23 Dec 2003 22:06:01 +0000 (22:06 +0000)
* java/io/ObjectInputStream.java
(getField): Handle transient and non persistent fields.
(readClassDescriptor): Better error handling, use the right
class loader.
(readFields): Fields marked as not present in the stream
or not to be set are not read and set.
* java/io/ObjectInputStream.java
(readFields): Changed implementation of GetField.
(readClassDescriptor): Documented.
* java/io/ObjectOutputStream.java
(writeClassDescriptor): Added condition when to write class super
class information.

From-SVN: r74985

libjava/ChangeLog
libjava/java/io/ObjectInputStream.java
libjava/java/io/ObjectOutputStream.java
libjava/java/io/ObjectStreamClass.java

index 9b2294e..4c81138 100644 (file)
@@ -1,3 +1,18 @@
+2003-12-23  Guilhem Lavaux <guilhem@kaffe.org>
+
+       * java/io/ObjectInputStream.java
+       (getField): Handle transient and non persistent fields.
+       (readClassDescriptor): Better error handling, use the right
+       class loader.
+       (readFields): Fields marked as not present in the stream
+       or not to be set are not read and set.
+       * java/io/ObjectInputStream.java
+       (readFields): Changed implementation of GetField.
+       (readClassDescriptor): Documented.
+       * java/io/ObjectOutputStream.java
+       (writeClassDescriptor): Added condition when to write class super
+       class information.
+
 2003-12-22  Fernando Nasser  <fnasser@redhat.com>
 
        * gnu/java/awt/peer/gtk/GtkChoicePeer.java (postItemEvent): Rename to...
index 974b12d..de13939 100644 (file)
@@ -101,6 +101,7 @@ public class ObjectInputStream extends InputStream
     this.nextOID = baseWireHandle;
     this.objectLookupTable = new Hashtable ();
     this.validators = new Vector ();
+    this.classLookupTable = new Hashtable();
     setBlockDataMode (true);
     readStreamHeader ();
   }
@@ -417,6 +418,22 @@ public class ObjectInputStream extends InputStream
     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,19 +460,41 @@ public class ObjectInputStream extends InputStream
        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());
       }
              
+    /* 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);
@@ -487,10 +526,13 @@ public class ObjectInputStream extends InputStream
     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 @@ public class ObjectInputStream extends InputStream
     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));
@@ -555,6 +599,14 @@ public class ObjectInputStream extends InputStream
     return Class.forName(osc.getName(), true, 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 ();
@@ -590,8 +642,8 @@ public class ObjectInputStream extends InputStream
    * Reconstruct class hierarchy the same way
    * {@link 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.
    *
@@ -774,52 +826,142 @@ public class ObjectInputStream extends InputStream
 
   public boolean readBoolean () throws IOException
   {
-    return this.dataInputStream.readBoolean ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 1)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    boolean value = this.dataInputStream.readBoolean ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public byte readByte () throws IOException
   {
-    return this.dataInputStream.readByte ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 1)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    byte value = this.dataInputStream.readByte ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public int readUnsignedByte () throws IOException
   {
-    return this.dataInputStream.readUnsignedByte ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 1)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    int value = this.dataInputStream.readUnsignedByte ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public short readShort () throws IOException
   {
-    return this.dataInputStream.readShort ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 2)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    short value = this.dataInputStream.readShort ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public int readUnsignedShort () throws IOException
   {
-    return this.dataInputStream.readUnsignedShort ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 2)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    int value = this.dataInputStream.readUnsignedShort ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public char readChar () throws IOException
   {
-    return this.dataInputStream.readChar ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 2)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    char value = this.dataInputStream.readChar ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public int readInt () throws IOException
   {
-    return this.dataInputStream.readInt ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 4)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    int value = this.dataInputStream.readInt ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public long readLong () throws IOException
   {
-    return this.dataInputStream.readLong ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 8)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    long value = this.dataInputStream.readLong ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public float readFloat () throws IOException
   {
-    return this.dataInputStream.readFloat ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 4)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    float value = this.dataInputStream.readFloat ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public double readDouble () throws IOException
   {
-    return this.dataInputStream.readDouble ();
+    boolean switchmode = true;
+    boolean oldmode = this.readDataFromBlock;
+    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 8)
+      switchmode = false;
+    if (switchmode)
+      oldmode = setBlockDataMode (true);
+    double value = this.dataInputStream.readDouble ();
+    if (switchmode)
+      setBlockDataMode (oldmode);
+    return value;
   }
 
   public void readFully (byte data[]) throws IOException
@@ -893,14 +1035,31 @@ public class ObjectInputStream extends InputStream
       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 {@link #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];
@@ -915,7 +1074,7 @@ public class ObjectInputStream extends InputStream
       objs[i] = readObject ();
     setBlockDataMode (oldmode);
 
-    return new GetField ()
+    prereadFields = new GetField()
       {
        public ObjectStreamClass getObjectStreamClass ()
        {
@@ -925,7 +1084,31 @@ public class ObjectInputStream extends InputStream
        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)
@@ -1067,24 +1250,76 @@ public class ObjectInputStream extends InputStream
          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;
   }
 
   /**
@@ -1334,6 +1569,15 @@ public class ObjectInputStream extends InputStream
              }
          }
 
+       if (stream_field.getOffset() < 0)
+         {
+           default_initialize = true;
+           set_value = false;
+         }
+       
+       if (!stream_field.isToSet()) 
+         set_value = false;
+
        try
          {
            if (type == Boolean.TYPE)
@@ -1485,10 +1729,24 @@ public class ObjectInputStream extends InputStream
     return ClassLoader.getSystemClassLoader ();
   }
 
-  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()
       {
        public Object run()
@@ -1497,6 +1755,14 @@ public class ObjectInputStream extends InputStream
          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;
   }
 
@@ -1546,6 +1812,9 @@ public class ObjectInputStream extends InputStream
        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)
@@ -1829,6 +2098,7 @@ public class ObjectInputStream extends InputStream
   private boolean fieldsAlreadyRead;
   private Vector validators;
   private Hashtable classLookupTable;
+  private GetField prereadFields;
 
   private static boolean dump;
 
index f79ac3e..0244e2d 100644 (file)
@@ -407,7 +407,8 @@ public class ObjectOutputStream extends OutputStream
     setBlockDataMode (oldmode);
     realOutput.writeByte (TC_ENDBLOCKDATA);
 
-    if (osc.isSerializable ())
+    if (osc.isSerializable()
+       || osc.isExternalizable())
       writeObject (osc.getSuper ());
     else
       writeObject (null);
index 0be9a7e..9d9d99d 100644 (file)
@@ -516,9 +516,12 @@ public class ObjectStreamClass implements Serializable
          && Modifier.isPrivate (modifiers))
       {
        fields = getSerialPersistentFields (cl);
-       Arrays.sort (fields);
-       calculateOffsets ();
-       return;
+       if (fields != null)
+         {
+           Arrays.sort(fields);
+           calculateOffsets();
+           return;
+         }
       }
     }
     catch (NoSuchFieldException ignore)
@@ -700,16 +703,41 @@ public class ObjectStreamClass implements Serializable
     }
   }
 
-  // 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[] 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 = {};