diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 9b2294e4299..4c81138ee3c 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,18 @@ +2003-12-23 Guilhem Lavaux + + * 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 * gnu/java/awt/peer/gtk/GtkChoicePeer.java (postItemEvent): Rename to... diff --git a/libjava/java/io/ObjectInputStream.java b/libjava/java/io/ObjectInputStream.java index 974b12df0c2..de139393335 100644 --- a/libjava/java/io/ObjectInputStream.java +++ b/libjava/java/io/ObjectInputStream.java @@ -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; + 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; - 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 ())); + /* 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; diff --git a/libjava/java/io/ObjectOutputStream.java b/libjava/java/io/ObjectOutputStream.java index f79ac3e4d8a..0244e2d2c91 100644 --- a/libjava/java/io/ObjectOutputStream.java +++ b/libjava/java/io/ObjectOutputStream.java @@ -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); diff --git a/libjava/java/io/ObjectStreamClass.java b/libjava/java/io/ObjectStreamClass.java index 0be9a7e5053..9d9d99d33d1 100644 --- a/libjava/java/io/ObjectStreamClass.java +++ b/libjava/java/io/ObjectStreamClass.java @@ -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 = {};