From 2fcbab4926cd8abef3f9254da5714b335527ad95 Mon Sep 17 00:00:00 2001 From: sjahed Date: Wed, 17 Jun 2026 20:25:21 -0400 Subject: [PATCH 1/6] Added a test to reproduce the issue 2941. --- .../src/test/java/org/apache/fory/ForyCopyTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index bd4cd7d418..d7bb548f40 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -79,6 +79,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import org.apache.fory.collection.LazyMap; +import org.apache.fory.config.Language; import org.apache.fory.context.CopyContext; import org.apache.fory.context.ReadContext; import org.apache.fory.context.WriteContext; @@ -454,4 +455,15 @@ public void testComplexCollectionCopy() { assertEquals(fory.copy(collectionFields).toCanEqual(), collectionFields.toCanEqual()); } } + + @Test + public void testCopyNonSerializablePackage() { + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withRefCopy(true) + .requireClassRegistration(false) // get past the registration gate + .build(); + Package pkg = String.class.getPackage(); // java.lang + Package copy = fory.copy(pkg); // now expect the real bug + } } From c4f782b603be6c11129acfe4d36d3f5e4859df6e Mon Sep 17 00:00:00 2001 From: sjahed Date: Wed, 24 Jun 2026 18:39:29 -0400 Subject: [PATCH 2/6] added a test to reproduce issue 2941 --- .../src/main/java/org/apache/fory/resolver/ClassResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index fe58520121..2852c4927c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -207,7 +207,7 @@ */ @NotThreadSafe @SuppressWarnings({"rawtypes", "unchecked"}) -public class ClassResolver extends TypeResolver { +public class ClassResolver extends TypeResolver { private static final Logger LOG = LoggerFactory.getLogger(ClassResolver.class); public static final int NATIVE_START_ID = Types.BOUND; From 69cbf245b9aaa1a5dd9b338e43860bfeacca5e03 Mon Sep 17 00:00:00 2001 From: sjahed Date: Thu, 25 Jun 2026 09:25:37 -0400 Subject: [PATCH 3/6] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../apache/fory/resolver/ClassResolver.java | 37 +++--------- .../serializer/NonSerializableSerializer.java | 52 ++++++++++++++++ .../java/org/apache/fory/ForyCopyTest.java | 59 +++++++++++++++---- 3 files changed, 108 insertions(+), 40 deletions(-) create mode 100644 java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index 2852c4927c..b7b1bfb9f5 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -114,36 +114,10 @@ import org.apache.fory.platform.AndroidSupport; import org.apache.fory.platform.GraalvmSupport; import org.apache.fory.reflect.ReflectionUtils; -import org.apache.fory.serializer.ArraySerializers; -import org.apache.fory.serializer.BufferSerializers; +import org.apache.fory.serializer.*; import org.apache.fory.serializer.CodegenSerializer.LazyInitBeanSerializer; -import org.apache.fory.serializer.CompatibleSerializer; -import org.apache.fory.serializer.CopyOnlyObjectSerializer; -import org.apache.fory.serializer.EnumSerializer; -import org.apache.fory.serializer.ExceptionSerializers; -import org.apache.fory.serializer.ExternalizableSerializer; -import org.apache.fory.serializer.ForyCopyableSerializer; -import org.apache.fory.serializer.JavaSerializer; -import org.apache.fory.serializer.JdkProxySerializer; -import org.apache.fory.serializer.LambdaSerializer; -import org.apache.fory.serializer.LocaleSerializer; -import org.apache.fory.serializer.NoneSerializer; -import org.apache.fory.serializer.ObjectSerializer; -import org.apache.fory.serializer.OptionalSerializers; -import org.apache.fory.serializer.PrimitiveArraySerializers; -import org.apache.fory.serializer.PrimitiveSerializers; -import org.apache.fory.serializer.ReplaceResolveSerializer; -import org.apache.fory.serializer.SerializedLambdaSerializer; -import org.apache.fory.serializer.Serializer; -import org.apache.fory.serializer.Serializers; -import org.apache.fory.serializer.Shareable; -import org.apache.fory.serializer.SqlTimeSerializers; -import org.apache.fory.serializer.TimeSerializers; -import org.apache.fory.serializer.UnknownClass; import org.apache.fory.serializer.UnknownClass.UnknownEmptyStruct; import org.apache.fory.serializer.UnknownClass.UnknownStruct; -import org.apache.fory.serializer.UnknownClassSerializers; -import org.apache.fory.serializer.UnsignedSerializers; import org.apache.fory.serializer.collection.ChildContainerSerializers; import org.apache.fory.serializer.collection.CollectionLikeSerializer; import org.apache.fory.serializer.collection.CollectionSerializer; @@ -1498,9 +1472,12 @@ public Class getSerializerClass(Class cls, boolean code } } if (config.checkJdkClassSerializable()) { - if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", cls)); + if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))){ + // Issue #2941: previously threw UnsupportedOperationException here, which also broke + // Fory.copy() for non-serlializble JDK classes (e.g. java.lang.Package). Route to a + // serializer that still refuses binary serialization (write/read throw) but + // supports copy() via reflective field copy + return NonSerializableSerializer.class; } } if (ScalaTypes.SCALA_AVAILABLE && ReflectionUtils.isScalaSingletonObject(cls)) { diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java new file mode 100644 index 0000000000..d8bff538b7 --- /dev/null +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.serializer; + +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.resolver.TypeResolver; + +/** + * Serializer for non-Serializable JDK classes (e.g. {@code java.lang.Package}). + * + *

Binary serialization remains unsupported — {@link #write} and {@link #read} throw + * {@link UnsupportedOperationException}, preserving the prior behavior of {@code ClassResolver}'s + * JDK-serializable guard. However {@code copy} is supported via the reflection-based field copy + * inherited from {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs + * that transitively contain such classes (issue #2941). + */ +public final class NonSerializableSerializer extends AbstractObjectSerializer { + + public NonSerializableSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type); + } + + @Override + public void write(WriteContext writeContext, T value) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type)); + } + + @Override + public T read(ReadContext readContext) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type)); + } +} diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index d7bb548f40..7edafc2ee3 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -84,6 +84,7 @@ import org.apache.fory.context.ReadContext; import org.apache.fory.context.WriteContext; import org.apache.fory.exception.ForyException; +import org.apache.fory.exception.SerializationException; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.EnumSerializerTest; import org.apache.fory.serializer.EnumSerializerTest.EnumFoo; @@ -456,14 +457,52 @@ public void testComplexCollectionCopy() { } } - @Test - public void testCopyNonSerializablePackage() { - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .withRefCopy(true) - .requireClassRegistration(false) // get past the registration gate - .build(); - Package pkg = String.class.getPackage(); // java.lang - Package copy = fory.copy(pkg); // now expect the real bug - } + @Test + public void testCopyNonSerializableJdkClass(){ + // Issue #2941: copying an object that is (or contains) a non-serializable JDK class + // like java.lang.Package used to throw. It should now succeed. + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + Package pkg = String.class.getPackage(); + Package copy = fory.copy(pkg); + Assert.assertNotNull(copy); + } + + @Test + public void testCopyHashMapStillWorks(){ + // Regression for chaokunyang's concern (comments on issue #2941): HashMap keeps + //essential state (size, table) in transient field. The copy path copies all fields + // including transient, so this must still produce an equal map. + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + Map map = new HashMap<>(); + map.put("a", 1); + map.put("b", 2); + Map copy = fory.copy(map); + Assert.assertEquals(map, copy); + } + + @Test + public void testSerializeNonSerializableJdkClassStillThrows(){ + // Regression guard: we must not have weakened serialization. Serliazing a + // non-serliazable JDK class must still throw, jsut defered to write time. + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + try { + fory.serialize(String.class.getPackage()); + Assert.fail("Expected serialization of java.lang.Package to fail"); + } catch (SerializationException e) { + Assert.assertTrue(e.getCause() instanceof UnsupportedOperationException); + Assert.assertTrue(e.getMessage().contains("doesn't support serialization")); + } + } + } From 92f555e46f4f280272397bbd76af2719af0aff41 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sat, 27 Jun 2026 01:51:19 -0400 Subject: [PATCH 4/6] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../apache/fory/platform/GraalvmSupport.java | 2 ++ .../apache/fory/resolver/ClassResolver.java | 6 +++--- .../serializer/NonSerializableSerializer.java | 4 ++-- .../java/org/apache/fory/ForyCopyTest.java | 20 +++---------------- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java index de036f9759..c054f1cec3 100644 --- a/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/platform/GraalvmSupport.java @@ -39,6 +39,7 @@ import org.apache.fory.serializer.JavaSerializer; import org.apache.fory.serializer.JdkProxySerializer; import org.apache.fory.serializer.LambdaSerializer; +import org.apache.fory.serializer.NonSerializableSerializer; import org.apache.fory.serializer.ObjectSerializer; import org.apache.fory.serializer.ObjectStreamSerializer; import org.apache.fory.serializer.PrimitiveArraySerializers; @@ -140,6 +141,7 @@ public class GraalvmSupport { registerDefaultSerializerClass(ObjectStreamSerializer.class); registerDefaultSerializerClass(JavaSerializer.class); registerDefaultSerializerClass(ObjectSerializer.class); + registerDefaultSerializerClass(NonSerializableSerializer.class); } /** Returns true if current process is running in graalvm native image build stage. */ diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index b7b1bfb9f5..c4b31a14c3 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -181,7 +181,7 @@ */ @NotThreadSafe @SuppressWarnings({"rawtypes", "unchecked"}) -public class ClassResolver extends TypeResolver { +public class ClassResolver extends TypeResolver { private static final Logger LOG = LoggerFactory.getLogger(ClassResolver.class); public static final int NATIVE_START_ID = Types.BOUND; @@ -1474,9 +1474,9 @@ public Class getSerializerClass(Class cls, boolean code if (config.checkJdkClassSerializable()) { if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))){ // Issue #2941: previously threw UnsupportedOperationException here, which also broke - // Fory.copy() for non-serlializble JDK classes (e.g. java.lang.Package). Route to a + // Fory.copy() for non-serializable JDK classes (e.g. java.lang.Package). Route to a // serializer that still refuses binary serialization (write/read throw) but - // supports copy() via reflective field copy + // supports field copy via AbstractObjectSerializer return NonSerializableSerializer.class; } } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index d8bff538b7..0ead42eb6a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -28,8 +28,8 @@ * *

Binary serialization remains unsupported — {@link #write} and {@link #read} throw * {@link UnsupportedOperationException}, preserving the prior behavior of {@code ClassResolver}'s - * JDK-serializable guard. However {@code copy} is supported via the reflection-based field copy - * inherited from {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs + * JDK-serializable guard. However {@code copy} is supported via the field copy via + * {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs * that transitively contain such classes (issue #2941). */ public final class NonSerializableSerializer extends AbstractObjectSerializer { diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 7edafc2ee3..393347f4cb 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -469,28 +469,14 @@ public void testCopyNonSerializableJdkClass(){ Package pkg = String.class.getPackage(); Package copy = fory.copy(pkg); Assert.assertNotNull(copy); + Assert.assertEquals(copy.getName(), pkg.getName()); } - @Test - public void testCopyHashMapStillWorks(){ - // Regression for chaokunyang's concern (comments on issue #2941): HashMap keeps - //essential state (size, table) in transient field. The copy path copies all fields - // including transient, so this must still produce an equal map. - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .requireClassRegistration(false) - .build(); - Map map = new HashMap<>(); - map.put("a", 1); - map.put("b", 2); - Map copy = fory.copy(map); - Assert.assertEquals(map, copy); - } @Test public void testSerializeNonSerializableJdkClassStillThrows(){ - // Regression guard: we must not have weakened serialization. Serliazing a - // non-serliazable JDK class must still throw, jsut defered to write time. + // Regression guard: we must not have weakened serialization. Serializing a + // non-serializable JDK class must still throw, just deferred to write time. Fory fory = Fory.builder() .withLanguage(Language.JAVA) From 80d3b784dfde701203667111c04d5e478e90fbc4 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sat, 27 Jun 2026 02:26:30 -0400 Subject: [PATCH 5/6] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../apache/fory/resolver/ClassResolver.java | 31 +++++++++++++++++-- .../serializer/NonSerializableSerializer.java | 27 ++++++++-------- .../java/org/apache/fory/ForyCopyTest.java | 24 +++++++------- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index c4b31a14c3..f0bec1585b 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -114,10 +114,37 @@ import org.apache.fory.platform.AndroidSupport; import org.apache.fory.platform.GraalvmSupport; import org.apache.fory.reflect.ReflectionUtils; -import org.apache.fory.serializer.*; +import org.apache.fory.serializer.ArraySerializers; +import org.apache.fory.serializer.BufferSerializers; +import org.apache.fory.serializer.CompatibleSerializer; +import org.apache.fory.serializer.CopyOnlyObjectSerializer; +import org.apache.fory.serializer.EnumSerializer; +import org.apache.fory.serializer.ExceptionSerializers; +import org.apache.fory.serializer.ExternalizableSerializer; +import org.apache.fory.serializer.ForyCopyableSerializer; +import org.apache.fory.serializer.JavaSerializer; +import org.apache.fory.serializer.JdkProxySerializer; +import org.apache.fory.serializer.LambdaSerializer; +import org.apache.fory.serializer.LocaleSerializer; +import org.apache.fory.serializer.NonSerializableSerializer; import org.apache.fory.serializer.CodegenSerializer.LazyInitBeanSerializer; +import org.apache.fory.serializer.NoneSerializer; +import org.apache.fory.serializer.ObjectSerializer; +import org.apache.fory.serializer.OptionalSerializers; +import org.apache.fory.serializer.PrimitiveArraySerializers; +import org.apache.fory.serializer.PrimitiveSerializers; +import org.apache.fory.serializer.ReplaceResolveSerializer; +import org.apache.fory.serializer.SerializedLambdaSerializer; +import org.apache.fory.serializer.Serializer; +import org.apache.fory.serializer.Serializers; +import org.apache.fory.serializer.Shareable; +import org.apache.fory.serializer.SqlTimeSerializers; +import org.apache.fory.serializer.TimeSerializers; +import org.apache.fory.serializer.UnknownClass; import org.apache.fory.serializer.UnknownClass.UnknownEmptyStruct; import org.apache.fory.serializer.UnknownClass.UnknownStruct; +import org.apache.fory.serializer.UnknownClassSerializers; +import org.apache.fory.serializer.UnsignedSerializers; import org.apache.fory.serializer.collection.ChildContainerSerializers; import org.apache.fory.serializer.collection.CollectionLikeSerializer; import org.apache.fory.serializer.collection.CollectionSerializer; @@ -1472,7 +1499,7 @@ public Class getSerializerClass(Class cls, boolean code } } if (config.checkJdkClassSerializable()) { - if (cls.getName().startsWith("java") && !(Serializable.class.isAssignableFrom(cls))){ + if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)){ // Issue #2941: previously threw UnsupportedOperationException here, which also broke // Fory.copy() for non-serializable JDK classes (e.g. java.lang.Package). Route to a // serializer that still refuses binary serialization (write/read throw) but diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index 0ead42eb6a..bc3606d50a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -33,20 +33,19 @@ * that transitively contain such classes (issue #2941). */ public final class NonSerializableSerializer extends AbstractObjectSerializer { + public NonSerializableSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type); + } - public NonSerializableSerializer(TypeResolver typeResolver, Class type) { - super(typeResolver, type); - } + @Override + public void write(WriteContext writeContext, T value) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type.getName())); + } - @Override - public void write(WriteContext writeContext, T value) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type)); - } - - @Override - public T read(ReadContext readContext) { - throw new UnsupportedOperationException( - String.format("Class %s doesn't support serialization.", type)); - } + @Override + public T read(ReadContext readContext) { + throw new UnsupportedOperationException( + String.format("Class %s doesn't support serialization.", type.getName())); + } } diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index 393347f4cb..b0035b7d8b 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -459,22 +459,22 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass(){ - // Issue #2941: copying an object that is (or contains) a non-serializable JDK class - // like java.lang.Package used to throw. It should now succeed. - - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .requireClassRegistration(false) - .build(); - Package pkg = String.class.getPackage(); - Package copy = fory.copy(pkg); - Assert.assertNotNull(copy); - Assert.assertEquals(copy.getName(), pkg.getName()); + // Issue #2941: copying an object that is (or contains) a non-serializable JDK class + // like java.lang.Package used to throw. It should now succeed. + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(false) + .build(); + Package pkg = String.class.getPackage(); + Package copy = fory.copy(pkg); + Assert.assertNotNull(copy); + Assert.assertEquals(copy.getName(), pkg.getName()); } @Test - public void testSerializeNonSerializableJdkClassStillThrows(){ + public void testSerializeNonSerializableJdkClassStillThrows() { // Regression guard: we must not have weakened serialization. Serializing a // non-serializable JDK class must still throw, just deferred to write time. From efd68664897816fe281b2c3f6037ede4b7da7351 Mon Sep 17 00:00:00 2001 From: sjahed Date: Sun, 28 Jun 2026 01:21:00 -0400 Subject: [PATCH 6/6] fix(java): support copy of non-serializable JDK classes such as java.lang.Package --- .../org/apache/fory/resolver/ClassResolver.java | 4 +--- .../fory/serializer/NonSerializableSerializer.java | 13 +++++++------ .../src/test/java/org/apache/fory/ForyCopyTest.java | 5 +---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index f0bec1585b..09eed73d36 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -1500,9 +1500,7 @@ public Class getSerializerClass(Class cls, boolean code } if (config.checkJdkClassSerializable()) { if (cls.getName().startsWith("java") && !Serializable.class.isAssignableFrom(cls)){ - // Issue #2941: previously threw UnsupportedOperationException here, which also broke - // Fory.copy() for non-serializable JDK classes (e.g. java.lang.Package). Route to a - // serializer that still refuses binary serialization (write/read throw) but + // Route to a serializer that still refuses binary serialization (write/read throw) but // supports field copy via AbstractObjectSerializer return NonSerializableSerializer.class; } diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java index bc3606d50a..8fa9d45917 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/NonSerializableSerializer.java @@ -24,13 +24,14 @@ import org.apache.fory.resolver.TypeResolver; /** - * Serializer for non-Serializable JDK classes (e.g. {@code java.lang.Package}). + * Serializer for non-Serializable JDK classes. Binary serialization is unsupported — + * {@link #write}/{@link #read} throw {@link UnsupportedOperationException} — while copy + * reuses {@link AbstractObjectSerializer}'s field-copy implementation. * - *

Binary serialization remains unsupported — {@link #write} and {@link #read} throw - * {@link UnsupportedOperationException}, preserving the prior behavior of {@code ClassResolver}'s - * JDK-serializable guard. However {@code copy} is supported via the field copy via - * {@link AbstractObjectSerializer}, so {@code Fory.copy()} can handle object graphs - * that transitively contain such classes (issue #2941). + *

Distinct from {@link CopyOnlyObjectSerializer}, which blocks serialization for + * registration-security reasons (remediable by registering the class). Here serialization + * is intrinsically unsupported and not remediable by registration, so the failure semantics + * differ deliberately. */ public final class NonSerializableSerializer extends AbstractObjectSerializer { public NonSerializableSerializer(TypeResolver typeResolver, Class type) { diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java index b0035b7d8b..68442e9d4d 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyCopyTest.java @@ -459,8 +459,6 @@ public void testComplexCollectionCopy() { @Test public void testCopyNonSerializableJdkClass(){ - // Issue #2941: copying an object that is (or contains) a non-serializable JDK class - // like java.lang.Package used to throw. It should now succeed. Fory fory = Fory.builder() .withLanguage(Language.JAVA) @@ -475,8 +473,7 @@ public void testCopyNonSerializableJdkClass(){ @Test public void testSerializeNonSerializableJdkClassStillThrows() { - // Regression guard: we must not have weakened serialization. Serializing a - // non-serializable JDK class must still throw, just deferred to write time. + // Serializing anon-serializable JDK class must still throw, just deferred to write time. Fory fory = Fory.builder() .withLanguage(Language.JAVA)