This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

FYI: Patch: serialization issues


Hi list,


I commited the attached patch from guilhem lavaux to fix more serialization 
issues.


Michael
Index: ChangeLog
===================================================================
RCS file: /cvs/gcc/gcc/libjava/ChangeLog,v
retrieving revision 1.2483
diff -u -b -B -r1.2483 ChangeLog
--- ChangeLog	23 Dec 2003 11:55:28 -0000	1.2483
+++ ChangeLog	23 Dec 2003 21:56:51 -0000
@@ -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-23  Michael Koch  <konqueror@gmx.de>
 
 	* javax/print/attribute/standard/Copies.java,
Index: java/io/ObjectInputStream.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/io/ObjectInputStream.java,v
retrieving revision 1.22
diff -u -b -B -r1.22 ObjectInputStream.java
--- java/io/ObjectInputStream.java	16 Dec 2003 13:45:01 -0000	1.22
+++ java/io/ObjectInputStream.java	23 Dec 2003 21:56:51 -0000
@@ -101,6 +101,7 @@
     this.nextOID = baseWireHandle;
     this.objectLookupTable = new Hashtable ();
     this.validators = new Vector ();
+    this.classLookupTable = new Hashtable();
     setBlockDataMode (true);
     readStreamHeader ();
   }
@@ -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,19 +460,41 @@
 	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 @@
     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));
@@ -555,6 +599,14 @@
     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 @@
    * 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 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 @@
       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 @@
       objs[i] = readObject ();
     setBlockDataMode (oldmode);
 
-    return new GetField ()
+    prereadFields = new GetField()
       {
 	public ObjectStreamClass getObjectStreamClass ()
 	{
@@ -925,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)
@@ -1067,24 +1250,76 @@
 	  throws IllegalArgumentException
 	{
 	  ObjectStreamField field = clazz.getField (name);
+	  boolean illegal = false;
 
-	  if (field == null)
-	    return null;
-
-	  Class field_type = field.getType ();
+	  try
+	    {
+	      try
+		{
+		  Class field_type = field.getType();
 
 	  if (type == field_type ||
-	      (type == null && ! field_type.isPrimitive ()))
+		      (type == null && !field_type.isPrimitive()))
+		    {
+		      /* See defaulted */
 	    return field;
+		    }
 
-	  throw new IllegalArgumentException ("Field requested is of type "
-					      + field_type.getName ()
+		  illegal = true;
+		  throw new IllegalArgumentException
+		    ("Field requested is of type "
+		     + field_type.getName()
 					      + ", but requested type was "
-					      + (type == null ?
-						 "Object" : type.getName ()));
+		     + (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 @@
 	      }
 	  }
 
+	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 @@
     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 @@
 	  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 @@
 	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 @@
   private boolean fieldsAlreadyRead;
   private Vector validators;
   private Hashtable classLookupTable;
+  private GetField prereadFields;
 
   private static boolean dump;
 
Index: java/io/ObjectOutputStream.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/io/ObjectOutputStream.java,v
retrieving revision 1.21
diff -u -b -B -r1.21 ObjectOutputStream.java
--- java/io/ObjectOutputStream.java	26 Sep 2003 19:59:56 -0000	1.21
+++ java/io/ObjectOutputStream.java	23 Dec 2003 21:56:51 -0000
@@ -407,7 +407,8 @@
     setBlockDataMode (oldmode);
     realOutput.writeByte (TC_ENDBLOCKDATA);
 
-    if (osc.isSerializable ())
+    if (osc.isSerializable()
+	|| osc.isExternalizable())
       writeObject (osc.getSuper ());
     else
       writeObject (null);
Index: java/io/ObjectStreamClass.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/io/ObjectStreamClass.java,v
retrieving revision 1.16
diff -u -b -B -r1.16 ObjectStreamClass.java
--- java/io/ObjectStreamClass.java	16 Dec 2003 13:45:01 -0000	1.16
+++ java/io/ObjectStreamClass.java	23 Dec 2003 21:56:51 -0000
@@ -516,11 +516,14 @@
 	  && Modifier.isPrivate (modifiers))
       {
 	fields = getSerialPersistentFields (cl);
-	Arrays.sort (fields);
-	calculateOffsets ();
+	if (fields != null)
+	  {
+	    Arrays.sort(fields);
+	    calculateOffsets();
 	return;
       }
     }
+    }
     catch (NoSuchFieldException ignore)
     {}
     catch (IllegalAccessException ignore)
@@ -700,16 +703,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[] 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 = {};

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]