[
https://issues.apache.org/jira/browse/LANG-1705?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18060928#comment-18060928
]
Makarand Hinge commented on LANG-1705:
--------------------------------------
[~ggregory]
It seems that the new casting behavior in SerializationUtils.clone() enforces
the expectation that the cloned instance should retain the original runtime
type. In our example, the issue arises because writeReplace() changes a Child
into a Parent, which effectively violates the clone contract and Liskov
substitutability.
Given this, would it be fair to say that preserving the exact runtime type
during serialization is the correct approach, and that adjusting the
serialization logic (rather than relaxing the cast) would be the recommended
solution?
> 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
> Priority: Major
> 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 SerializationTest$Parent to
> 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;
> public class SerializationTest
> {
> @Test
> public void serializeParent() throws Exception
> {
> // 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 Exception
> {
> 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)