gudzpoz opened a new issue, #2251: URL: https://github.com/apache/fury/issues/2251
### Search before asking - [x] I had searched in the [issues](https://github.com/apache/fury/issues) and found no similar issues. ### Version Fury: `'org.apache.fury:fury-core:0.10.2'` JDK: GraalVM 23.0.2+7.1 (build 23.0.2+7-jvmci-b01) ### Component(s) Java ### Minimal reproduce step A collection class with a custom serializer: ```java public static final class EmptyList extends AbstractList<Object> { @Override public Object get(int index) { throw new IndexOutOfBoundsException(); } @Override public int size() { return 0; } public static final class EmptySerializer extends Serializer<EmptyList> { public EmptySerializer(Fury fury) { super(fury, EmptyList.class); } @Override public void write(MemoryBuffer buffer, EmptyList value) { // no-op } @Override public EmptyList read(MemoryBuffer buffer) { return new EmptyList(); } } } ``` When it is put in another class: ```java public record SomeRecord(EmptyList specialList, List<Object> normalList) { } ``` It will produce `ClassCastException` when serialized: ```java @Test public void testCollection() { Fury fury = Fury.builder() .withLanguage(Language.JAVA) .build(); fury.register(EmptyList.class); fury.registerSerializer(EmptyList.class, EmptyList.EmptySerializer.class); fury.register(SomeRecord.class); SomeRecord someRecord = new SomeRecord(new EmptyList(), List.of()); byte[] bytes = fury.serialize(someRecord); // <-- ClassCastException here Object output = fury.deserialize(bytes); SomeRecord someRecord2 = assertInstanceOf(SomeRecord.class, output); assertEquals(someRecord, someRecord2); } ``` ### What did you expect to see? 1. I expect the `testCollection` test above to pass without throwing exceptions. 2. I expect being able to write a custom serializer for *any* class. Forcing the usage of `AbstractCollectionSerializer` for all collections is not ideal and there are plenty of special cases when one wants a special serializer. ### What did you see instead? ``` 2025-05-26 03:42:55 INFO Fury:159 [Test worker] - Created new fury org.apache.fury.Fury@2072acb2 2025-05-26 03:42:55 INFO CompileUnit:55 [Test worker] - Generate code for party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest_SomeRecordFuryCodec_0 took 31 ms. 2025-05-26 03:42:55 INFO JaninoUtils:121 [Test worker] - Compile [DumpUtilsTest_SomeRecordFuryCodec_0] take 100 ms class party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest$EmptyList$EmptySerializer cannot be cast to class org.apache.fury.serializer.collection.AbstractCollectionSerializer (party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest$EmptyList$EmptySerializer and org.apache.fury.serializer.collection.AbstractCollectionSerializer are in unnamed module of loader 'app') java.lang.ClassCastException: class party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest$EmptyList$EmptySerializer cannot be cast to class org.apache.fury.serializer.collection.AbstractCollectionSerializer (party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest$EmptyList$EmptySerializer and org.apache.fury.serializer.collection.AbstractCollectionSerializer are in unnamed module of loader 'app') at party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest_SomeRecordFuryCodec_0.<init>(DumpUtilsTest_SomeRecordFuryCodec_0.java:50) at org.apache.fury.serializer.Serializers.createSerializer(Serializers.java:129) at org.apache.fury.serializer.Serializers.newSerializer(Serializers.java:104) at org.apache.fury.resolver.ClassResolver.createSerializer(ClassResolver.java:1232) at org.apache.fury.resolver.ClassResolver.getOrUpdateClassInfo(ClassResolver.java:1170) at org.apache.fury.Fury.write(Fury.java:349) at org.apache.fury.Fury.serialize(Fury.java:273) at org.apache.fury.Fury.serialize(Fury.java:227) at party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.testCollection(DumpUtilsTest.java:95) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at java.base/java.util.ArrayList.forEach(ArrayList.java:1597) at java.base/java.util.ArrayList.forEach(ArrayList.java:1597) ``` ### Anything Else? <details> <summary>The generated code</summary> The exception is from the `abstractCollectionSerializer0 = ((AbstractCollectionSerializer)classResolver.getRawSerializer(party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest$EmptyList.class));` line. ```java package party.iroiro.juicemacs.elisp.runtime.pdump; import org.apache.fury.Fury; import org.apache.fury.memory.MemoryBuffer; import org.apache.fury.resolver.NoRefResolver; import org.apache.fury.memory.Platform; import org.apache.fury.resolver.ClassInfo; import org.apache.fury.resolver.ClassInfoHolder; import org.apache.fury.resolver.ClassResolver; import org.apache.fury.builder.Generated; import org.apache.fury.serializer.CodegenSerializer.LazyInitBeanSerializer; import org.apache.fury.serializer.EnumSerializer; import org.apache.fury.serializer.Serializer; import org.apache.fury.serializer.StringSerializer; import org.apache.fury.serializer.ObjectSerializer; import org.apache.fury.serializer.CompatibleSerializer; import org.apache.fury.serializer.collection.AbstractCollectionSerializer; import org.apache.fury.serializer.collection.AbstractMapSerializer; import org.apache.fury.builder.Generated.GeneratedObjectSerializer; public final class DumpUtilsTest_SomeRecordFuryCodec_0 extends GeneratedObjectSerializer { private final NoRefResolver refResolver; private final ClassResolver classResolver; private final StringSerializer strSerializer; private Fury fury; private ClassInfo listClassInfo; private final ClassInfoHolder objectClassInfoHolder; private final org.apache.fury.serializer.Serializers.EmptyObjectSerializer emptyObjectSerializer; private ClassInfo object1ClassInfo; private final AbstractCollectionSerializer abstractCollectionSerializer0; private final ClassInfoHolder object5ClassInfoHolder; private ClassInfo object7ClassInfo; private final ClassInfoHolder list5ClassInfoHolder; private final ClassInfoHolder object11ClassInfoHolder; public DumpUtilsTest_SomeRecordFuryCodec_0(Fury fury, Class classType) { super(fury, classType); this.fury = fury; fury.getClassResolver().setSerializerIfAbsent(classType, this); org.apache.fury.resolver.RefResolver refResolver0 = fury.getRefResolver(); refResolver = ((NoRefResolver)refResolver0); classResolver = fury.getClassResolver(); strSerializer = fury.getStringSerializer(); listClassInfo = classResolver.nilClassInfo(); objectClassInfoHolder = classResolver.nilClassInfoHolder(); emptyObjectSerializer = ((org.apache.fury.serializer.Serializers.EmptyObjectSerializer)classResolver.getRawSerializer(java.lang.Object.class)); object1ClassInfo = classResolver.nilClassInfo(); abstractCollectionSerializer0 = ((AbstractCollectionSerializer)classResolver.getRawSerializer(party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest$EmptyList.class)); object5ClassInfoHolder = classResolver.nilClassInfoHolder(); object7ClassInfo = classResolver.nilClassInfo(); list5ClassInfoHolder = classResolver.nilClassInfoHolder(); object11ClassInfoHolder = classResolver.nilClassInfoHolder(); } private AbstractCollectionSerializer writeCollectionClassInfo(MemoryBuffer memoryBuffer, java.util.List list1) { ClassResolver classResolver = this.classResolver; Class value = listClassInfo.getCls(); Class cls = list1.getClass(); if ((value != cls)) { listClassInfo = classResolver.getClassInfo(cls); } classResolver.writeClass(memoryBuffer, listClassInfo); return ((AbstractCollectionSerializer)listClassInfo.getSerializer()); } private void sameElementClassWrite(java.util.List list2, boolean value0, int value1, MemoryBuffer memoryBuffer1, int value2) { Serializer serializer; if (((value1 & 4) != 4)) { serializer = emptyObjectSerializer; } else { serializer = objectClassInfoHolder.getSerializer(); } for (int i = 0; i < value2; i+=1) { Object object0 = list2.get(i); if (value0) { if ((object0 == null)) { memoryBuffer1.writeByte(((byte)-3)); } else { memoryBuffer1.writeByte(((byte)-1)); serializer.write(memoryBuffer1, object0); } } else { serializer.write(memoryBuffer1, object0); } } } private void writeClassAndObject(MemoryBuffer memoryBuffer2, Object object2) { ClassResolver classResolver = this.classResolver; Class value3 = object1ClassInfo.getCls(); Class cls0 = object2.getClass(); if ((value3 != cls0)) { object1ClassInfo = classResolver.getClassInfo(cls0); } classResolver.writeClass(memoryBuffer2, object1ClassInfo); object1ClassInfo.getSerializer().write(memoryBuffer2, object2); } private void writeClassAndObject1(MemoryBuffer memoryBuffer3, Object object3) { ClassResolver classResolver = this.classResolver; Class value4 = object1ClassInfo.getCls(); Class cls1 = object3.getClass(); if ((value4 != cls1)) { object1ClassInfo = classResolver.getClassInfo(cls1); } classResolver.writeClass(memoryBuffer3, object1ClassInfo); object1ClassInfo.getSerializer().write(memoryBuffer3, object3); } private void writeFields(MemoryBuffer memoryBuffer4, party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.SomeRecord someRecord1) { java.util.List normalList = someRecord1.normalList(); if ((normalList == null)) { memoryBuffer4.writeByte(((byte)-3)); } else { memoryBuffer4.writeByte(((byte)-1)); AbstractCollectionSerializer abstractCollectionSerializer = this.writeCollectionClassInfo(memoryBuffer4, normalList); if (abstractCollectionSerializer.supportCodegenHook()) { java.util.List list0 = (java.util.List)abstractCollectionSerializer.onCollectionWrite(memoryBuffer4, normalList); int value5 = list0.size(); if ((value5 > 0)) { int value6 = abstractCollectionSerializer.writeTypeNullabilityHeader(memoryBuffer4, list0, java.lang.Object.class, objectClassInfoHolder); boolean sameElementClass = (value6 & 8) != 8; boolean hasNull = (value6 & 2) == 2; if (sameElementClass) { this.sameElementClassWrite(list0, hasNull, value6, memoryBuffer4, value5); } else { for (int i0 = 0; i0 < value5; i0+=1) { Object object4 = list0.get(i0); if (hasNull) { if ((object4 == null)) { memoryBuffer4.writeByte(((byte)-3)); } else { memoryBuffer4.writeByte(((byte)-1)); this.writeClassAndObject(memoryBuffer4, object4); } } else { this.writeClassAndObject1(memoryBuffer4, object4); } } } } } else { abstractCollectionSerializer.write(memoryBuffer4, normalList); } } } private void sameElementClassWrite1(int value7, java.util.List list3, int value8, boolean value9, MemoryBuffer memoryBuffer5) { Serializer serializer0; if (((value7 & 4) != 4)) { serializer0 = emptyObjectSerializer; } else { serializer0 = object5ClassInfoHolder.getSerializer(); } for (int i1 = 0; i1 < value8; i1+=1) { Object object6 = list3.get(i1); if (value9) { if ((object6 == null)) { memoryBuffer5.writeByte(((byte)-3)); } else { memoryBuffer5.writeByte(((byte)-1)); serializer0.write(memoryBuffer5, object6); } } else { serializer0.write(memoryBuffer5, object6); } } } private void writeClassAndObject2(MemoryBuffer memoryBuffer6, Object object8) { ClassResolver classResolver = this.classResolver; Class value10 = object7ClassInfo.getCls(); Class cls2 = object8.getClass(); if ((value10 != cls2)) { object7ClassInfo = classResolver.getClassInfo(cls2); } classResolver.writeClass(memoryBuffer6, object7ClassInfo); object7ClassInfo.getSerializer().write(memoryBuffer6, object8); } private void writeClassAndObject3(MemoryBuffer memoryBuffer7, Object object9) { ClassResolver classResolver = this.classResolver; Class value11 = object7ClassInfo.getCls(); Class cls3 = object9.getClass(); if ((value11 != cls3)) { object7ClassInfo = classResolver.getClassInfo(cls3); } classResolver.writeClass(memoryBuffer7, object7ClassInfo); object7ClassInfo.getSerializer().write(memoryBuffer7, object9); } private void writeFields1(MemoryBuffer memoryBuffer8, party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.SomeRecord someRecord2) { AbstractCollectionSerializer abstractCollectionSerializer0 = this.abstractCollectionSerializer0; party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.EmptyList specialList = someRecord2.specialList(); if ((specialList == null)) { memoryBuffer8.writeByte(((byte)-3)); } else { memoryBuffer8.writeByte(((byte)-1)); if (abstractCollectionSerializer0.supportCodegenHook()) { java.util.List list4 = (java.util.List)abstractCollectionSerializer0.onCollectionWrite(memoryBuffer8, specialList); int value12 = list4.size(); if ((value12 > 0)) { int value13 = abstractCollectionSerializer0.writeTypeNullabilityHeader(memoryBuffer8, list4, java.lang.Object.class, object5ClassInfoHolder); boolean sameElementClass0 = (value13 & 8) != 8; boolean hasNull0 = (value13 & 2) == 2; if (sameElementClass0) { this.sameElementClassWrite1(value13, list4, value12, hasNull0, memoryBuffer8); } else { for (int i2 = 0; i2 < value12; i2+=1) { Object object10 = list4.get(i2); if (hasNull0) { if ((object10 == null)) { memoryBuffer8.writeByte(((byte)-3)); } else { memoryBuffer8.writeByte(((byte)-1)); this.writeClassAndObject2(memoryBuffer8, object10); } } else { this.writeClassAndObject3(memoryBuffer8, object10); } } } } } else { abstractCollectionSerializer0.write(memoryBuffer8, specialList); } } } private void differentTypeElemsRead(int value14, boolean value15, java.util.Collection collection, MemoryBuffer memoryBuffer9) { ClassResolver classResolver = this.classResolver; for (int i3 = 0; i3 < value14; i3+=1) { if (value15) { if ((memoryBuffer9.readByte() != ((byte)-3))) { collection.add(classResolver.readClassInfo(memoryBuffer9, object11ClassInfoHolder).getSerializer().read(memoryBuffer9)); } else { collection.add(null); } } else { collection.add(classResolver.readClassInfo(memoryBuffer9, object11ClassInfoHolder).getSerializer().read(memoryBuffer9)); } } } private void differentTypeElemsRead1(boolean value16, java.util.Collection collection1, int value17, MemoryBuffer memoryBuffer10) { ClassResolver classResolver = this.classResolver; for (int i4 = 0; i4 < value17; i4+=1) { if (value16) { if ((memoryBuffer10.readByte() != ((byte)-3))) { collection1.add(classResolver.readClassInfo(memoryBuffer10, object11ClassInfoHolder).getSerializer().read(memoryBuffer10)); } else { collection1.add(null); } } else { collection1.add(classResolver.readClassInfo(memoryBuffer10, object11ClassInfoHolder).getSerializer().read(memoryBuffer10)); } } } @Override public final void write(MemoryBuffer buffer, Object obj) { party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.SomeRecord someRecord3 = (party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.SomeRecord)obj; this.writeFields(buffer, someRecord3); this.writeFields1(buffer, someRecord3); } @Override public final Object read(MemoryBuffer buffer) { ClassResolver classResolver = this.classResolver; AbstractCollectionSerializer abstractCollectionSerializer0 = this.abstractCollectionSerializer0; Object object15; if ((buffer.readByte() != ((byte)-3))) { AbstractCollectionSerializer collectionSerializer = (AbstractCollectionSerializer)classResolver.readClassInfo(buffer, list5ClassInfoHolder).getSerializer(); Object object14; if (collectionSerializer.supportCodegenHook()) { java.util.Collection collection2 = collectionSerializer.newCollection(buffer); int size = collectionSerializer.getAndClearNumElements(); if ((size > 0)) { int flags = buffer.readByte(); boolean sameElementClass1 = (flags & 8) != 8; boolean hasNull1 = (flags & 2) == 2; if (sameElementClass1) { Serializer serializer1; if (((flags & 4) != 4)) { serializer1 = emptyObjectSerializer; } else { serializer1 = classResolver.readClassInfo(buffer, object11ClassInfoHolder).getSerializer(); } for (int i5 = 0; i5 < size; i5+=1) { if (hasNull1) { if ((buffer.readByte() != ((byte)-3))) { collection2.add(serializer1.read(buffer)); } else { collection2.add(null); } } else { collection2.add(serializer1.read(buffer)); } } } else { this.differentTypeElemsRead(size, hasNull1, collection2, buffer); } } Object object12 = collectionSerializer.onCollectionRead(collection2); object14 = object12; } else { Object object13 = collectionSerializer.read(buffer); object14 = object13; } object15 = object14; } else { object15 = null; } java.util.List castedValue = (java.util.List)object15; Object object19; if ((buffer.readByte() != ((byte)-3))) { Object object18; if (abstractCollectionSerializer0.supportCodegenHook()) { java.util.Collection collection3 = abstractCollectionSerializer0.newCollection(buffer); int size1 = abstractCollectionSerializer0.getAndClearNumElements(); if ((size1 > 0)) { int flags1 = buffer.readByte(); boolean sameElementClass2 = (flags1 & 8) != 8; boolean hasNull2 = (flags1 & 2) == 2; if (sameElementClass2) { Serializer serializer15; if (((flags1 & 4) != 4)) { serializer15 = emptyObjectSerializer; } else { serializer15 = classResolver.readClassInfo(buffer, object11ClassInfoHolder).getSerializer(); } for (int i6 = 0; i6 < size1; i6+=1) { if (hasNull2) { if ((buffer.readByte() != ((byte)-3))) { collection3.add(serializer15.read(buffer)); } else { collection3.add(null); } } else { collection3.add(serializer15.read(buffer)); } } } else { this.differentTypeElemsRead1(hasNull2, collection3, size1, buffer); } } Object object16 = abstractCollectionSerializer0.onCollectionRead(collection3); object18 = object16; } else { Object object17 = abstractCollectionSerializer0.read(buffer); object18 = object17; } object19 = object18; } else { object19 = null; } party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.EmptyList castedValue1 = (party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.EmptyList)object19; party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.SomeRecord someRecord4 = new party.iroiro.juicemacs.elisp.runtime.pdump.DumpUtilsTest.SomeRecord(castedValue1, castedValue); return someRecord4; } } ``` </details> ### Are you willing to submit a PR? - [ ] I'm willing to submit a PR! -- 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]
