Uploaded image for project: 'Commons Lang'
  1. Commons Lang
  2. LANG-1705

commons-lang3:3.13.0 introduces breaking change in SerializationUtils

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Open
    • Major
    • Resolution: Unresolved
    • 3.13.0
    • None
    • lang.*
    • None

    Description

      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:

      "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

      and also the contract of #clone, so could be treated as "told you", anyhow such code may exist and then #clone will fail with:

      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)
      

      Standalone test for reproducing the issue with 3.13.0:

      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);
              }
      
          }
      }
      

       

      Attachments

        1. SerializationTest.java
          2 kB
          Rico Neubauer

        Issue Links

          Activity

            People

              Unassigned Unassigned
              riconeubauer Rico Neubauer
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated: