Breaking object-serialization in Java is easy. Just change some fields in the used DTOs transfered via network and whooopp its broken.
I came along this topic as i tried some openjdk contribution.
The contribution was to same exceptions that are extending RuntimeException but not supporting the 2 conventional constructors with the exception cause.
There are 10 Exception where i found a seperate cause field.
1. javax/xml/crypto/NoSuchMechanismException.java: private Throwable _exception;
2. javax/security/sasl/SaslException.java: private Throwable _exception;
3. java/lang/reflect/UndeclaredThrowableException.java: private Throwable undeclaredThrowable;
4. java/lang/reflect/InvocationTargetException.java: private Throwable target;
5. java/lang/ClassNotFoundException.java: private Throwable ex;
6. com/sun/java/browser/dom/DOMAccessException.java: private Throwable ex;
7. com/sun/java/browser/dom/DOMUnsupportedException.java: private Throwable ex;
8. javax/naming/NamingException.java: protected Throwable rootException = null;
9. java/rmi/RemoteException.java: public Throwable detail;
10. java/rmi/activation/ActivationException.java: public Throwable detail;
Nr. 1 was the first i have looked at. My first attempt was to just remove the cause field add the missing constructors and remove the unnessacery methods for printing the stacktrace. But this definitly break serialisation compatibility. The serialversionUID is the same but the serialization content is missing the cause field.
My second attempt was to add the cause field in serialization manually through introducing something like this.
65 private static final ObjectStreamField[] serialPersistentFields = {
66 new ObjectStreamField("cause", Throwable.class)};
114 private void readObject(ObjectInputStream stream)
115 throws IOException, ClassNotFoundException {
116 initCauseFieldFromSerializationStream(stream, "cause");
117 }
118
119 private void writeObject(ObjectOutputStream stream)
120 throws IOException {
121 writeCauseAsAPrivateField(stream, "cause");
122 }
While introducing the following in Throwable.java
1097 protected void initCauseFieldFromSerializationStream(ObjectInputStream stream, String oldFieldName) throws ClassNotFoundException, IOException {
1098 GetField readFields = stream.readFields();
1099 Throwable cause = (Throwable) readFields.get(oldFieldName, this);
1100 /* If there is a cause that is deserialized and there is no cause
1101 * set in Throwable.cause (== null means serialized prior jdk8)
1102 * then initialize cause. There is another implicit functionality here:
1103 * If Throwable.cause is initialized yet(since jdk8) and
1104 * this.getCause() != cause then there is something is rotten in the
1105 * state of Denmark. Calling initCause in this situation will result
1106 * in an exception
1107 */
1108 if (cause != null && this.getCause() != cause) {
1109 // If cause is
1110 initCause(cause);
1111 }
1112 }
1131 protected void writeCauseAsAPrivateField(ObjectOutputStream stream, String oldFieldName) throws IOException {
1132 PutField putFields = stream.putFields();
1133 if (this.getCause() != null) {
1134 putFields.put(oldFieldName, this.cause);
1135 }
1136 stream.writeFields();
1137 }
This might be ok. But it is somewhat the otherway around this problem is solved already in
the other cases mentioned above. The other solution leaves the cause field but utilizes the
functionality of chaining in Throwable in an much easier fashion.
But this will be part of another post.
0 Responses to “Backward compatibility Object Serialization in Java”