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.

From-SVN: r74985
This commit is contained in:
Guilhem Lavaux 2003-12-23 22:06:01 +00:00 committed by Michael Koch
parent f2073745a8
commit 692fb023ef
4 changed files with 358 additions and 44 deletions

View 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> 2003-12-22 Fernando Nasser <fnasser@redhat.com>
* gnu/java/awt/peer/gtk/GtkChoicePeer.java (postItemEvent): Rename to... * gnu/java/awt/peer/gtk/GtkChoicePeer.java (postItemEvent): Rename to...

View File

@ -101,6 +101,7 @@ public class ObjectInputStream extends InputStream
this.nextOID = baseWireHandle; this.nextOID = baseWireHandle;
this.objectLookupTable = new Hashtable (); this.objectLookupTable = new Hashtable ();
this.validators = new Vector (); this.validators = new Vector ();
this.classLookupTable = new Hashtable();
setBlockDataMode (true); setBlockDataMode (true);
readStreamHeader (); readStreamHeader ();
} }
@ -417,6 +418,22 @@ public class ObjectInputStream extends InputStream
return ret_val; 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 () protected ObjectStreamClass readClassDescriptor ()
throws ClassNotFoundException, IOException throws ClassNotFoundException, IOException
{ {
@ -443,19 +460,41 @@ public class ObjectInputStream extends InputStream
dumpElementln (field_name); dumpElementln (field_name);
String class_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 == '[') if (type_code == 'L' || type_code == '[')
class_name = (String)readObject (); class_name = (String)readObject ();
else else
class_name = String.valueOf (type_code); 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] = 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); 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); boolean oldmode = setBlockDataMode (true);
osc.setClass (clazz, lookupClass(clazz.getSuperclass())); osc.setClass (clazz, lookupClass(clazz.getSuperclass()));
classLookupTable.put(clazz, osc); classLookupTable.put(clazz, osc);
@ -487,10 +526,13 @@ public class ObjectInputStream extends InputStream
throws ClassNotFoundException, IOException, NotActiveException throws ClassNotFoundException, IOException, NotActiveException
{ {
if (this.currentObject == null || this.currentObjectStreamClass == null) 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) 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); boolean oldmode = setBlockDataMode(false);
readFields (this.currentObject, this.currentObjectStreamClass); readFields (this.currentObject, this.currentObjectStreamClass);
@ -523,10 +565,12 @@ public class ObjectInputStream extends InputStream
throws InvalidObjectException, NotActiveException throws InvalidObjectException, NotActiveException
{ {
if (this.currentObject == null || this.currentObjectStreamClass == null) 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) 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, this.validators.addElement (new ValidatorAndPriority (validator,
priority)); priority));
@ -555,6 +599,14 @@ public class ObjectInputStream extends InputStream
return Class.forName(osc.getName(), true, currentLoader()); 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() private ClassLoader currentLoader()
{ {
SecurityManager sm = System.getSecurityManager (); SecurityManager sm = System.getSecurityManager ();
@ -590,8 +642,8 @@ public class ObjectInputStream extends InputStream
* Reconstruct class hierarchy the same way * Reconstruct class hierarchy the same way
* {@link java.io.ObjectStreamClass.getObjectStreamClasses(java.lang.Class)} does * {@link java.io.ObjectStreamClass.getObjectStreamClasses(java.lang.Class)} does
* but using lookupClass instead of ObjectStreamClass.lookup. This * but using lookupClass instead of ObjectStreamClass.lookup. This
* dup is necessary localize the lookup table. Hopefully some future rewritings will * dup is necessary localize the lookup table. Hopefully some future
* be able to prevent this. * rewritings will be able to prevent this.
* *
* @param clazz This is the class for which we want the hierarchy. * @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 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 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 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 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 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 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 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 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 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 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 public void readFully (byte data[]) throws IOException
@ -893,14 +1035,31 @@ public class ObjectInputStream extends InputStream
throws IOException, IllegalArgumentException; 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 () public GetField readFields ()
throws IOException, ClassNotFoundException, NotActiveException throws IOException, ClassNotFoundException, NotActiveException
{ {
if (this.currentObject == null || this.currentObjectStreamClass == null) if (this.currentObject == null || this.currentObjectStreamClass == null)
throw new NotActiveException ("readFields called by non-active class and/or object"); throw new NotActiveException ("readFields called by non-active class and/or object");
if (prereadFields != null)
return prereadFields;
if (fieldsAlreadyRead) 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 ObjectStreamClass clazz = this.currentObjectStreamClass;
final byte[] prim_field_data = new byte[clazz.primFieldSize]; final byte[] prim_field_data = new byte[clazz.primFieldSize];
@ -915,7 +1074,7 @@ public class ObjectInputStream extends InputStream
objs[i] = readObject (); objs[i] = readObject ();
setBlockDataMode (oldmode); setBlockDataMode (oldmode);
return new GetField () prereadFields = new GetField()
{ {
public ObjectStreamClass getObjectStreamClass () public ObjectStreamClass getObjectStreamClass ()
{ {
@ -925,7 +1084,31 @@ public class ObjectInputStream extends InputStream
public boolean defaulted (String name) public boolean defaulted (String name)
throws IOException, IllegalArgumentException 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) public boolean get (String name, boolean defvalue)
@ -1067,24 +1250,76 @@ public class ObjectInputStream extends InputStream
throws IllegalArgumentException throws IllegalArgumentException
{ {
ObjectStreamField field = clazz.getField (name); ObjectStreamField field = clazz.getField (name);
boolean illegal = false;
if (field == null) try
return null; {
try
{
Class field_type = field.getType(); Class field_type = field.getType();
if (type == field_type || if (type == field_type ||
(type == null && !field_type.isPrimitive())) (type == null && !field_type.isPrimitive()))
{
/* See defaulted */
return field; return field;
}
throw new IllegalArgumentException ("Field requested is of type " illegal = true;
throw new IllegalArgumentException
("Field requested is of type "
+ field_type.getName() + field_type.getName()
+ ", but requested type was " + ", but requested type was "
+ (type == null ? + (type == null ? "Object" : type.getName()));
"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 try
{ {
if (type == Boolean.TYPE) if (type == Boolean.TYPE)
@ -1485,10 +1729,24 @@ public class ObjectInputStream extends InputStream
return ClassLoader.getSystemClassLoader (); 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 throws java.lang.NoSuchFieldException
{ {
final Field f = klass.getDeclaredField(name); final Field f = klass.getDeclaredField(name);
ObjectStreamField sf = lookupClass(klass).getField(name);
AccessController.doPrivileged(new PrivilegedAction() AccessController.doPrivileged(new PrivilegedAction()
{ {
public Object run() public Object run()
@ -1497,6 +1755,14 @@ public class ObjectInputStream extends InputStream
return null; 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; return f;
} }
@ -1546,6 +1812,9 @@ public class ObjectInputStream extends InputStream
throw new IOException ("Failure invoking readObject() on " + throw new IOException ("Failure invoking readObject() on " +
klass + ": " + x.getClass().getName()); klass + ": " + x.getClass().getName());
} }
// Invalidate fields which has been read through readFields.
prereadFields = null;
} }
private native Object allocateObject (Class clazz) private native Object allocateObject (Class clazz)
@ -1829,6 +2098,7 @@ public class ObjectInputStream extends InputStream
private boolean fieldsAlreadyRead; private boolean fieldsAlreadyRead;
private Vector validators; private Vector validators;
private Hashtable classLookupTable; private Hashtable classLookupTable;
private GetField prereadFields;
private static boolean dump; private static boolean dump;

View File

@ -407,7 +407,8 @@ public class ObjectOutputStream extends OutputStream
setBlockDataMode (oldmode); setBlockDataMode (oldmode);
realOutput.writeByte (TC_ENDBLOCKDATA); realOutput.writeByte (TC_ENDBLOCKDATA);
if (osc.isSerializable ()) if (osc.isSerializable()
|| osc.isExternalizable())
writeObject (osc.getSuper ()); writeObject (osc.getSuper ());
else else
writeObject (null); writeObject (null);

View File

@ -516,11 +516,14 @@ public class ObjectStreamClass implements Serializable
&& Modifier.isPrivate (modifiers)) && Modifier.isPrivate (modifiers))
{ {
fields = getSerialPersistentFields (cl); fields = getSerialPersistentFields (cl);
if (fields != null)
{
Arrays.sort(fields); Arrays.sort(fields);
calculateOffsets(); calculateOffsets();
return; return;
} }
} }
}
catch (NoSuchFieldException ignore) catch (NoSuchFieldException ignore)
{} {}
catch (IllegalAccessException ignore) catch (IllegalAccessException 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) private ObjectStreamField[] getSerialPersistentFields (Class clazz)
throws NoSuchFieldException, IllegalAccessException throws NoSuchFieldException, IllegalAccessException
{ {
ObjectStreamField[] fieldsArray = null;
ObjectStreamField[] o;
// Use getDeclaredField rather than getField for the same reason // Use getDeclaredField rather than getField for the same reason
// as above in getDefinedSUID. // as above in getDefinedSUID.
Field f = clazz.getDeclaredField("serialPersistentFields"); Field f = clazz.getDeclaredField("serialPersistentFields");
f.setAccessible(true); 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 = {}; public static final ObjectStreamField[] NO_FIELDS = {};