-
Enhancement
-
Resolution: Unresolved
-
P4
-
None
-
1.3.0, 1.4.0
-
Fix Understood
-
generic
-
generic
An earlier RFE (4139262) requested the addition of a skipObject() method to
ObjectInputStream for performance reasons, and was (justifiably) rejected on
the grounds that such a method would not result in significant performance
gains. However, the proposed solution for bugs 4313167
("ClassNotFoundException in skipped objects causes serialization to fail") and
4312433 ("reading back reference to obj with unresolved class should throw
exception") reestablishes the need for skipObject().
The proposed solution for bugs 4313167 and 4312433 involves ObjectInputStream
maintaining an internal table mapping wire handles for deserialized objects to
associated ClassNotFoundExceptions. ObjectInputStream must maintain this table
by tracking dependencies between deserialized objects: for instance, if
deserialized object A contains a reference to deserialized object B, and B's
class cannot be resolved in the local VM, the appropriate
ClassNotFoundException should be associated with the handles for both A and B
(unless A's handle was already tagged with a previous exception). Back
references complicate the picture somewhat; e.g., if object D contains a back
reference to C, and an exception is associated with C sometime after D has
already been deserialized (as the result of C referencing another object whose
class is missing), the resulting exception must be propagated back to D's
handle.
Now consider the interaction of ClassNotFoundException tracking with
class-defined readObject() methods. If a class defines a readObject() method
which calls ObjectInputStream.readObject(), and OIS.readObject() throws a
ClassNotFoundException, should the exception automatically be associated with
the enclosing object? At first blush, the answer would seem to be no: the
custom readObject() method may wish to tolerate the missing class, and can do
so by catching and swallowing the ClassNotFoundException--i.e., the exception
should be associated with the enclosing object only if the exception is thrown
(directly or indirectly) from the object's custom readObject method.
The problem with this approach is that it does not take into account objects
whose exception status is not known until sometime after OIS.readObject()
returns. More specifically, consider the case where the call to
OIS.readObject() reads in a back reference and returns the (partially
deserialized) contained object. Further suppose that later on, a
ClassNotFoundException is associated with the contained object. Should this
exception be propagated to the container object? If no, then delayed back
reference exceptions will never be propagated--even for "intolerant" custom
readObject() implementations. This is clearly unacceptable.
The alternative, then, is for the delayed exception to always be propagated to
the container object. This, however, clashes with the "catch and swallow"
method of tolerating exceptions described above--immediate exceptions can be
caught and forgotten by a tolerant custom readObject() method, but delayed
exceptions cannot be avoided. It seems that OIS.readObject() should have the
same exception-propagation properties regardless of whether the exception is
delayed or immediate, which suggests that OIS.readObject() should propagate
exceptions to the container object *even if* the exception is immediate (i.e.,
thrown by OIS.readObject(), and thus catchable).
However, if OIS.readObject() always propagates exceptions to the containing
object, custom readObject() methods have no means of tolerating exceptions. In
other words, there is no way for a custom readObject() method to duplicate the
functionality present in default object serialization, which is able to
disregard ClassNotFoundExceptions occurring in skipped fields.
This RFE proposes the addition of a skipObject() method to ObjectInputStream:
void skipObject() throws IOException;
This method would allow a custom readObject() method to indicate that any
ClassNotFoundException (delayed or immediate) occurring in the skipped
object should not be propagated to the containing object. As previously
mentioned, calling ObjectInputStream.readObject() would then indicate that any
ClassNotFoundException occuring in the read object should always be propagated
to the containing object.
###@###.### 2002-05-08
Upon further reflection, a more flexible and powerful solution would be to
instead add a new ClassNotFoundException-tolerant readObject() method to
ObjectInputStream. This method would allow one to attempt to read an
object (and receive its value), but not be tainted with a ClassNotFoundException
if the read failed. This functionality would subsume that of the proposed
skipObject() above--to skip an object, one could simply call the "tolerant"
readObject method and ignore its return value. One possible signature for
the method would be:
Object readObject(Object fallbackValue) throws IOException;
Essentially, this variant of readObject would attempt to read in the
object and return it. If the read failed due to an unresolvable class,
however, readObject would return the fallbackValue provided as an argument.
A similar fallback value could be passed to a new constructor of
ObjectStreamField, indicating a ClassNotFoundException-tolerant serial
field:
ObjectStreamField(String name,
Class type,
boolean unshared,
Object fallbackValue);
During deserialization, if the value for the indicated field is unresolvable,
then the field would be set to the given fallback value (rather than
resulting in a ClassNotFoundException being associated with the instance
that owns the field).
ObjectInputStream for performance reasons, and was (justifiably) rejected on
the grounds that such a method would not result in significant performance
gains. However, the proposed solution for bugs 4313167
("ClassNotFoundException in skipped objects causes serialization to fail") and
4312433 ("reading back reference to obj with unresolved class should throw
exception") reestablishes the need for skipObject().
The proposed solution for bugs 4313167 and 4312433 involves ObjectInputStream
maintaining an internal table mapping wire handles for deserialized objects to
associated ClassNotFoundExceptions. ObjectInputStream must maintain this table
by tracking dependencies between deserialized objects: for instance, if
deserialized object A contains a reference to deserialized object B, and B's
class cannot be resolved in the local VM, the appropriate
ClassNotFoundException should be associated with the handles for both A and B
(unless A's handle was already tagged with a previous exception). Back
references complicate the picture somewhat; e.g., if object D contains a back
reference to C, and an exception is associated with C sometime after D has
already been deserialized (as the result of C referencing another object whose
class is missing), the resulting exception must be propagated back to D's
handle.
Now consider the interaction of ClassNotFoundException tracking with
class-defined readObject() methods. If a class defines a readObject() method
which calls ObjectInputStream.readObject(), and OIS.readObject() throws a
ClassNotFoundException, should the exception automatically be associated with
the enclosing object? At first blush, the answer would seem to be no: the
custom readObject() method may wish to tolerate the missing class, and can do
so by catching and swallowing the ClassNotFoundException--i.e., the exception
should be associated with the enclosing object only if the exception is thrown
(directly or indirectly) from the object's custom readObject method.
The problem with this approach is that it does not take into account objects
whose exception status is not known until sometime after OIS.readObject()
returns. More specifically, consider the case where the call to
OIS.readObject() reads in a back reference and returns the (partially
deserialized) contained object. Further suppose that later on, a
ClassNotFoundException is associated with the contained object. Should this
exception be propagated to the container object? If no, then delayed back
reference exceptions will never be propagated--even for "intolerant" custom
readObject() implementations. This is clearly unacceptable.
The alternative, then, is for the delayed exception to always be propagated to
the container object. This, however, clashes with the "catch and swallow"
method of tolerating exceptions described above--immediate exceptions can be
caught and forgotten by a tolerant custom readObject() method, but delayed
exceptions cannot be avoided. It seems that OIS.readObject() should have the
same exception-propagation properties regardless of whether the exception is
delayed or immediate, which suggests that OIS.readObject() should propagate
exceptions to the container object *even if* the exception is immediate (i.e.,
thrown by OIS.readObject(), and thus catchable).
However, if OIS.readObject() always propagates exceptions to the containing
object, custom readObject() methods have no means of tolerating exceptions. In
other words, there is no way for a custom readObject() method to duplicate the
functionality present in default object serialization, which is able to
disregard ClassNotFoundExceptions occurring in skipped fields.
This RFE proposes the addition of a skipObject() method to ObjectInputStream:
void skipObject() throws IOException;
This method would allow a custom readObject() method to indicate that any
ClassNotFoundException (delayed or immediate) occurring in the skipped
object should not be propagated to the containing object. As previously
mentioned, calling ObjectInputStream.readObject() would then indicate that any
ClassNotFoundException occuring in the read object should always be propagated
to the containing object.
###@###.### 2002-05-08
Upon further reflection, a more flexible and powerful solution would be to
instead add a new ClassNotFoundException-tolerant readObject() method to
ObjectInputStream. This method would allow one to attempt to read an
object (and receive its value), but not be tainted with a ClassNotFoundException
if the read failed. This functionality would subsume that of the proposed
skipObject() above--to skip an object, one could simply call the "tolerant"
readObject method and ignore its return value. One possible signature for
the method would be:
Object readObject(Object fallbackValue) throws IOException;
Essentially, this variant of readObject would attempt to read in the
object and return it. If the read failed due to an unresolvable class,
however, readObject would return the fallbackValue provided as an argument.
A similar fallback value could be passed to a new constructor of
ObjectStreamField, indicating a ClassNotFoundException-tolerant serial
field:
ObjectStreamField(String name,
Class type,
boolean unshared,
Object fallbackValue);
During deserialization, if the value for the indicated field is unresolvable,
then the field would be set to the given fallback value (rather than
resulting in a ClassNotFoundException being associated with the instance
that owns the field).
- relates to
-
JDK-4312433 reading back reference to obj with unresolved class should throw exception
-
- Resolved
-
-
JDK-4139262 java.io.ObjectInputStream needs skipObject()
-
- Closed
-
-
JDK-4226541 "ForgivingObjectInputStream" to improve deserialization robustness
-
- Closed
-