retronym commented on PR #1161:
URL: https://github.com/apache/fury/pull/1161#issuecomment-2702541425

   I've been looking at this issue in the context of normal JDK serialization 
and Scala collections, which as-of Scala 2.13, use the serialization proxy 
pattern much more widely and are now prone to corrupt deserialized object 
graphs.
   
   I wrote a few tests to show that the underlying issue affect a small number 
of Java collections, too. I then tried the same test cases with Fury, both with 
and without custom Scala serializers registered.
   
   
   ```java
   package repro;
   
   import org.apache.commons.lang3.SerializationUtils;
   import org.apache.fury.Fury;
   import org.apache.fury.config.Language;
   import org.junit.Test;
   
   import java.io.Serializable;
   import java.util.ArrayList;
   import java.util.Arrays;
   import java.util.List;
   
   
   public class JavaCollectionCircularSerialization {
       @Test
       public void testJavaArrayList() {
           testJavaSerialization(new ArrayListFactory());
       }
   
       @Test
       public void testJavaAsList() {
           testJavaSerialization(new AsListFactory());
       }
   
       @Test
       public void testJavaListOf() {
           // java.lang.ClassCastException: class java.util.CollSer cannot be 
cast to class java.util.List (java.util.CollSer and java.util.List are in 
module java.base of loader 'bootstrap')
           testJavaSerialization(new ListOfFactory());
       }
   
       @Test
       public void testFuryArrayList() {
           testFurySerialization(new ArrayListFactory());
       }
   
       @Test
       public void testFuryAsList() {
           // Cannot invoke "java.util.List.getClass()" because "o" is null
           testFurySerialization(new AsListFactory());
       }
   
       @Test
       public void testFuryListOf() {
           // Cannot invoke "java.util.List.getClass()" because "o" is null
           testFurySerialization(new ListOfFactory());
       }
   
       interface ListFactory {
           <T> List<T> newList(T elem);
       }
   
       static class ArrayListFactory implements ListFactory {
           @Override
           public <T> List<T> newList(T elem) {
               ArrayList<T> os = new ArrayList<>();
               os.add(elem);
               return os;
           }
       }
   
       static class AsListFactory implements ListFactory {
           @Override
           public <T> List<T> newList(T elem) {
               return Arrays.asList(elem);
           }
       }
   
       static class ListOfFactory implements ListFactory {
           @Override
           public <T> List<T> newList(T elem) {
               return List.of(elem);
           }
       }
   
       static <T> void testJavaSerialization(ListFactory factory) {
           var c1 = new ArrayList<List<?>>();
           var c2 = factory.newList(c1);
           c1.add(c2);
           List<List<List<?>>> clone = (List<List<List<?>>>) 
SerializationUtils.clone((Serializable) c2);
           var o = clone.get(0).get(0);
           System.out.println(o.getClass()); // CollSer
           if (o.size() != 1) {
               throw new AssertionError("size not preserved: " + o.size());
           }
           if (o != clone) {
               throw new AssertionError("circular ref not preserved");
           }
       }
   
       static <T> void testFurySerialization(ListFactory factory) {
           var c1 = new ArrayList<List<?>>();
           var c2 = factory.newList(c1);
           c1.add(c2);
   
           Fury fury = Fury.builder()
                   .withLanguage(Language.JAVA)
                   .requireClassRegistration(false)
                   .withRefTracking(true)
                   .build();
           List<ArrayList<List<?>>> clone = (List<ArrayList<List<?>>>) 
fury.deserialize(fury.serialize(c2));
   
           var o = clone.get(0).get(0);
           System.out.println(o.getClass()); // CollSer
           if (o.size() != 1) {
               throw new AssertionError("size not preserved: " + o.size());
           }
           if (o != clone) {
               throw new AssertionError("circular ref not preserved");
           }
       }
   }
   
   ```
   
   ```java
   package repro;
   
   import org.apache.commons.lang3.SerializationUtils;
   import org.apache.fury.Fury;
   import org.apache.fury.config.Language;
   import org.apache.fury.serializer.Serializer;
   import org.apache.fury.serializer.scala.ScalaSerializers;
   import org.junit.Test;
   
   import java.io.Serializable;
   import java.util.ArrayList;
   import java.util.Arrays;
   import java.util.List;
   
   // Scala 2.13 uses the serialization proxy pattern for collections which had 
run into
   // the limitation of Java serialization + readResolve + circular references.
   public class ScalaCollectionCircularSerialization {
       @Test
       public void testJavaDefaultSeq() {
           // java.lang.ClassCastException: class 
scala.collection.generic.DefaultSerializationProxy cannot be cast to class 
scala.collection.Seq (scala.collection.generic.DefaultSerializationProxy and 
scala.collection.Seq are in unnamed module of loader 'app')
           testJavaSerialization(new DefaultSeqFactory());
       }
   
       @Test
       public void testFuryDefaultSeq() {
           // java.lang.ClassCastException: class 
scala.collection.generic.DefaultSerializationProxy cannot be cast to class 
scala.collection.Seq (scala.collection.generic.DefaultSerializationProxy and 
scala.collection.Seq are in unnamed module of loader 'app')
           testFurySerialization(new DefaultSeqFactory(), false);
       }
   
       @Test
       public void testFuryWithCustomScalaSerializersDefaultSeq() {
           // java.lang.NullPointerException: Cannot invoke 
"scala.collection.Seq.getClass()" because "o" is null
           testFurySerialization(new DefaultSeqFactory(), true);
       }
   
       interface SeqFactory {
           <T> scala.collection.Seq<T> newSeq(T elem);
       }
   
       static class DefaultSeqFactory implements SeqFactory {
           @Override
           public <T> scala.collection.Seq<T> newSeq(T elem) {
               var builder = scala.collection.Seq$.MODULE$.newBuilder();
               builder.addOne(elem);
               return (scala.collection.Seq<T>) builder.result();
           }
       }
   
       static <T> void testJavaSerialization(SeqFactory factory) {
           var c1 = new ArrayList<scala.collection.Seq<?>>();
           var c2 = factory.newSeq(c1);
           c1.add(c2);
           scala.collection.Seq<List<scala.collection.Seq<?>>> clone = 
(scala.collection.Seq<List<scala.collection.Seq<?>>>) 
SerializationUtils.clone((Serializable) c2);
           var o = clone.apply(0).get(0);
           System.out.println(o.getClass()); // CollSer
           if (o.size() != 1) {
               throw new AssertionError("size not preserved: " + o.size());
           }
           if (o != clone) {
               throw new AssertionError("circular ref not preserved");
           }
       }
   
       static <T> void testFurySerialization(SeqFactory factory, boolean 
useCustomScalaSerializer) {
           var c1 = new ArrayList<scala.collection.Seq<?>>();
           var c2 = factory.newSeq(c1);
           c1.add(c2);
   
           Fury fury = Fury.builder()
                   .withLanguage(Language.JAVA)
                   .requireClassRegistration(false)
                   .withRefTracking(true)
                   .build();
           if (useCustomScalaSerializer) {
               ScalaSerializers.registerSerializers(fury);
           }
   
           scala.collection.Seq<List<scala.collection.Seq<?>>> clone = 
(scala.collection.Seq<List<scala.collection.Seq<?>>>) 
fury.deserialize(fury.serialize(c2));
   
           var o = clone.apply(0).get(0);
           System.out.println(o.getClass()); // CollSer
           if (o.size() != 1) {
               throw new AssertionError("size not preserved: " + o.size());
           }
           if (o != clone) {
               throw new AssertionError("circular ref not preserved");
           }
       }
   }
   ```
   
   https//github.com/retronym/circular-serialization.git


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to