Rico Neubauer created LANG-1705: ----------------------------------- Summary: commons-lang3:3.13.0 introduces breaking change in SerializationUtils Key: LANG-1705 URL: https://issues.apache.org/jira/browse/LANG-1705 Project: Commons Lang Issue Type: Bug Components: lang.* Affects Versions: 3.13.0 Reporter: Rico Neubauer Attachments: SerializationTest.java
Want to report a change in behavior, which is not necessarily a bug/regression, but might get triggered by crude classes getting serialized. With this commit: 357951ff5c28dbd724611e8d41e23686f09a164a released in 3.13.0 SerializationUtils#clone changed a bit that it now makes a cast to the expected type. This sounds reasonable, but might break existing code that serializes classes that have parent-child dependencies and overwrite #writeObject in a certain way. I'll provide a standalone test case below with comments that should make it clear. Issue is in case writeObject changes the type of object that gets serialized, then class obtained from in#readObject of the previously serialized object will be different from the expected one. This most probably is a violation of: {noformat} "The object returned should be either of the same type as the object passed in or an object that when read and resolved will result in an object of a type that is compatible with all references to the object." https://docs.oracle.com/en/java/javase/11/docs/specs/serialization/output.html{noformat} and also the contract of #clone, so could be treated as "told you", anyhow such code may exist and then #clone will fail with: {code:java} java.lang.ClassCastException: Cannot cast com.seeburger.test.SerializationTest$Parent to com.seeburger.test.SerializationTest$Child at java.base/java.lang.Class.cast(Class.java:3605) at org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:148) {code} Standalone test for reproducing the issue with 3.13.0: {code:java} import static org.junit.Assert.assertEquals; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Objects; import org.apache.commons.lang3.SerializationUtils; import org.junit.Test; import com.seeburger.frame.integration.adapter.AdapterException; public class SerializationTest { @Test public void serializeParent() throws AdapterException, RuntimeException { // this test will pass with any version Parent parent = new Parent(true); Parent clonedParent = SerializationUtils.clone(parent); assertEquals(parent, clonedParent); } @Test public void serializeChild() throws AdapterException, RuntimeException { Child child = new Child(true); // this test will pass with org.apache.commons:commons-lang3:3.12.0, // but will fail with 3.13.0 Parent clonedChild = SerializationUtils.clone(child); assertEquals(new Parent(true), clonedChild); } static class Parent implements Serializable { private static final long serialVersionUID = 1L; protected boolean someField; Parent(boolean someField) { this.someField = someField; } protected Parent(Parent parent) { this.someField = parent.someField; } /** * protected modifier lets also child's serialization call this */ protected Object writeReplace() throws ObjectStreamException { return new Parent(this); } @Override public int hashCode() { return Objects.hash(someField); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Parent other = (Parent)obj; return someField == other.someField; } } static class Child extends Parent { private static final long serialVersionUID = 2L; Child(boolean someField) { super(someField); } } } {code} -- This message was sent by Atlassian Jira (v8.20.10#820010)