JDK-4385429 : exception chaining facility: serialization of some Exception classes broken
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.4.0
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_2.5
  • CPU: sparc
  • Submitted: 2000-11-02
  • Updated: 2021-01-11
  • Resolved: 2000-11-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
1.4.0 betaFixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
With the addition of the exception chaining facility, some Exception types
that supported legacy exception chaining were retrofitted to work with
the new facility.  Some of these exceptions will no longer deserialize.
In particular, ClassNotFoundException can no longer be serialized and
deserialized in 1.4 without throwing an IllegalStateException.

The problem is the readObject method of ClassNotFoundException (and
other exceptions such as UndeclaredThrowable exception).  When a CNFE
is created, its cause field (in Throwable) will be set to either
null or some value other than the CNFE itself (i.e., it will have
an initialized value).  When the CNFE is deserialized, the cause field
will have an initialized value.  The readObject method obtains the
value of the cause by calling getCause.  The code assumes that if
getCause returns null, that the cause field has not been initialized.
However, if the cause field was initialized (in serializing/deserializing
between 1.4 implementations it will be initialized), the field may
be initialized to null.  The readObject method next will attempt to
initialize the cause (which has already been initialized) by calling 
initCause which will throw IllegalStateException because it assumes 
the caller is attempting to overwrite the already initialized value.

There is no way to distinguish (via getCause) whether the cause is
validly initialized to null or whether the cause has not yet been 
initialized.

Here is a program that serializes and deserializes a CNFE and 
throws an IllegalStateException when run with our recent nightly builds:

import java.io.*;

public class Bug {

    public static void main(String[] args) {

	try {

	    ByteArrayOutputStream bout = new ByteArrayOutputStream();
	    ObjectOutputStream out = new ObjectOutputStream(bout);
	    out.writeObject(new ClassNotFoundException("test"));
	    out.flush();

	    ByteArrayInputStream bin =
		new ByteArrayInputStream(bout.toByteArray());
	    ObjectInputStream in = new ObjectInputStream(bin);
	    Exception cnfe = (ClassNotFoundException) in.readObject();
	    System.err.println("test passed");
	    
	} catch (Exception e) {

	    System.err.println("test failed: " + e.getMessage());
	    e.printStackTrace();
	}
    }
}

It produces the following output:
	
	test failed: Can't overwrite cause
java.lang.IllegalStateException: Can't overwrite cause
	at java.lang.Throwable.initCause(Throwable.java:305)
	at java.lang.ClassNotFoundException.readObject(ClassNotFoundException.java:115)
	at java.lang.reflect.Method.invoke(Native Method)
	at java.io.ObjectInputStream.invokeObjectReader(ObjectInputStream.java:2213)
	at java.io.ObjectInputStream.inputObject(ObjectInputStream.java:1410)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:386)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:236)
	at Bug.main(Bug.java:17)

			

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: merlin FIXED IN: merlin INTEGRATED IN: merlin-beta VERIFIED IN: merlin-beta3
14-06-2004

SUGGESTED FIX One solution is to add a method to Throwable "isCauseInitialized" or some such that returns true if the cause was initialized, otherwise it returns false. The readObject method can then be modified to set the cause only if it hasn't yet been initialized.
11-06-2004

PUBLIC COMMENTS .
10-06-2004

EVALUATION The reporter is correct. A careful reading of the readObject method in ClassNotFoundException will show that it already contains code designed to detect this situation, but the code is broken; it catches IllegalArgumentException instead of IllegalStateException, so the IllegalStateException propagates outward and prevents deserialization. Substituting the intended class in the catch clause will fix this problem. The same problem exists in ExceptionInInitializerError, UndeclaredThrowableException, InvocationTargetException, PrivilegedActionException, PrinterIOException. (Can you say "cut-and-paste?) The suggested fix (adding a public method to Throwable to allow callers to query the "initialized chained exception" status of an exception) is unnecessary. The presence of this public method would be confusing to the average programmer and so should be avoided. joshua.bloch@Eng 2000-11-02
02-11-2000