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)
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)
- relates to
-
JDK-8210721 Replace legacy serial exception field with Throwable::cause
-
- Closed
-
-
JDK-6604085 REGRESSION: InvocationTargetException doesn't print chained exception's stack trace
-
- Closed
-
-
JDK-8259538 ExceptionInInitializerError.initCause() always throws exception
-
- Open
-