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)

Reply via email to