Two bugs when serializing proxies. Firstly, we were assigning the handles for the proxyClassDescInfo and its associated classAnnotation in the wrong order. The grammar for newClassDesc makes it clear that the handle is allocated before the classAnnotation is read.
The much harder problem has to do with reading a proxy class from a stream and then writing it to another stream. When reading a proxy, we generate an ObjectStreamClass by calling lookupClass(), and then we read the ObjectStreamClass for the proxy's superclass (which is always Proxy) from the stream. We then set the proxy's ObjectStreamClass's superclass. This has the effect that the ObjectStreamClass for the proxy class is cached in the global cache. Later, someone comes to write an instance of the same proxy class. They find the ObjectStreamClass for that proxy in the global cache, but the ObjectStreamClass for the proxy's superclass has only been created by reading it from the input stream, so it has never has its fields set. Writing the Proxy fails because of a NullPointerException. Fixing this properly seems to be incredibly hard. I've spent the last couple of days trying all the obvious fixes, and they all cause breakages somewhere else. So, I've written a workaround: if we are writing an object's fields, first make sure we've called setFields on the ObjectStreamClass. This is safe, because it only has any effect in a case where we would otherwise have crashed. If anyone wants to fix the real bug, please do, but IMO the only cure for our serialization code is to replace it altogether. Andrew. 2007-04-25 Andrew Haley <[EMAIL PROTECTED]> * java/io/ObjectStreamClass.java (ensureFieldsSet): New method. (setFields): call ensureFieldsSet. (fieldsSet): New field. * java/io/ObjectOutputStream.java (writeFields): Call osc.ensureFieldsSet(). * java/io/ObjectInputStream.java (parseContent): Assign the handle for a PROXYCLASSDESC immediately after reading the marker. Index: ObjectInputStream.java =================================================================== --- ObjectInputStream.java (revision 123952) +++ ObjectInputStream.java (working copy) @@ -222,7 +222,14 @@ case TC_PROXYCLASSDESC: { - if(dump) dumpElementln("PROXYCLASS"); + if(dump) dumpElementln("PROXYCLASSDESC"); + + // The grammar at this point is + // TC_PROXYCLASSDESC newHandle proxyClassDescInfo + // i.e. we have to assign the handle immediately after + // reading the marker. + int handle = assignNewHandle("Dummy proxy"); + int n_intf = this.realInputStream.readInt(); String[] intfs = new String[n_intf]; for (int i = 0; i < n_intf; i++) @@ -250,7 +257,7 @@ new InternalError("Object ctor missing").initCause(x); } } - assignNewHandle(osc); + rememberHandle(osc,handle); if (!is_consumed) { @@ -1986,6 +1993,8 @@ private void dumpElementln (String msg, Object obj) { + if (obj == null) + obj = "(null)"; try { System.out.print(msg); Index: ObjectOutputStream.java =================================================================== --- ObjectOutputStream.java (revision 123952) +++ ObjectOutputStream.java (working copy) @@ -1212,10 +1212,14 @@ // writes out FIELDS of OBJECT for the specified ObjectStreamClass. - // FIELDS are already in canonical order. + // FIELDS are already supposed already to be in canonical order, but + // under some circumstances (to do with Proxies) this isn't the + // case, so we call ensureFieldsSet(). private void writeFields(Object obj, ObjectStreamClass osc) throws IOException { + osc.ensureFieldsSet(osc.forClass()); + ObjectStreamField[] fields = osc.fields; boolean oldmode = setBlockDataMode(false); @@ -1348,6 +1352,8 @@ System.out.print (" "); System.out.print (Thread.currentThread() + ": "); System.out.print (msg); + if (obj == null) + obj = "(null)"; if (java.lang.reflect.Proxy.isProxyClass(obj.getClass())) System.out.print (obj.getClass()); else Index: ObjectStreamClass.java =================================================================== --- ObjectStreamClass.java (revision 123952) +++ ObjectStreamClass.java (working copy) @@ -654,11 +654,25 @@ flags |= ObjectStreamConstants.SC_ENUM; } + // FIXME: This is a workaround for a fairly obscure bug that happens + // when reading a Proxy and then writing it back out again. The + // result is that the ObjectStreamClass doesn't have its fields set, + // generating a NullPointerException. Rather than this kludge we + // should probably fix the real bug, but it would require a fairly + // radical reorganization to do so. + final void ensureFieldsSet(Class cl) + { + if (! fieldsSet) + setFields(cl); + } + // Sets fields to be a sorted array of the serializable fields of // clazz. private void setFields(Class cl) { + fieldsSet = true; + SetAccessibleAction setAccessible = new SetAccessibleAction(); if (!isSerializable() || isExternalizable() || isEnum()) @@ -1094,6 +1108,9 @@ boolean isProxyClass = false; + // True after setFields() has been called + private boolean fieldsSet = false; + // This is probably not necessary because this class is special cased already // but it will avoid showing up as a discrepancy when comparing SUIDs. private static final long serialVersionUID = -6120832682080437368L;