From ce38d2adc730d92bf48c1bd5b7f289f84c41a1f2 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 10:33:56 +0800 Subject: [PATCH 01/46] feat: add fory-graalvm-feature --- integration_tests/graalvm_tests/pom.xml | 5 + .../fory/graalvm/FeatureTestExample.java | 127 ++++++++ .../java/org/apache/fory/graalvm/Main.java | 1 + .../src/main/java/org/apache/fory/Fory.java | 22 ++ .../apache/fory/reflect/ObjectCreators.java | 25 +- java/fory-graalvm-feature/pom.xml | 53 ++++ .../graalvm/feature/ForyGraalVMFeature.java | 78 +++++ .../org.graalvm.nativeimage.hosted.Feature | 1 + .../feature/GraalVMIntegrationTest.java | 289 ++++++++++++++++++ java/pom.xml | 1 + 10 files changed, 600 insertions(+), 2 deletions(-) create mode 100644 integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java create mode 100644 java/fory-graalvm-feature/pom.xml create mode 100644 java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java create mode 100644 java/fory-graalvm-feature/src/main/resources/META-INF/services/org.graalvm.nativeimage.hosted.Feature create mode 100644 java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 94a0d83d32..16b0a72b91 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -59,6 +59,11 @@ fory-core ${project.version} + + org.apache.fory + fory-graalvm-feature + ${project.version} + diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java new file mode 100644 index 0000000000..d26e7cb20b --- /dev/null +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -0,0 +1,127 @@ +/* + * 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.graalvm; + +import org.apache.fory.Fory; +import org.apache.fory.config.Language; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + + +public class FeatureTestExample { + + // Test class with private constructor (problematic for creation) + public static class ProblematicClass { + private String value; + + private ProblematicClass() { + // Private constructor + } + + public ProblematicClass(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + // Test interface for proxy + public interface TestInterface { + String getValue(); + void setValue(String value); + } + + // Simple invocation handler + public static class TestInvocationHandler implements InvocationHandler { + private String value; + + public TestInvocationHandler(String value) { + this.value = value; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("getValue".equals(method.getName())) { + return value; + } else if ("setValue".equals(method.getName())) { + value = (String) args[0]; + return null; + } + return null; + } + } + + public static void main(String[] args) { + System.out.println("Testing Fory GraalVM Feature..."); + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withRefTracking(true) + .build(); + + // Register problematic class + fory.register(ProblematicClass.class); + + // Register proxy interface + Fory.addProxyInterface(TestInterface.class); + + try { + // Test 1: Serialize/deserialize problematic class + ProblematicClass original = new ProblematicClass("test-value"); + byte[] serialized = fory.serialize(original); + ProblematicClass deserialized = (ProblematicClass) fory.deserialize(serialized); + + if (!"test-value".equals(deserialized.getValue())) { + throw new RuntimeException("Problematic class test failed"); + } + System.out.println("✓ Problematic class serialization test passed"); + + // Test 2: Serialize/deserialize proxy object + TestInterface proxy = (TestInterface) Proxy.newProxyInstance( + TestInterface.class.getClassLoader(), + new Class[]{TestInterface.class}, + new TestInvocationHandler("proxy-value") + ); + + byte[] proxySerialised = fory.serialize(proxy); + TestInterface deserializedProxy = (TestInterface) fory.deserialize(proxySerialised); + + if (!"proxy-value".equals(deserializedProxy.getValue())) { + throw new RuntimeException("Proxy test failed"); + } + System.out.println("✓ Proxy serialization test passed"); + + System.out.println("All GraalVM Feature tests passed!"); + + } catch (Exception e) { + System.err.println("GraalVM Feature test failed: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } +} \ No newline at end of file diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java index d1e1591b83..74a02e5c45 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Main.java @@ -40,5 +40,6 @@ public static void main(String[] args) throws Throwable { Benchmark.main(args); CollectionExample.main(args); ArrayExample.main(args); + FeatureTestExample.main(args); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index 43b7b74dfb..ab077fca7c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -25,8 +25,11 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.concurrent.NotThreadSafe; @@ -89,6 +92,10 @@ public final class Fory implements BaseFory { private static final Logger LOG = LoggerFactory.getLogger(Fory.class); + // Static collections for GraalVM Feature support + private static final Set> REGISTERED_CLASSES = ConcurrentHashMap.newKeySet(); + private static final Set> PROXY_INTERFACES = ConcurrentHashMap.newKeySet(); + public static final byte NULL_FLAG = -3; // This flag indicates that object is a not-null value. // We don't use another byte to indicate REF, so that we can save one byte. @@ -177,11 +184,13 @@ public Fory(ForyBuilder builder, ClassLoader classLoader) { @Override public void register(Class cls) { _getTypeResolver().register(cls); + REGISTERED_CLASSES.add(cls); } @Override public void register(Class cls, int id) { _getTypeResolver().register(cls, id); + REGISTERED_CLASSES.add(cls); } @Deprecated @@ -213,6 +222,7 @@ public void register(Class cls, String typeName) { public void register(Class cls, String namespace, String typeName) { _getTypeResolver().register(cls, namespace, typeName); + REGISTERED_CLASSES.add(cls); } @Override @@ -1764,4 +1774,16 @@ public MetaCompressor getMetaCompressor() { public static ForyBuilder builder() { return new ForyBuilder(); } + + public static Set> getRegisteredClasses() { + return Collections.unmodifiableSet(REGISTERED_CLASSES); + } + + public static Set> getProxyInterfaces() { + return Collections.unmodifiableSet(PROXY_INTERFACES); + } + + public static void addProxyInterface(Class anInterface) { + PROXY_INTERFACES.add(anInterface); + } } diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java index 9b61dbb98d..2fdbbde7af 100644 --- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java +++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java @@ -77,16 +77,37 @@ public static ObjectCreator getObjectCreator(Class type) { return (ObjectCreator) cache.get(type, () -> creategetObjectCreator(type)); } + /** + * Checks if a class is problematic for object creation and requires special handling in GraalVM. + * + *

A class is considered problematic if it lacks a public no-arg constructor and would + * typically require ReflectionFactory or unsafe allocation for instantiation. + * + * @param type the class to check + * @return true if the class is problematic for creation, false otherwise + */ + public static boolean isProblematicForCreation(Class type) { + if (type.isInterface() || java.lang.reflect.Modifier.isAbstract(type.getModifiers()) || type.isArray()) { + return false; + } + try { + type.getConstructor(); + return false; + } catch (NoSuchMethodException e) { + return true; + } + } + private static ObjectCreator creategetObjectCreator(Class type) { if (RecordUtils.isRecord(type)) { return new RecordObjectCreator<>(type); } Constructor noArgConstructor = ReflectionUtils.getNoArgConstructor(type); - if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE && Platform.JAVA_VERSION >= 25) { + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { if (noArgConstructor != null) { return new DeclaredNoArgCtrObjectCreator<>(type); } else { - return new ParentNoArgCtrObjectCreator<>(type); + return new UnsafeObjectCreator<>(type); } } if (noArgConstructor == null) { diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml new file mode 100644 index 0000000000..4bbbcb0039 --- /dev/null +++ b/java/fory-graalvm-feature/pom.xml @@ -0,0 +1,53 @@ + + + + fory-parent + org.apache.fory + 0.13.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + fory-graalvm-feature + Fory GraalVM Feature + + + + org.apache.fory + fory-core + ${project.version} + + + org.graalvm.sdk + graal-sdk + 23.0.0 + provided + + + junit + junit + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.2.0 + + + copy-dependencies + package + + copy-dependencies + + + + + + + \ No newline at end of file diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java new file mode 100644 index 0000000000..593ad63234 --- /dev/null +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -0,0 +1,78 @@ +/* + * 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.graalvm.feature; + +import org.apache.fory.Fory; +import org.apache.fory.reflect.ObjectCreators; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeReflection; + +import java.lang.reflect.Field; +import java.util.Set; + +/** + * GraalVM Feature for Apache Fory serialization framework. + * + *

This feature automatically registers necessary metadata for GraalVM native image compilation + * to ensure Fory serialization works correctly at runtime. It handles: + *

    + *
  • Registering problematic classes for unsafe allocation
  • + *
  • Registering field reflection access for serialization
  • + *
  • Registering proxy interfaces for dynamic proxy creation
  • + *
+ */ +public class ForyGraalVMFeature implements Feature { + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + // Process all registered classes + Set> registeredClasses = Fory.getRegisteredClasses(); + for (Class clazz : registeredClasses) { + handleForyClass(clazz); + } + + // Process all proxy interfaces + Set> proxyInterfaces = Fory.getProxyInterfaces(); + for (Class proxyInterface : proxyInterfaces) { + RuntimeReflection.register(proxyInterface); + RuntimeReflection.register(proxyInterface.getMethods()); + } + } + + private void handleForyClass(Class clazz) { + if (ObjectCreators.isProblematicForCreation(clazz)) { + // Register for unsafe allocation + RuntimeReflection.registerForReflectiveInstantiation(clazz); + + // Register all fields for reflection access + for (Field field : clazz.getDeclaredFields()) { + RuntimeReflection.register(field); + } + } + + // Always register the class itself for reflection + RuntimeReflection.register(clazz); + } + + @Override + public String getDescription() { + return "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation."; + } +} \ No newline at end of file diff --git a/java/fory-graalvm-feature/src/main/resources/META-INF/services/org.graalvm.nativeimage.hosted.Feature b/java/fory-graalvm-feature/src/main/resources/META-INF/services/org.graalvm.nativeimage.hosted.Feature new file mode 100644 index 0000000000..72cf9f8397 --- /dev/null +++ b/java/fory-graalvm-feature/src/main/resources/META-INF/services/org.graalvm.nativeimage.hosted.Feature @@ -0,0 +1 @@ +org.apache.fory.graalvm.feature.ForyGraalVMFeature \ No newline at end of file diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java new file mode 100644 index 0000000000..0946c138d1 --- /dev/null +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java @@ -0,0 +1,289 @@ +/* + * 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.graalvm.feature; + +import org.apache.fory.Fory; +import org.apache.fory.config.Language; +import org.apache.fory.reflect.ObjectCreator; +import org.apache.fory.reflect.ObjectCreators; + + +public class GraalVMIntegrationTest { + + // Test classes for ObjectCreator functionality + public static class SimpleClass { + private String name; + private int value; + + public SimpleClass() {} + + public SimpleClass(String name, int value) { + this.name = name; + this.value = value; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public int getValue() { return value; } + public void setValue(int value) { this.value = value; } + + @Override + public String toString() { + return "SimpleClass{name='" + name + "', value=" + value + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SimpleClass)) return false; + SimpleClass that = (SimpleClass) o; + return value == that.value && + (name != null ? name.equals(that.name) : that.name == null); + } + } + + public static abstract class AbstractParent { + // No no-arg constructor - forces use of special ObjectCreator + } + + public static class ProblematicChild extends AbstractParent { + private String data; + + public ProblematicChild(String data) { + this.data = data; + } + + public String getData() { return data; } + public void setData(String data) { this.data = data; } + + @Override + public String toString() { + return "ProblematicChild{data='" + data + "'}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ProblematicChild)) return false; + ProblematicChild that = (ProblematicChild) o; + return data != null ? data.equals(that.data) : that.data == null; + } + } + + public static class AnotherProblematicChild extends AbstractParent { + private int number; + + public AnotherProblematicChild(int number) { + this.number = number; + } + + public int getNumber() { return number; } + public void setNumber(int number) { this.number = number; } + + @Override + public String toString() { + return "AnotherProblematicChild{number=" + number + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AnotherProblematicChild)) return false; + AnotherProblematicChild that = (AnotherProblematicChild) o; + return number == that.number; + } + } + + public static void main(String[] args) { + System.out.println("=== GraalVM Integration Test ==="); + System.out.println("Java version: " + System.getProperty("java.version")); + System.out.println("Java vendor: " + System.getProperty("java.vendor")); + System.out.println("Runtime: " + System.getProperty("java.runtime.name")); + System.out.println(); + + GraalVMIntegrationTest test = new GraalVMIntegrationTest(); + + try { + // Test 1: Basic Fory serialization + test.testBasicSerialization(); + + // Test 2: GraalVM Feature functionality + test.testGraalVMFeature(); + + // Test 3: ObjectCreator tests + test.testObjectCreators(); + + // Test 4: Problematic class handling + test.testProblematicClasses(); + + // Test 5: ReflectionFactory availability + test.testReflectionFactoryAvailability(); + + System.out.println("\n✅ All tests passed successfully!"); + + } catch (Exception e) { + System.err.println("\n❌ Test failed:"); + e.printStackTrace(); + System.exit(1); + } + } + + public void testBasicSerialization() { + System.out.println("--- Test 1: Basic Fory Serialization ---"); + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(true) + .build(); + + fory.register(SimpleClass.class); + + SimpleClass original = new SimpleClass("test", 42); + System.out.println("Original: " + original); + + byte[] serialized = fory.serialize(original); + System.out.println("Serialized " + serialized.length + " bytes"); + + SimpleClass deserialized = (SimpleClass) fory.deserialize(serialized); + System.out.println("Deserialized: " + deserialized); + + if (!original.equals(deserialized)) { + throw new RuntimeException("Serialization test failed: objects not equal"); + } + + System.out.println("✓ Basic serialization test passed\n"); + } + + public void testGraalVMFeature() { + System.out.println("--- Test 2: GraalVM Feature ---"); + + ForyGraalVMFeature feature = new ForyGraalVMFeature(); + System.out.println("Feature description: " + feature.getDescription()); + + Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .requireClassRegistration(true) + .build(); + + int initialSize = Fory.getRegisteredClasses().size(); + System.out.println("Initial registered classes count: " + initialSize); + + fory.register(SimpleClass.class); + + int newSize = Fory.getRegisteredClasses().size(); + System.out.println("After registration count: " + newSize); + System.out.println("Contains SimpleClass: " + Fory.getRegisteredClasses().contains(SimpleClass.class)); + + if (!Fory.getRegisteredClasses().contains(SimpleClass.class)) { + throw new RuntimeException("Class registration tracking failed"); + } + + // Note: SimpleClass may have been registered in previous test + if (newSize < initialSize) { + throw new RuntimeException("Registered classes count decreased unexpectedly"); + } + + System.out.println("✓ GraalVM Feature test passed\n"); + } + + public void testObjectCreators() { + System.out.println("--- Test 3: ObjectCreator Tests ---"); + + // Test simple class with no-arg constructor + ObjectCreator simpleCreator = ObjectCreators.getObjectCreator(SimpleClass.class); + System.out.println("SimpleClass ObjectCreator: " + simpleCreator.getClass().getSimpleName()); + + SimpleClass simpleInstance = simpleCreator.newInstance(); + if (simpleInstance == null) { + throw new RuntimeException("SimpleClass instance creation failed"); + } + + if (!simpleInstance.getClass().equals(SimpleClass.class)) { + throw new RuntimeException("SimpleClass instance type incorrect: " + simpleInstance.getClass()); + } + + System.out.println("✓ SimpleClass ObjectCreator test passed"); + + // Test problematic class detection + boolean isProblematic = ObjectCreators.isProblematicForCreation(ProblematicChild.class); + System.out.println("ProblematicChild is problematic: " + isProblematic); + + if (!isProblematic) { + throw new RuntimeException("ProblematicChild should be detected as problematic"); + } + + System.out.println("✓ ObjectCreator tests passed\n"); + } + + public void testProblematicClasses() { + System.out.println("--- Test 4: Problematic Class Handling ---"); + + // Test ProblematicChild + ObjectCreator problematicCreator = ObjectCreators.getObjectCreator(ProblematicChild.class); + System.out.println("ProblematicChild ObjectCreator: " + problematicCreator.getClass().getSimpleName()); + + ProblematicChild problematicInstance = problematicCreator.newInstance(); + if (problematicInstance == null) { + throw new RuntimeException("ProblematicChild instance creation failed"); + } + + if (!problematicInstance.getClass().equals(ProblematicChild.class)) { + throw new RuntimeException("ProblematicChild instance type incorrect: " + problematicInstance.getClass()); + } + + System.out.println("✓ ProblematicChild creation successful: " + problematicInstance); + + // Test AnotherProblematicChild + ObjectCreator anotherCreator = ObjectCreators.getObjectCreator(AnotherProblematicChild.class); + System.out.println("AnotherProblematicChild ObjectCreator: " + anotherCreator.getClass().getSimpleName()); + + AnotherProblematicChild anotherInstance = anotherCreator.newInstance(); + if (anotherInstance == null) { + throw new RuntimeException("AnotherProblematicChild instance creation failed"); + } + + if (!anotherInstance.getClass().equals(AnotherProblematicChild.class)) { + throw new RuntimeException("AnotherProblematicChild instance type incorrect: " + anotherInstance.getClass()); + } + + System.out.println("✓ AnotherProblematicChild creation successful: " + anotherInstance); + System.out.println("✓ Problematic class handling test passed\n"); + } + + public void testReflectionFactoryAvailability() { + System.out.println("--- Test 5: ReflectionFactory Availability ---"); + + try { + Class reflectionFactoryClass = Class.forName("jdk.internal.reflect.ReflectionFactory"); + System.out.println("✓ ReflectionFactory class found: " + reflectionFactoryClass.getName()); + + java.lang.reflect.Method getFactoryMethod = reflectionFactoryClass.getMethod("getReflectionFactory"); + Object factory = getFactoryMethod.invoke(null); + System.out.println("✓ ReflectionFactory instance obtained: " + factory); + + } catch (ClassNotFoundException e) { + System.out.println("⚠ ReflectionFactory not found - may indicate GraalVM Native Image: " + e.getMessage()); + } catch (Exception e) { + System.out.println("⚠ Error accessing ReflectionFactory: " + e.getMessage()); + } + + System.out.println("✓ ReflectionFactory availability test completed\n"); + } +} \ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml index f31ca93240..a6b0e0e7ca 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -62,6 +62,7 @@ fory-core fory-extensions fory-test-core + fory-graalvm-feature From c8be5a37964fa73bb10f46c62a9a1eabcd594e39 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 11:14:22 +0800 Subject: [PATCH 02/46] fix: ci bug --- java/fory-graalvm-feature/pom.xml | 2 +- .../graalvm/feature/ForyGraalVMFeature.java | 76 ++--- .../feature/ForyGraalVMFeatureTest.java | 116 +++++++ .../feature/GraalVMIntegrationTest.java | 289 ------------------ java/pom.xml | 4 +- 5 files changed, 157 insertions(+), 330 deletions(-) create mode 100644 java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java delete mode 100644 java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index 4bbbcb0039..5ae0ed0693 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -22,7 +22,7 @@ org.graalvm.sdk graal-sdk - 23.0.0 + 22.3.0 provided diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 593ad63234..4487aff648 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -19,60 +19,60 @@ package org.apache.fory.graalvm.feature; +import java.lang.reflect.Field; +import java.util.Set; import org.apache.fory.Fory; import org.apache.fory.reflect.ObjectCreators; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; -import java.lang.reflect.Field; -import java.util.Set; - /** * GraalVM Feature for Apache Fory serialization framework. - * + * *

This feature automatically registers necessary metadata for GraalVM native image compilation * to ensure Fory serialization works correctly at runtime. It handles: + * *

    - *
  • Registering problematic classes for unsafe allocation
  • - *
  • Registering field reflection access for serialization
  • - *
  • Registering proxy interfaces for dynamic proxy creation
  • + *
  • Registering problematic classes for unsafe allocation + *
  • Registering field reflection access for serialization + *
  • Registering proxy interfaces for dynamic proxy creation *
*/ public class ForyGraalVMFeature implements Feature { - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - // Process all registered classes - Set> registeredClasses = Fory.getRegisteredClasses(); - for (Class clazz : registeredClasses) { - handleForyClass(clazz); - } - - // Process all proxy interfaces - Set> proxyInterfaces = Fory.getProxyInterfaces(); - for (Class proxyInterface : proxyInterfaces) { - RuntimeReflection.register(proxyInterface); - RuntimeReflection.register(proxyInterface.getMethods()); - } + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + // Process all registered classes + Set> registeredClasses = Fory.getRegisteredClasses(); + for (Class clazz : registeredClasses) { + handleForyClass(clazz); } - private void handleForyClass(Class clazz) { - if (ObjectCreators.isProblematicForCreation(clazz)) { - // Register for unsafe allocation - RuntimeReflection.registerForReflectiveInstantiation(clazz); - - // Register all fields for reflection access - for (Field field : clazz.getDeclaredFields()) { - RuntimeReflection.register(field); - } - } - - // Always register the class itself for reflection - RuntimeReflection.register(clazz); + // Process all proxy interfaces + Set> proxyInterfaces = Fory.getProxyInterfaces(); + for (Class proxyInterface : proxyInterfaces) { + RuntimeReflection.register(proxyInterface); + RuntimeReflection.register(proxyInterface.getMethods()); } + } + + private void handleForyClass(Class clazz) { + if (ObjectCreators.isProblematicForCreation(clazz)) { + // Register for unsafe allocation + RuntimeReflection.registerForReflectiveInstantiation(clazz); - @Override - public String getDescription() { - return "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation."; + // Register all fields for reflection access + for (Field field : clazz.getDeclaredFields()) { + RuntimeReflection.register(field); + } } -} \ No newline at end of file + + // Always register the class itself for reflection + RuntimeReflection.register(clazz); + } + + @Override + public String getDescription() { + return "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation."; + } +} diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java new file mode 100644 index 0000000000..a6331d5d27 --- /dev/null +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -0,0 +1,116 @@ +/* + * 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.graalvm.feature; + +import static org.junit.Assert.*; + +import java.util.Set; +import org.apache.fory.Fory; +import org.apache.fory.reflect.ObjectCreators; +import org.junit.Before; +import org.junit.Test; + +public class ForyGraalVMFeatureTest { + + private ForyGraalVMFeature feature; + + public static class TestClass { + private String field1; + private int field2; + } + + public static class ProblematicClass { + private String data; + + private ProblematicClass() {} + } + + @Before + public void setUp() { + feature = new ForyGraalVMFeature(); + } + + @Test + public void testGetDescription() { + String description = feature.getDescription(); + assertEquals( + "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation.", + description); + } + + @Test + public void testFeatureCreation() { + assertNotNull(feature); + assertTrue(feature instanceof org.graalvm.nativeimage.hosted.Feature); + } + + @Test + public void testObjectCreatorsIntegration() { + // Test that ObjectCreators.isProblematicForCreation works correctly + boolean isProblematic = ObjectCreators.isProblematicForCreation(ProblematicClass.class); + assertTrue("ProblematicClass should be detected as problematic", isProblematic); + + boolean isNotProblematic = ObjectCreators.isProblematicForCreation(TestClass.class); + assertFalse("TestClass should not be detected as problematic", isNotProblematic); + } + + @Test + public void testForyStaticMethods() { + // Test that Fory static methods are accessible + Set> registeredClasses = Fory.getRegisteredClasses(); + assertNotNull("Registered classes should not be null", registeredClasses); + + Set> proxyInterfaces = Fory.getProxyInterfaces(); + assertNotNull("Proxy interfaces should not be null", proxyInterfaces); + } + + @Test + public void testBeforeAnalysisDoesNotThrow() { + // Test that beforeAnalysis method can be called without throwing exceptions + // and the feature can be instantiated + try { + java.lang.reflect.Method beforeAnalysisMethod = + ForyGraalVMFeature.class.getMethod( + "beforeAnalysis", org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess.class); + assertNotNull("beforeAnalysis method should exist", beforeAnalysisMethod); + } catch (NoSuchMethodException e) { + fail("beforeAnalysis method should exist: " + e.getMessage()); + } + } + + @Test + public void testHandleForyClassMethod() { + // Test that the private handleForyClass method exists + try { + java.lang.reflect.Method handleMethod = + ForyGraalVMFeature.class.getDeclaredMethod("handleForyClass", Class.class); + assertNotNull("handleForyClass method should exist", handleMethod); + + // Just verify the method exists, don't try to invoke it since it calls GraalVM APIs + assertEquals( + "Method should be private", + java.lang.reflect.Modifier.PRIVATE, + handleMethod.getModifiers() & java.lang.reflect.Modifier.PRIVATE); + + } catch (NoSuchMethodException e) { + fail("handleForyClass method should exist: " + e.getMessage()); + } + } +} diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java deleted file mode 100644 index 0946c138d1..0000000000 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/GraalVMIntegrationTest.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * 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.graalvm.feature; - -import org.apache.fory.Fory; -import org.apache.fory.config.Language; -import org.apache.fory.reflect.ObjectCreator; -import org.apache.fory.reflect.ObjectCreators; - - -public class GraalVMIntegrationTest { - - // Test classes for ObjectCreator functionality - public static class SimpleClass { - private String name; - private int value; - - public SimpleClass() {} - - public SimpleClass(String name, int value) { - this.name = name; - this.value = value; - } - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public int getValue() { return value; } - public void setValue(int value) { this.value = value; } - - @Override - public String toString() { - return "SimpleClass{name='" + name + "', value=" + value + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SimpleClass)) return false; - SimpleClass that = (SimpleClass) o; - return value == that.value && - (name != null ? name.equals(that.name) : that.name == null); - } - } - - public static abstract class AbstractParent { - // No no-arg constructor - forces use of special ObjectCreator - } - - public static class ProblematicChild extends AbstractParent { - private String data; - - public ProblematicChild(String data) { - this.data = data; - } - - public String getData() { return data; } - public void setData(String data) { this.data = data; } - - @Override - public String toString() { - return "ProblematicChild{data='" + data + "'}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ProblematicChild)) return false; - ProblematicChild that = (ProblematicChild) o; - return data != null ? data.equals(that.data) : that.data == null; - } - } - - public static class AnotherProblematicChild extends AbstractParent { - private int number; - - public AnotherProblematicChild(int number) { - this.number = number; - } - - public int getNumber() { return number; } - public void setNumber(int number) { this.number = number; } - - @Override - public String toString() { - return "AnotherProblematicChild{number=" + number + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof AnotherProblematicChild)) return false; - AnotherProblematicChild that = (AnotherProblematicChild) o; - return number == that.number; - } - } - - public static void main(String[] args) { - System.out.println("=== GraalVM Integration Test ==="); - System.out.println("Java version: " + System.getProperty("java.version")); - System.out.println("Java vendor: " + System.getProperty("java.vendor")); - System.out.println("Runtime: " + System.getProperty("java.runtime.name")); - System.out.println(); - - GraalVMIntegrationTest test = new GraalVMIntegrationTest(); - - try { - // Test 1: Basic Fory serialization - test.testBasicSerialization(); - - // Test 2: GraalVM Feature functionality - test.testGraalVMFeature(); - - // Test 3: ObjectCreator tests - test.testObjectCreators(); - - // Test 4: Problematic class handling - test.testProblematicClasses(); - - // Test 5: ReflectionFactory availability - test.testReflectionFactoryAvailability(); - - System.out.println("\n✅ All tests passed successfully!"); - - } catch (Exception e) { - System.err.println("\n❌ Test failed:"); - e.printStackTrace(); - System.exit(1); - } - } - - public void testBasicSerialization() { - System.out.println("--- Test 1: Basic Fory Serialization ---"); - - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .requireClassRegistration(true) - .build(); - - fory.register(SimpleClass.class); - - SimpleClass original = new SimpleClass("test", 42); - System.out.println("Original: " + original); - - byte[] serialized = fory.serialize(original); - System.out.println("Serialized " + serialized.length + " bytes"); - - SimpleClass deserialized = (SimpleClass) fory.deserialize(serialized); - System.out.println("Deserialized: " + deserialized); - - if (!original.equals(deserialized)) { - throw new RuntimeException("Serialization test failed: objects not equal"); - } - - System.out.println("✓ Basic serialization test passed\n"); - } - - public void testGraalVMFeature() { - System.out.println("--- Test 2: GraalVM Feature ---"); - - ForyGraalVMFeature feature = new ForyGraalVMFeature(); - System.out.println("Feature description: " + feature.getDescription()); - - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .requireClassRegistration(true) - .build(); - - int initialSize = Fory.getRegisteredClasses().size(); - System.out.println("Initial registered classes count: " + initialSize); - - fory.register(SimpleClass.class); - - int newSize = Fory.getRegisteredClasses().size(); - System.out.println("After registration count: " + newSize); - System.out.println("Contains SimpleClass: " + Fory.getRegisteredClasses().contains(SimpleClass.class)); - - if (!Fory.getRegisteredClasses().contains(SimpleClass.class)) { - throw new RuntimeException("Class registration tracking failed"); - } - - // Note: SimpleClass may have been registered in previous test - if (newSize < initialSize) { - throw new RuntimeException("Registered classes count decreased unexpectedly"); - } - - System.out.println("✓ GraalVM Feature test passed\n"); - } - - public void testObjectCreators() { - System.out.println("--- Test 3: ObjectCreator Tests ---"); - - // Test simple class with no-arg constructor - ObjectCreator simpleCreator = ObjectCreators.getObjectCreator(SimpleClass.class); - System.out.println("SimpleClass ObjectCreator: " + simpleCreator.getClass().getSimpleName()); - - SimpleClass simpleInstance = simpleCreator.newInstance(); - if (simpleInstance == null) { - throw new RuntimeException("SimpleClass instance creation failed"); - } - - if (!simpleInstance.getClass().equals(SimpleClass.class)) { - throw new RuntimeException("SimpleClass instance type incorrect: " + simpleInstance.getClass()); - } - - System.out.println("✓ SimpleClass ObjectCreator test passed"); - - // Test problematic class detection - boolean isProblematic = ObjectCreators.isProblematicForCreation(ProblematicChild.class); - System.out.println("ProblematicChild is problematic: " + isProblematic); - - if (!isProblematic) { - throw new RuntimeException("ProblematicChild should be detected as problematic"); - } - - System.out.println("✓ ObjectCreator tests passed\n"); - } - - public void testProblematicClasses() { - System.out.println("--- Test 4: Problematic Class Handling ---"); - - // Test ProblematicChild - ObjectCreator problematicCreator = ObjectCreators.getObjectCreator(ProblematicChild.class); - System.out.println("ProblematicChild ObjectCreator: " + problematicCreator.getClass().getSimpleName()); - - ProblematicChild problematicInstance = problematicCreator.newInstance(); - if (problematicInstance == null) { - throw new RuntimeException("ProblematicChild instance creation failed"); - } - - if (!problematicInstance.getClass().equals(ProblematicChild.class)) { - throw new RuntimeException("ProblematicChild instance type incorrect: " + problematicInstance.getClass()); - } - - System.out.println("✓ ProblematicChild creation successful: " + problematicInstance); - - // Test AnotherProblematicChild - ObjectCreator anotherCreator = ObjectCreators.getObjectCreator(AnotherProblematicChild.class); - System.out.println("AnotherProblematicChild ObjectCreator: " + anotherCreator.getClass().getSimpleName()); - - AnotherProblematicChild anotherInstance = anotherCreator.newInstance(); - if (anotherInstance == null) { - throw new RuntimeException("AnotherProblematicChild instance creation failed"); - } - - if (!anotherInstance.getClass().equals(AnotherProblematicChild.class)) { - throw new RuntimeException("AnotherProblematicChild instance type incorrect: " + anotherInstance.getClass()); - } - - System.out.println("✓ AnotherProblematicChild creation successful: " + anotherInstance); - System.out.println("✓ Problematic class handling test passed\n"); - } - - public void testReflectionFactoryAvailability() { - System.out.println("--- Test 5: ReflectionFactory Availability ---"); - - try { - Class reflectionFactoryClass = Class.forName("jdk.internal.reflect.ReflectionFactory"); - System.out.println("✓ ReflectionFactory class found: " + reflectionFactoryClass.getName()); - - java.lang.reflect.Method getFactoryMethod = reflectionFactoryClass.getMethod("getReflectionFactory"); - Object factory = getFactoryMethod.invoke(null); - System.out.println("✓ ReflectionFactory instance obtained: " + factory); - - } catch (ClassNotFoundException e) { - System.out.println("⚠ ReflectionFactory not found - may indicate GraalVM Native Image: " + e.getMessage()); - } catch (Exception e) { - System.out.println("⚠ Error accessing ReflectionFactory: " + e.getMessage()); - } - - System.out.println("✓ ReflectionFactory availability test completed\n"); - } -} \ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml index a6b0e0e7ca..2fd0ce8694 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -66,8 +66,8 @@ - 1.8 - 1.8 + 17 + 17 32.1.2-jre 3.1.12 1.13 From 8d1023434d7f2bb91c9da30055c37a1977989b64 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 11:34:08 +0800 Subject: [PATCH 03/46] fix: ci bugs --- .../apache/fory/reflect/ObjectCreators.java | 4 +- java/fory-graalvm-feature/pom.xml | 31 +++++++++------ .../graalvm/feature/ForyGraalVMFeature.java | 1 - .../feature/ForyGraalVMFeatureTest.java | 39 ++----------------- java/pom.xml | 5 +-- 5 files changed, 27 insertions(+), 53 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java index 2fdbbde7af..bfa5b66f64 100644 --- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java +++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java @@ -87,7 +87,9 @@ public static ObjectCreator getObjectCreator(Class type) { * @return true if the class is problematic for creation, false otherwise */ public static boolean isProblematicForCreation(Class type) { - if (type.isInterface() || java.lang.reflect.Modifier.isAbstract(type.getModifiers()) || type.isArray()) { + if (type.isInterface() + || java.lang.reflect.Modifier.isAbstract(type.getModifiers()) + || type.isArray()) { return false; } try { diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index 5ae0ed0693..1f1f599404 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -13,6 +13,12 @@ fory-graalvm-feature Fory GraalVM Feature + + 1.8 + 1.8 + ${basedir}/.. + + org.apache.fory @@ -22,7 +28,7 @@ org.graalvm.sdk graal-sdk - 22.3.0 + 23.0.0 provided @@ -36,17 +42,18 @@ org.apache.maven.plugins - maven-dependency-plugin - 3.2.0 - - - copy-dependencies - package - - copy-dependencies - - - + maven-jar-plugin + + + + org.apache.fory.graalvm.feature + + + + + + org.apache.maven.plugins + maven-compiler-plugin
diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 4487aff648..179846beff 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -71,7 +71,6 @@ private void handleForyClass(Class clazz) { RuntimeReflection.register(clazz); } - @Override public String getDescription() { return "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation."; } diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index a6331d5d27..3024f82d6b 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -55,12 +55,6 @@ public void testGetDescription() { description); } - @Test - public void testFeatureCreation() { - assertNotNull(feature); - assertTrue(feature instanceof org.graalvm.nativeimage.hosted.Feature); - } - @Test public void testObjectCreatorsIntegration() { // Test that ObjectCreators.isProblematicForCreation works correctly @@ -82,35 +76,8 @@ public void testForyStaticMethods() { } @Test - public void testBeforeAnalysisDoesNotThrow() { - // Test that beforeAnalysis method can be called without throwing exceptions - // and the feature can be instantiated - try { - java.lang.reflect.Method beforeAnalysisMethod = - ForyGraalVMFeature.class.getMethod( - "beforeAnalysis", org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess.class); - assertNotNull("beforeAnalysis method should exist", beforeAnalysisMethod); - } catch (NoSuchMethodException e) { - fail("beforeAnalysis method should exist: " + e.getMessage()); - } - } - - @Test - public void testHandleForyClassMethod() { - // Test that the private handleForyClass method exists - try { - java.lang.reflect.Method handleMethod = - ForyGraalVMFeature.class.getDeclaredMethod("handleForyClass", Class.class); - assertNotNull("handleForyClass method should exist", handleMethod); - - // Just verify the method exists, don't try to invoke it since it calls GraalVM APIs - assertEquals( - "Method should be private", - java.lang.reflect.Modifier.PRIVATE, - handleMethod.getModifiers() & java.lang.reflect.Modifier.PRIVATE); - - } catch (NoSuchMethodException e) { - fail("handleForyClass method should exist: " + e.getMessage()); - } + public void testFeatureInstantiation() { + assertNotNull("Feature should be instantiated", feature); + assertNotNull("Feature description should not be null", feature.getDescription()); } } diff --git a/java/pom.xml b/java/pom.xml index 2fd0ce8694..f31ca93240 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -62,12 +62,11 @@ fory-core fory-extensions fory-test-core - fory-graalvm-feature - 17 - 17 + 1.8 + 1.8 32.1.2-jre 3.1.12 1.13 From 9caacb7aee029dc4e59a52cf13bd271a5962fd3a Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 13:12:12 +0800 Subject: [PATCH 04/46] fix: checkstyle --- java/fory-graalvm-feature/pom.xml | 20 ++++++++++++++++++++ java/pom.xml | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index 1f1f599404..d7db088ebb 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -1,4 +1,24 @@ + diff --git a/java/pom.xml b/java/pom.xml index f31ca93240..b7a1d6b836 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -62,6 +62,7 @@ fory-core fory-extensions fory-test-core + fory-graalvm-feature @@ -281,4 +282,23 @@ +nkXRef>false + + + + com.diffplug.spotless + spotless-maven-plugin + ${maven-spotless-plugin.version} + + + + + 1.19.1 + + + + + + + From 2e78cdf0bd2019ed916777df23a404693ebfa67c Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 13:30:56 +0800 Subject: [PATCH 05/46] Update pom.xml --- java/pom.xml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index b7a1d6b836..a6b0e0e7ca 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -282,23 +282,4 @@ -nkXRef>false - - - - com.diffplug.spotless - spotless-maven-plugin - ${maven-spotless-plugin.version} - - - - - 1.19.1 - - - - - - - From de0078169ab4bd89ca5d5dfc08d8ad957f355508 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 14:12:01 +0800 Subject: [PATCH 06/46] fix: ci bugs --- .../main/java/org/apache/fory/graalvm/FeatureTestExample.java | 2 +- java/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index d26e7cb20b..b3b6952eb9 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -84,8 +84,8 @@ public static void main(String[] args) { .withRefTracking(true) .build(); - // Register problematic class fory.register(ProblematicClass.class); + fory.register(TestInvocationHandler.class); // Register proxy interface Fory.addProxyInterface(TestInterface.class); diff --git a/java/pom.xml b/java/pom.xml index a6b0e0e7ca..ca4dd767b9 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -62,7 +62,6 @@ fory-core fory-extensions fory-test-core - fory-graalvm-feature @@ -94,6 +93,7 @@ fory-simd + fory-graalvm-feature From ed2b97af44a8f2741d570ae74dfd8d4e3e15218d Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 14:45:10 +0800 Subject: [PATCH 07/46] fix: ci bug --- .../main/java/org/apache/fory/graalvm/FeatureTestExample.java | 1 + 1 file changed, 1 insertion(+) diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index b3b6952eb9..72100e3546 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -82,6 +82,7 @@ public static void main(String[] args) { Fory fory = Fory.builder() .withLanguage(Language.JAVA) .withRefTracking(true) + .withCodegen(false) .build(); fory.register(ProblematicClass.class); From 3a261fc146e4ea043c07e31a9e7daa220020fb58 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 9 Oct 2025 20:54:02 +0800 Subject: [PATCH 08/46] fix: ci bugs --- .../fory/graalvm/FeatureTestExample.java | 189 +++++++++--------- 1 file changed, 93 insertions(+), 96 deletions(-) diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index 72100e3546..d75913b62a 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -19,110 +19,107 @@ package org.apache.fory.graalvm; -import org.apache.fory.Fory; -import org.apache.fory.config.Language; - import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; - +import org.apache.fory.Fory; +import org.apache.fory.config.Language; public class FeatureTestExample { - // Test class with private constructor (problematic for creation) - public static class ProblematicClass { - private String value; - - private ProblematicClass() { - // Private constructor - } - - public ProblematicClass(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } + // Test class with private constructor (problematic for creation) + public static class ProblematicClass { + private String value; + + private ProblematicClass() { + // Private constructor } - - // Test interface for proxy - public interface TestInterface { - String getValue(); - void setValue(String value); + + public ProblematicClass(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + // Test interface for proxy + public interface TestInterface { + String getValue(); + + void setValue(String value); + } + + // Simple invocation handler + public static class TestInvocationHandler implements InvocationHandler { + private String value; + + public TestInvocationHandler(String value) { + this.value = value; } - - // Simple invocation handler - public static class TestInvocationHandler implements InvocationHandler { - private String value; - - public TestInvocationHandler(String value) { - this.value = value; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if ("getValue".equals(method.getName())) { - return value; - } else if ("setValue".equals(method.getName())) { - value = (String) args[0]; - return null; - } - return null; - } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("getValue".equals(method.getName())) { + return value; + } else if ("setValue".equals(method.getName())) { + value = (String) args[0]; + return null; + } + return null; } + } + + public static void main(String[] args) { + System.out.println("Testing Fory GraalVM Feature..."); + + Fory fory = + Fory.builder().withLanguage(Language.JAVA).withRefTracking(true).withCodegen(false).build(); + + fory.register(ProblematicClass.class); + fory.register(TestInvocationHandler.class); + + // Register proxy interface + Fory.addProxyInterface(TestInterface.class); + + try { + // Test 1: Serialize/deserialize problematic class + ProblematicClass original = new ProblematicClass("test-value"); + byte[] serialized = fory.serialize(original); + ProblematicClass deserialized = (ProblematicClass) fory.deserialize(serialized); + + if (!"test-value".equals(deserialized.getValue())) { + throw new RuntimeException("Problematic class test failed"); + } + System.out.println("✓ Problematic class serialization test passed"); + + // Test 2: Serialize/deserialize proxy object + TestInterface proxy = + (TestInterface) + Proxy.newProxyInstance( + TestInterface.class.getClassLoader(), + new Class[] {TestInterface.class}, + new TestInvocationHandler("proxy-value")); + + byte[] proxySerialised = fory.serialize(proxy); + TestInterface deserializedProxy = (TestInterface) fory.deserialize(proxySerialised); + + if (!"proxy-value".equals(deserializedProxy.getValue())) { + throw new RuntimeException("Proxy test failed"); + } + System.out.println("✓ Proxy serialization test passed"); + + System.out.println("All GraalVM Feature tests passed!"); - public static void main(String[] args) { - System.out.println("Testing Fory GraalVM Feature..."); - - Fory fory = Fory.builder() - .withLanguage(Language.JAVA) - .withRefTracking(true) - .withCodegen(false) - .build(); - - fory.register(ProblematicClass.class); - fory.register(TestInvocationHandler.class); - - // Register proxy interface - Fory.addProxyInterface(TestInterface.class); - - try { - // Test 1: Serialize/deserialize problematic class - ProblematicClass original = new ProblematicClass("test-value"); - byte[] serialized = fory.serialize(original); - ProblematicClass deserialized = (ProblematicClass) fory.deserialize(serialized); - - if (!"test-value".equals(deserialized.getValue())) { - throw new RuntimeException("Problematic class test failed"); - } - System.out.println("✓ Problematic class serialization test passed"); - - // Test 2: Serialize/deserialize proxy object - TestInterface proxy = (TestInterface) Proxy.newProxyInstance( - TestInterface.class.getClassLoader(), - new Class[]{TestInterface.class}, - new TestInvocationHandler("proxy-value") - ); - - byte[] proxySerialised = fory.serialize(proxy); - TestInterface deserializedProxy = (TestInterface) fory.deserialize(proxySerialised); - - if (!"proxy-value".equals(deserializedProxy.getValue())) { - throw new RuntimeException("Proxy test failed"); - } - System.out.println("✓ Proxy serialization test passed"); - - System.out.println("All GraalVM Feature tests passed!"); - - } catch (Exception e) { - System.err.println("GraalVM Feature test failed: " + e.getMessage()); - e.printStackTrace(); - System.exit(1); - } + } catch (Exception e) { + System.err.println("GraalVM Feature test failed: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); } -} \ No newline at end of file + } +} From 0c5e82bc4f7e9e31218c87c90d7333ba86e31baf Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 10 Oct 2025 11:55:37 +0800 Subject: [PATCH 09/46] feat: fix Timing issue in GraalVM Feature --- .../src/main/java/org/apache/fory/Fory.java | 17 +++- .../apache/fory/reflect/ObjectCreators.java | 18 +++- java/fory-graalvm-feature/pom.xml | 2 +- .../graalvm/feature/ForyGraalVMFeature.java | 60 +++++++----- .../feature/ForyGraalVMFeatureTest.java | 93 +++++++++++++++++-- 5 files changed, 152 insertions(+), 38 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index ab077fca7c..fbc99198a6 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -1776,14 +1777,26 @@ public static ForyBuilder builder() { } public static Set> getRegisteredClasses() { - return Collections.unmodifiableSet(REGISTERED_CLASSES); + return Collections.unmodifiableSet(new HashSet<>(REGISTERED_CLASSES)); } public static Set> getProxyInterfaces() { - return Collections.unmodifiableSet(PROXY_INTERFACES); + return Collections.unmodifiableSet(new HashSet<>(PROXY_INTERFACES)); } public static void addProxyInterface(Class anInterface) { + if (anInterface == null) { + throw new NullPointerException("Proxy interface must not be null"); + } + if (!anInterface.isInterface()) { + throw new IllegalArgumentException( + "Proxy type must be an interface: " + anInterface.getName()); + } PROXY_INTERFACES.add(anInterface); } + + public static void clearRegistrations() { + REGISTERED_CLASSES.clear(); + PROXY_INTERFACES.clear(); + } } diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java index bfa5b66f64..19230b249a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java +++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java @@ -89,15 +89,23 @@ public static ObjectCreator getObjectCreator(Class type) { public static boolean isProblematicForCreation(Class type) { if (type.isInterface() || java.lang.reflect.Modifier.isAbstract(type.getModifiers()) - || type.isArray()) { + || type.isArray() + || type.isEnum() + || type.isAnonymousClass() + || type.isLocalClass() + || RecordUtils.isRecord(type)) { return false; } - try { - type.getConstructor(); - return false; - } catch (NoSuchMethodException e) { + Constructor[] constructors = type.getDeclaredConstructors(); + if (constructors.length == 0) { return true; } + for (Constructor constructor : constructors) { + if (constructor.getParameterCount() == 0) { + return false; + } + } + return true; } private static ObjectCreator creategetObjectCreator(Class type) { diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index d7db088ebb..05d9ecafec 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -77,4 +77,4 @@ - \ No newline at end of file + diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 179846beff..18a0d29a07 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -21,6 +21,7 @@ import java.lang.reflect.Field; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.fory.Fory; import org.apache.fory.reflect.ObjectCreators; import org.graalvm.nativeimage.hosted.Feature; @@ -40,38 +41,55 @@ */ public class ForyGraalVMFeature implements Feature { + private final Set> processedClasses = ConcurrentHashMap.newKeySet(); + private final Set> processedProxyInterfaces = ConcurrentHashMap.newKeySet(); + @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - // Process all registered classes - Set> registeredClasses = Fory.getRegisteredClasses(); - for (Class clazz : registeredClasses) { - handleForyClass(clazz); + public void duringAnalysis(DuringAnalysisAccess access) { + boolean changed = false; + + for (Class clazz : Fory.getRegisteredClasses()) { + if (processedClasses.add(clazz)) { + handleForyClass(clazz); + changed = true; + } + } + + for (Class proxyInterface : Fory.getProxyInterfaces()) { + if (processedProxyInterfaces.add(proxyInterface)) { + RuntimeReflection.register(proxyInterface); + RuntimeReflection.register(proxyInterface.getMethods()); + changed = true; + } } - // Process all proxy interfaces - Set> proxyInterfaces = Fory.getProxyInterfaces(); - for (Class proxyInterface : proxyInterfaces) { - RuntimeReflection.register(proxyInterface); - RuntimeReflection.register(proxyInterface.getMethods()); + if (changed) { + access.requireAnalysisIteration(); } } + public String getDescription() { + return "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation."; + } + private void handleForyClass(Class clazz) { if (ObjectCreators.isProblematicForCreation(clazz)) { - // Register for unsafe allocation - RuntimeReflection.registerForReflectiveInstantiation(clazz); - - // Register all fields for reflection access - for (Field field : clazz.getDeclaredFields()) { - RuntimeReflection.register(field); + try { + RuntimeReflection.registerForReflectiveInstantiation(clazz); + for (Field field : clazz.getDeclaredFields()) { + RuntimeReflection.register(field); + } + } catch (Exception e) { + throw new RuntimeException( + String.format( + "Failed to register class '%s' for GraalVM Native Image. " + + "This class lacks an accessible no-arg constructor. " + + "Please ensure fory-graalvm-feature is included in your native-image build.", + clazz.getName()), + e); } } - // Always register the class itself for reflection RuntimeReflection.register(clazz); } - - public String getDescription() { - return "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation."; - } } diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index 3024f82d6b..ccc372d5f3 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -24,6 +24,7 @@ import java.util.Set; import org.apache.fory.Fory; import org.apache.fory.reflect.ObjectCreators; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,22 +32,44 @@ public class ForyGraalVMFeatureTest { private ForyGraalVMFeature feature; - public static class TestClass { + public static class PublicNoArgConstructorClass { private String field1; private int field2; } - public static class ProblematicClass { + public static class ProtectedNoArgConstructorClass { + protected ProtectedNoArgConstructorClass() {} + } + + public static class PrivateParameterizedConstructorClass { private String data; - private ProblematicClass() {} + private PrivateParameterizedConstructorClass(String data) { + this.data = data; + } + } + + public interface SampleProxyInterface { + void execute(); + } + + public static class NonInterfaceProxy {} + + public enum SampleEnum { + VALUE } @Before public void setUp() { + Fory.clearRegistrations(); feature = new ForyGraalVMFeature(); } + @After + public void tearDown() { + Fory.clearRegistrations(); + } + @Test public void testGetDescription() { String description = feature.getDescription(); @@ -56,13 +79,22 @@ public void testGetDescription() { } @Test - public void testObjectCreatorsIntegration() { - // Test that ObjectCreators.isProblematicForCreation works correctly - boolean isProblematic = ObjectCreators.isProblematicForCreation(ProblematicClass.class); - assertTrue("ProblematicClass should be detected as problematic", isProblematic); + public void testObjectCreatorsProblematicDetection() { + assertTrue( + "Class without no-arg constructor should be problematic", + ObjectCreators.isProblematicForCreation(PrivateParameterizedConstructorClass.class)); - boolean isNotProblematic = ObjectCreators.isProblematicForCreation(TestClass.class); - assertFalse("TestClass should not be detected as problematic", isNotProblematic); + assertFalse( + "Public no-arg constructor should not be problematic", + ObjectCreators.isProblematicForCreation(PublicNoArgConstructorClass.class)); + + assertFalse( + "Protected no-arg constructor should not be problematic", + ObjectCreators.isProblematicForCreation(ProtectedNoArgConstructorClass.class)); + + assertFalse( + "Enums should not be considered problematic", + ObjectCreators.isProblematicForCreation(SampleEnum.class)); } @Test @@ -73,6 +105,13 @@ public void testForyStaticMethods() { Set> proxyInterfaces = Fory.getProxyInterfaces(); assertNotNull("Proxy interfaces should not be null", proxyInterfaces); + + try { + registeredClasses.add(PublicNoArgConstructorClass.class); + fail("Snapshots should be unmodifiable"); + } catch (UnsupportedOperationException expected) { + // expected + } } @Test @@ -80,4 +119,40 @@ public void testFeatureInstantiation() { assertNotNull("Feature should be instantiated", feature); assertNotNull("Feature description should not be null", feature.getDescription()); } + + @Test + public void testAddProxyInterfaceRejectsNull() { + try { + Fory.addProxyInterface(null); + fail("Null proxy interface should throw NullPointerException"); + } catch (NullPointerException expected) { + // expected + } + } + + @Test + public void testAddProxyInterfaceRejectsNonInterface() { + try { + Fory.addProxyInterface(NonInterfaceProxy.class); + fail("Non-interface proxy type should throw IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + @Test + public void testClearRegistrationsResetsState() { + Fory builderInstance = Fory.builder().build(); + Fory.clearRegistrations(); + builderInstance.register(PublicNoArgConstructorClass.class); + Fory.addProxyInterface(SampleProxyInterface.class); + + assertFalse(Fory.getRegisteredClasses().isEmpty()); + assertFalse(Fory.getProxyInterfaces().isEmpty()); + + Fory.clearRegistrations(); + + assertTrue(Fory.getRegisteredClasses().isEmpty()); + assertTrue(Fory.getProxyInterfaces().isEmpty()); + } } From 1592b3a920f7743b39d522c53137297c6d90b66c Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Sat, 11 Oct 2025 17:21:55 +0800 Subject: [PATCH 10/46] feat: remove static fields --- .../src/main/java/org/apache/fory/Fory.java | 55 ++++++++------ .../apache/fory/resolver/TypeResolver.java | 72 +++++++++++++++++++ .../graalvm/feature/ForyGraalVMFeature.java | 6 +- 3 files changed, 110 insertions(+), 23 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index fbc99198a6..62130660dc 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -93,10 +93,6 @@ public final class Fory implements BaseFory { private static final Logger LOG = LoggerFactory.getLogger(Fory.class); - // Static collections for GraalVM Feature support - private static final Set> REGISTERED_CLASSES = ConcurrentHashMap.newKeySet(); - private static final Set> PROXY_INTERFACES = ConcurrentHashMap.newKeySet(); - public static final byte NULL_FLAG = -3; // This flag indicates that object is a not-null value. // We don't use another byte to indicate REF, so that we can save one byte. @@ -185,13 +181,13 @@ public Fory(ForyBuilder builder, ClassLoader classLoader) { @Override public void register(Class cls) { _getTypeResolver().register(cls); - REGISTERED_CLASSES.add(cls); + TypeResolver.registerClassForGraalvm(cls, config.getConfigHash()); } @Override public void register(Class cls, int id) { _getTypeResolver().register(cls, id); - REGISTERED_CLASSES.add(cls); + TypeResolver.registerClassForGraalvm(cls, config.getConfigHash()); } @Deprecated @@ -223,7 +219,7 @@ public void register(Class cls, String typeName) { public void register(Class cls, String namespace, String typeName) { _getTypeResolver().register(cls, namespace, typeName); - REGISTERED_CLASSES.add(cls); + TypeResolver.registerClassForGraalvm(cls, config.getConfigHash()); } @Override @@ -1776,27 +1772,46 @@ public static ForyBuilder builder() { return new ForyBuilder(); } + /** + * Get all registered classes for GraalVM native image compilation. + * This is a convenience method that delegates to {@link TypeResolver#getAllRegisteredClasses()}. + * + * @return unmodifiable set of all registered classes + */ public static Set> getRegisteredClasses() { - return Collections.unmodifiableSet(new HashSet<>(REGISTERED_CLASSES)); + return TypeResolver.getAllRegisteredClasses(); } + /** + * Get all registered proxy interfaces for GraalVM native image compilation. + * This is a convenience method that delegates to {@link TypeResolver#getAllProxyInterfaces()}. + * + * @return unmodifiable set of all registered proxy interfaces + */ public static Set> getProxyInterfaces() { - return Collections.unmodifiableSet(new HashSet<>(PROXY_INTERFACES)); + return TypeResolver.getAllProxyInterfaces(); } - public static void addProxyInterface(Class anInterface) { - if (anInterface == null) { - throw new NullPointerException("Proxy interface must not be null"); - } - if (!anInterface.isInterface()) { - throw new IllegalArgumentException( - "Proxy type must be an interface: " + anInterface.getName()); - } - PROXY_INTERFACES.add(anInterface); + /** + * Register a proxy interface for GraalVM native image compilation. + * This is a convenience method that delegates to {@link TypeResolver#registerProxyInterfaceForGraalvm}. + * + *

Note: This registers the proxy interface globally across all Fory configurations. + * If you need per-configuration registration, use the TypeResolver methods directly. + * + * @param proxyInterface the proxy interface to register + * @throws NullPointerException if proxyInterface is null + * @throws IllegalArgumentException if proxyInterface is not an interface + */ + public static void addProxyInterface(Class proxyInterface) { + TypeResolver.registerProxyInterfaceForGraalvm(proxyInterface, 0); } + /** + * Clear all GraalVM registrations. This is primarily for testing purposes. + * This is a convenience method that delegates to {@link TypeResolver#clearGraalvmRegistrations()}. + */ public static void clearRegistrations() { - REGISTERED_CLASSES.clear(); - PROXY_INTERFACES.clear(); + TypeResolver.clearGraalvmRegistrations(); } } diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java index 4db04ecf95..c659476492 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java @@ -589,11 +589,15 @@ static class GraalvmClassRegistry { final List resolvers; final Map, Class> serializerClassMap; final Map> deserializerClassMap; + final Set> registeredClasses; + final Set> proxyInterfaces; private GraalvmClassRegistry() { resolvers = Collections.synchronizedList(new ArrayList<>()); serializerClassMap = new ConcurrentHashMap<>(); deserializerClassMap = new ConcurrentHashMap<>(); + registeredClasses = ConcurrentHashMap.newKeySet(); + proxyInterfaces = ConcurrentHashMap.newKeySet(); } } @@ -670,6 +674,74 @@ public final MetaStringResolver getMetaStringResolver() { return metaStringResolver; } + /** + * Get all registered classes across all GraalVM registries for native image compilation. + * + * @return unmodifiable set of all registered classes + */ + public static Set> getAllRegisteredClasses() { + Set> allClasses = ConcurrentHashMap.newKeySet(); + for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { + allClasses.addAll(registry.registeredClasses); + } + return Collections.unmodifiableSet(allClasses); + } + + /** + * Get all registered proxy interfaces across all GraalVM registries for native image compilation. + * + * @return unmodifiable set of all registered proxy interfaces + */ + public static Set> getAllProxyInterfaces() { + Set> allInterfaces = ConcurrentHashMap.newKeySet(); + for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { + allInterfaces.addAll(registry.proxyInterfaces); + } + return Collections.unmodifiableSet(allInterfaces); + } + + /** + * Register a class in the GraalVM registry for native image compilation. + * This should be called from {@link org.apache.fory.Fory#register} methods. + * + * @param cls the class to register + * @param configHash the configuration hash for the Fory instance + */ + public static void registerClassForGraalvm(Class cls, int configHash) { + GraalvmClassRegistry registry = + GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); + registry.registeredClasses.add(cls); + } + + /** + * Register a proxy interface in the GraalVM registry for native image compilation. + * + * @param proxyInterface the proxy interface to register + * @param configHash the configuration hash for the Fory instance + */ + public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int configHash) { + if (proxyInterface == null) { + throw new NullPointerException("Proxy interface must not be null"); + } + if (!proxyInterface.isInterface()) { + throw new IllegalArgumentException( + "Proxy type must be an interface: " + proxyInterface.getName()); + } + GraalvmClassRegistry registry = + GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); + registry.proxyInterfaces.add(proxyInterface); + } + + /** + * Clear all GraalVM registrations. This is primarily for testing purposes. + */ + public static void clearGraalvmRegistrations() { + for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { + registry.registeredClasses.clear(); + registry.proxyInterfaces.clear(); + } + } + static class ExtRegistry { // Here we set it to 1 because `NO_CLASS_ID` is 0 to avoid calculating it again in // `register(Class cls)`. diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 18a0d29a07..2909cfad3c 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -22,8 +22,8 @@ import java.lang.reflect.Field; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.apache.fory.Fory; import org.apache.fory.reflect.ObjectCreators; +import org.apache.fory.resolver.TypeResolver; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -48,14 +48,14 @@ public class ForyGraalVMFeature implements Feature { public void duringAnalysis(DuringAnalysisAccess access) { boolean changed = false; - for (Class clazz : Fory.getRegisteredClasses()) { + for (Class clazz : TypeResolver.getAllRegisteredClasses()) { if (processedClasses.add(clazz)) { handleForyClass(clazz); changed = true; } } - for (Class proxyInterface : Fory.getProxyInterfaces()) { + for (Class proxyInterface : TypeResolver.getAllProxyInterfaces()) { if (processedProxyInterfaces.add(proxyInterface)) { RuntimeReflection.register(proxyInterface); RuntimeReflection.register(proxyInterface.getMethods()); From ab25f6d49b9cbd04c3e4d15c6a3e667e66f16e01 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Sat, 11 Oct 2025 17:50:30 +0800 Subject: [PATCH 11/46] fix: chectstyle --- .../src/main/java/org/apache/fory/Fory.java | 23 ++++++++----------- .../apache/fory/resolver/TypeResolver.java | 8 +++---- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index 62130660dc..4576258c99 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -25,12 +25,9 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.concurrent.NotThreadSafe; @@ -1773,8 +1770,8 @@ public static ForyBuilder builder() { } /** - * Get all registered classes for GraalVM native image compilation. - * This is a convenience method that delegates to {@link TypeResolver#getAllRegisteredClasses()}. + * Get all registered classes for GraalVM native image compilation. This is a convenience method + * that delegates to {@link TypeResolver#getAllRegisteredClasses()}. * * @return unmodifiable set of all registered classes */ @@ -1783,8 +1780,8 @@ public static Set> getRegisteredClasses() { } /** - * Get all registered proxy interfaces for GraalVM native image compilation. - * This is a convenience method that delegates to {@link TypeResolver#getAllProxyInterfaces()}. + * Get all registered proxy interfaces for GraalVM native image compilation. This is a convenience + * method that delegates to {@link TypeResolver#getAllProxyInterfaces()}. * * @return unmodifiable set of all registered proxy interfaces */ @@ -1793,11 +1790,11 @@ public static Set> getProxyInterfaces() { } /** - * Register a proxy interface for GraalVM native image compilation. - * This is a convenience method that delegates to {@link TypeResolver#registerProxyInterfaceForGraalvm}. + * Register a proxy interface for GraalVM native image compilation. This is a convenience method + * that delegates to {@link TypeResolver#registerProxyInterfaceForGraalvm}. * - *

Note: This registers the proxy interface globally across all Fory configurations. - * If you need per-configuration registration, use the TypeResolver methods directly. + *

Note: This registers the proxy interface globally across all Fory configurations. If you + * need per-configuration registration, use the TypeResolver methods directly. * * @param proxyInterface the proxy interface to register * @throws NullPointerException if proxyInterface is null @@ -1808,8 +1805,8 @@ public static void addProxyInterface(Class proxyInterface) { } /** - * Clear all GraalVM registrations. This is primarily for testing purposes. - * This is a convenience method that delegates to {@link TypeResolver#clearGraalvmRegistrations()}. + * Clear all GraalVM registrations. This is primarily for testing purposes. This is a convenience + * method that delegates to {@link TypeResolver#clearGraalvmRegistrations()}. */ public static void clearRegistrations() { TypeResolver.clearGraalvmRegistrations(); diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java index c659476492..4b1aa67339 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java @@ -701,8 +701,8 @@ public static Set> getAllProxyInterfaces() { } /** - * Register a class in the GraalVM registry for native image compilation. - * This should be called from {@link org.apache.fory.Fory#register} methods. + * Register a class in the GraalVM registry for native image compilation. This should be called + * from {@link org.apache.fory.Fory#register} methods. * * @param cls the class to register * @param configHash the configuration hash for the Fory instance @@ -732,9 +732,7 @@ public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int registry.proxyInterfaces.add(proxyInterface); } - /** - * Clear all GraalVM registrations. This is primarily for testing purposes. - */ + /** Clear all GraalVM registrations. This is primarily for testing purposes. */ public static void clearGraalvmRegistrations() { for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { registry.registeredClasses.clear(); From e9f8ef4a4a8f71e5924ec0f67d94af38745026cd Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Mon, 13 Oct 2025 13:46:28 +0800 Subject: [PATCH 12/46] fix: Api bug --- .../fory/graalvm/FeatureTestExample.java | 24 +++++----- .../src/main/java/org/apache/fory/Fory.java | 44 ------------------- .../apache/fory/reflect/ObjectCreators.java | 4 ++ .../apache/fory/resolver/TypeResolver.java | 10 +++++ .../org/apache/fory/util/GraalvmSupport.java | 19 ++++++++ .../feature/ForyGraalVMFeatureTest.java | 38 +++++++--------- 6 files changed, 60 insertions(+), 79 deletions(-) diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index d75913b62a..50e389fde3 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -24,18 +24,18 @@ import java.lang.reflect.Proxy; import org.apache.fory.Fory; import org.apache.fory.config.Language; +import org.apache.fory.resolver.TypeResolver; public class FeatureTestExample { - // Test class with private constructor (problematic for creation) - public static class ProblematicClass { + public static class PrivateConstructorClass { private String value; - private ProblematicClass() { + private PrivateConstructorClass() { // Private constructor } - public ProblematicClass(String value) { + public PrivateConstructorClass(String value) { this.value = value; } @@ -81,22 +81,22 @@ public static void main(String[] args) { Fory fory = Fory.builder().withLanguage(Language.JAVA).withRefTracking(true).withCodegen(false).build(); - fory.register(ProblematicClass.class); + fory.register(PrivateConstructorClass.class); fory.register(TestInvocationHandler.class); // Register proxy interface - Fory.addProxyInterface(TestInterface.class); + TypeResolver.addProxyInterface(TestInterface.class); try { - // Test 1: Serialize/deserialize problematic class - ProblematicClass original = new ProblematicClass("test-value"); + // Test 1: Serialize/deserialize class with private constructor + PrivateConstructorClass original = new PrivateConstructorClass("test-value"); byte[] serialized = fory.serialize(original); - ProblematicClass deserialized = (ProblematicClass) fory.deserialize(serialized); + PrivateConstructorClass deserialized = (PrivateConstructorClass) fory.deserialize(serialized); if (!"test-value".equals(deserialized.getValue())) { - throw new RuntimeException("Problematic class test failed"); + throw new RuntimeException("Private constructor class test failed"); } - System.out.println("✓ Problematic class serialization test passed"); + System.out.println("Private constructor class serialization test passed"); // Test 2: Serialize/deserialize proxy object TestInterface proxy = @@ -112,7 +112,7 @@ public static void main(String[] args) { if (!"proxy-value".equals(deserializedProxy.getValue())) { throw new RuntimeException("Proxy test failed"); } - System.out.println("✓ Proxy serialization test passed"); + System.out.println("Proxy serialization test passed"); System.out.println("All GraalVM Feature tests passed!"); diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index 4576258c99..9d1e0b7920 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -27,7 +27,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; -import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.concurrent.NotThreadSafe; @@ -1768,47 +1767,4 @@ public MetaCompressor getMetaCompressor() { public static ForyBuilder builder() { return new ForyBuilder(); } - - /** - * Get all registered classes for GraalVM native image compilation. This is a convenience method - * that delegates to {@link TypeResolver#getAllRegisteredClasses()}. - * - * @return unmodifiable set of all registered classes - */ - public static Set> getRegisteredClasses() { - return TypeResolver.getAllRegisteredClasses(); - } - - /** - * Get all registered proxy interfaces for GraalVM native image compilation. This is a convenience - * method that delegates to {@link TypeResolver#getAllProxyInterfaces()}. - * - * @return unmodifiable set of all registered proxy interfaces - */ - public static Set> getProxyInterfaces() { - return TypeResolver.getAllProxyInterfaces(); - } - - /** - * Register a proxy interface for GraalVM native image compilation. This is a convenience method - * that delegates to {@link TypeResolver#registerProxyInterfaceForGraalvm}. - * - *

Note: This registers the proxy interface globally across all Fory configurations. If you - * need per-configuration registration, use the TypeResolver methods directly. - * - * @param proxyInterface the proxy interface to register - * @throws NullPointerException if proxyInterface is null - * @throws IllegalArgumentException if proxyInterface is not an interface - */ - public static void addProxyInterface(Class proxyInterface) { - TypeResolver.registerProxyInterfaceForGraalvm(proxyInterface, 0); - } - - /** - * Clear all GraalVM registrations. This is primarily for testing purposes. This is a convenience - * method that delegates to {@link TypeResolver#clearGraalvmRegistrations()}. - */ - public static void clearRegistrations() { - TypeResolver.clearGraalvmRegistrations(); - } } diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java index 19230b249a..31a3222615 100644 --- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java +++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java @@ -23,6 +23,7 @@ import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; import org.apache.fory.collection.ClassValueCache; import org.apache.fory.collection.Tuple2; import org.apache.fory.exception.ForyException; @@ -104,6 +105,9 @@ public static boolean isProblematicForCreation(Class type) { if (constructor.getParameterCount() == 0) { return false; } + if (RecordUtils.isRecord(type) && Modifier.isPublic(constructor.getModifiers())) { + return false; + } } return true; } diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java index 4b1aa67339..6085abd74a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java @@ -732,6 +732,16 @@ public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int registry.proxyInterfaces.add(proxyInterface); } + /** + * Register a proxy interface globally for GraalVM native image compilation. + * + *

This is a convenience wrapper over {@link #registerProxyInterfaceForGraalvm(Class, int)} + * with a shared registration scope. + */ + public static void addProxyInterface(Class proxyInterface) { + registerProxyInterfaceForGraalvm(proxyInterface, 0); + } + /** Clear all GraalVM registrations. This is primarily for testing purposes. */ public static void clearGraalvmRegistrations() { for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index 7bfe2b00b1..c8fdd35077 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -21,9 +21,11 @@ import java.lang.reflect.Constructor; import java.util.Objects; +import java.util.Set; import org.apache.fory.Fory; import org.apache.fory.exception.ForyException; import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.Serializer; /** A helper for Graalvm native image support. */ @@ -54,6 +56,23 @@ public static boolean isGraalRuntime() { && GRAAL_IMAGE_RUNTIME.equals(System.getProperty(GRAAL_IMAGE_CODE_KEY)); } + /** + * Returns all classes registered for GraalVM native image compilation across all configurations. + */ + public static Set> getRegisteredClasses() { + return TypeResolver.getAllRegisteredClasses(); + } + + /** Returns all proxy interfaces registered for GraalVM native image compilation. */ + public static Set> getProxyInterfaces() { + return TypeResolver.getAllProxyInterfaces(); + } + + /** Clears all GraalVM native image registrations. Primarily for testing purposes. */ + public static void clearRegistrations() { + TypeResolver.clearGraalvmRegistrations(); + } + public static class GraalvmSerializerHolder extends Serializer { private final Class serializerClass; private Serializer serializer; diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index ccc372d5f3..21b63642e0 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -21,9 +21,10 @@ import static org.junit.Assert.*; -import java.util.Set; import org.apache.fory.Fory; import org.apache.fory.reflect.ObjectCreators; +import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.util.GraalvmSupport; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -61,13 +62,13 @@ public enum SampleEnum { @Before public void setUp() { - Fory.clearRegistrations(); + GraalvmSupport.clearRegistrations(); feature = new ForyGraalVMFeature(); } @After public void tearDown() { - Fory.clearRegistrations(); + GraalvmSupport.clearRegistrations(); } @Test @@ -100,18 +101,9 @@ public void testObjectCreatorsProblematicDetection() { @Test public void testForyStaticMethods() { // Test that Fory static methods are accessible - Set> registeredClasses = Fory.getRegisteredClasses(); - assertNotNull("Registered classes should not be null", registeredClasses); + assertNotNull("Registered classes should not be null", GraalvmSupport.getRegisteredClasses()); - Set> proxyInterfaces = Fory.getProxyInterfaces(); - assertNotNull("Proxy interfaces should not be null", proxyInterfaces); - - try { - registeredClasses.add(PublicNoArgConstructorClass.class); - fail("Snapshots should be unmodifiable"); - } catch (UnsupportedOperationException expected) { - // expected - } + assertNotNull("Proxy interfaces should not be null", GraalvmSupport.getProxyInterfaces()); } @Test @@ -123,7 +115,7 @@ public void testFeatureInstantiation() { @Test public void testAddProxyInterfaceRejectsNull() { try { - Fory.addProxyInterface(null); + TypeResolver.addProxyInterface(null); fail("Null proxy interface should throw NullPointerException"); } catch (NullPointerException expected) { // expected @@ -133,7 +125,7 @@ public void testAddProxyInterfaceRejectsNull() { @Test public void testAddProxyInterfaceRejectsNonInterface() { try { - Fory.addProxyInterface(NonInterfaceProxy.class); + TypeResolver.addProxyInterface(NonInterfaceProxy.class); fail("Non-interface proxy type should throw IllegalArgumentException"); } catch (IllegalArgumentException expected) { // expected @@ -143,16 +135,16 @@ public void testAddProxyInterfaceRejectsNonInterface() { @Test public void testClearRegistrationsResetsState() { Fory builderInstance = Fory.builder().build(); - Fory.clearRegistrations(); + GraalvmSupport.clearRegistrations(); builderInstance.register(PublicNoArgConstructorClass.class); - Fory.addProxyInterface(SampleProxyInterface.class); + TypeResolver.addProxyInterface(SampleProxyInterface.class); - assertFalse(Fory.getRegisteredClasses().isEmpty()); - assertFalse(Fory.getProxyInterfaces().isEmpty()); + assertFalse(GraalvmSupport.getRegisteredClasses().isEmpty()); + assertFalse(GraalvmSupport.getProxyInterfaces().isEmpty()); - Fory.clearRegistrations(); + GraalvmSupport.clearRegistrations(); - assertTrue(Fory.getRegisteredClasses().isEmpty()); - assertTrue(Fory.getProxyInterfaces().isEmpty()); + assertTrue(GraalvmSupport.getRegisteredClasses().isEmpty()); + assertTrue(GraalvmSupport.getProxyInterfaces().isEmpty()); } } From 21d7de87d037269fdc2f6eaf7f24dd5741409ad2 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Mon, 13 Oct 2025 14:10:07 +0800 Subject: [PATCH 13/46] fix: adapt jdk17 --- .../java/org/apache/fory/reflect/ObjectCreators.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java index 31a3222615..c8a1bd31ce 100644 --- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java +++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java @@ -93,8 +93,7 @@ public static boolean isProblematicForCreation(Class type) { || type.isArray() || type.isEnum() || type.isAnonymousClass() - || type.isLocalClass() - || RecordUtils.isRecord(type)) { + || type.isLocalClass()) { return false; } Constructor[] constructors = type.getDeclaredConstructors(); @@ -105,9 +104,14 @@ public static boolean isProblematicForCreation(Class type) { if (constructor.getParameterCount() == 0) { return false; } - if (RecordUtils.isRecord(type) && Modifier.isPublic(constructor.getModifiers())) { - return false; + } + if (RecordUtils.isRecord(type)) { + for (Constructor constructor : constructors) { + if (Modifier.isPublic(constructor.getModifiers())) { + return false; + } } + return true; } return true; } From 56e00e7a384e9203055d6bbfe8982f2182edb74a Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Mon, 13 Oct 2025 17:20:53 +0800 Subject: [PATCH 14/46] fix: adapt jdk17 --- .../apache/fory/reflect/ObjectCreators.java | 8 +- .../org/apache/fory/util/GraalvmSupport.java | 30 +++++ .../fory/util/GraalvmSupportRecordTest.java | 112 ++++++++++++++++++ 3 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java index c8a1bd31ce..e9b4695b1c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java +++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java @@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; -import java.lang.reflect.Modifier; import org.apache.fory.collection.ClassValueCache; import org.apache.fory.collection.Tuple2; import org.apache.fory.exception.ForyException; @@ -106,12 +105,7 @@ public static boolean isProblematicForCreation(Class type) { } } if (RecordUtils.isRecord(type)) { - for (Constructor constructor : constructors) { - if (Modifier.isPublic(constructor.getModifiers())) { - return false; - } - } - return true; + return !GraalvmSupport.isRecordConstructorPublicAccessible(type); } return true; } diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index c8fdd35077..a5ba7f666f 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -20,6 +20,7 @@ package org.apache.fory.util; import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; import java.util.Objects; import java.util.Set; import org.apache.fory.Fory; @@ -27,6 +28,7 @@ import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.Serializer; +import org.apache.fory.util.record.RecordUtils; /** A helper for Graalvm native image support. */ public class GraalvmSupport { @@ -115,4 +117,32 @@ private Serializer getSerializer() { public static ForyException throwNoArgCtrException(Class type) { throw new ForyException("Please provide a no-arg constructor for " + type); } + + public static boolean isRecordConstructorPublicAccessible(Class type) { + if (!RecordUtils.isRecord(type)) { + return false; + } + + try { + Constructor[] constructors = type.getDeclaredConstructors(); + for (Constructor constructor : constructors) { + if (Modifier.isPublic(constructor.getModifiers())) { + Class[] paramTypes = constructor.getParameterTypes(); + boolean allParamsPublic = true; + for (Class paramType : paramTypes) { + if (!Modifier.isPublic(paramType.getModifiers())) { + allParamsPublic = false; + break; + } + } + if (allParamsPublic) { + return true; + } + } + } + } catch (Exception e) { + return false; + } + return false; + } } diff --git a/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java b/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java new file mode 100644 index 0000000000..d83c40371d --- /dev/null +++ b/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java @@ -0,0 +1,112 @@ +/* + * 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.util; + +import org.apache.fory.reflect.ObjectCreators; +import org.apache.fory.util.record.RecordUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class GraalvmSupportRecordTest { + + public static class RegularClass { + public int intField; + public String stringField; + + public RegularClass() {} + + public RegularClass(int intField, String stringField) { + this.intField = intField; + this.stringField = stringField; + } + } + + private static class PrivateClass { + @SuppressWarnings("unused") + private final int value; + + @SuppressWarnings("unused") + public PrivateClass(int value) { + this.value = value; + } + } + + @Test + public void testIsRecordConstructorPublicAccessible_WithNonRecord() { + boolean result = GraalvmSupport.isRecordConstructorPublicAccessible(RegularClass.class); + Assert.assertFalse(result, "Non-Record class should return false"); + } + + @Test + public void testIsRecordConstructorPublicAccessible_WithObject() { + boolean result = GraalvmSupport.isRecordConstructorPublicAccessible(Object.class); + Assert.assertFalse(result, "Object class should return false"); + } + + @Test + public void testIsRecordConstructorPublicAccessible_WithString() { + boolean result = GraalvmSupport.isRecordConstructorPublicAccessible(String.class); + Assert.assertFalse(result, "String class should return false"); + } + + @Test + public void testObjectCreators_IsProblematicForCreation_WithRegularClass() { + boolean result = ObjectCreators.isProblematicForCreation(RegularClass.class); + Assert.assertFalse( + result, "RegularClass with no-arg constructor should not be problematic for creation"); + } + + @Test + public void testBackwardCompatibility_OnOlderJDK() { + boolean result = GraalvmSupport.isRecordConstructorPublicAccessible(RegularClass.class); + Assert.assertFalse(result, "Should return false for non-Record classes on any JDK version"); + + try { + GraalvmSupport.isRecordConstructorPublicAccessible(Object.class); + GraalvmSupport.isRecordConstructorPublicAccessible(String.class); + GraalvmSupport.isRecordConstructorPublicAccessible(Integer.class); + Assert.assertTrue(true, "Method should not throw exceptions on older JDK versions"); + } catch (Exception e) { + Assert.fail("Method should not throw exceptions on older JDK versions", e); + } + } + + @Test + public void testObjectCreators_BackwardCompatibility() { + Assert.assertFalse(ObjectCreators.isProblematicForCreation(RegularClass.class)); + Assert.assertFalse(ObjectCreators.isProblematicForCreation(Object.class)); + Assert.assertFalse(ObjectCreators.isProblematicForCreation(String.class)); + + Assert.assertTrue(ObjectCreators.isProblematicForCreation(PrivateClass.class)); + + Assert.assertFalse(ObjectCreators.isProblematicForCreation(Runnable.class)); + } + + @Test + public void testRecordUtilsIntegration() { + Assert.assertFalse(RecordUtils.isRecord(RegularClass.class)); + Assert.assertFalse(RecordUtils.isRecord(Object.class)); + Assert.assertFalse(RecordUtils.isRecord(String.class)); + + Assert.assertFalse(GraalvmSupport.isRecordConstructorPublicAccessible(RegularClass.class)); + Assert.assertFalse(GraalvmSupport.isRecordConstructorPublicAccessible(Object.class)); + Assert.assertFalse(GraalvmSupport.isRecordConstructorPublicAccessible(String.class)); + } +} From 021b1c55a1e89a5e478c1f4c0dd98a5722d063c0 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Mon, 13 Oct 2025 23:05:13 +0800 Subject: [PATCH 15/46] fix:APi moved to GraalvmSupport --- .../apache/fory/reflect/ObjectCreators.java | 33 ------------------- .../org/apache/fory/util/GraalvmSupport.java | 33 +++++++++++++++++++ .../fory/util/GraalvmSupportRecordTest.java | 13 ++++---- .../graalvm/feature/ForyGraalVMFeature.java | 4 +-- .../feature/ForyGraalVMFeatureTest.java | 9 +++-- 5 files changed, 45 insertions(+), 47 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java index e9b4695b1c..7cc0fbe36c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java +++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java @@ -77,39 +77,6 @@ public static ObjectCreator getObjectCreator(Class type) { return (ObjectCreator) cache.get(type, () -> creategetObjectCreator(type)); } - /** - * Checks if a class is problematic for object creation and requires special handling in GraalVM. - * - *

A class is considered problematic if it lacks a public no-arg constructor and would - * typically require ReflectionFactory or unsafe allocation for instantiation. - * - * @param type the class to check - * @return true if the class is problematic for creation, false otherwise - */ - public static boolean isProblematicForCreation(Class type) { - if (type.isInterface() - || java.lang.reflect.Modifier.isAbstract(type.getModifiers()) - || type.isArray() - || type.isEnum() - || type.isAnonymousClass() - || type.isLocalClass()) { - return false; - } - Constructor[] constructors = type.getDeclaredConstructors(); - if (constructors.length == 0) { - return true; - } - for (Constructor constructor : constructors) { - if (constructor.getParameterCount() == 0) { - return false; - } - } - if (RecordUtils.isRecord(type)) { - return !GraalvmSupport.isRecordConstructorPublicAccessible(type); - } - return true; - } - private static ObjectCreator creategetObjectCreator(Class type) { if (RecordUtils.isRecord(type)) { return new RecordObjectCreator<>(type); diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index a5ba7f666f..e552d74911 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -145,4 +145,37 @@ public static boolean isRecordConstructorPublicAccessible(Class type) { } return false; } + + /** + * Checks if a class is problematic for object creation and requires special handling in GraalVM. + * + *

A class is considered problematic if it lacks a public no-arg constructor and would + * typically require ReflectionFactory or unsafe allocation for instantiation. + * + * @param type the class to check + * @return true if the class is problematic for creation, false otherwise + */ + public static boolean isProblematicForCreation(Class type) { + if (type.isInterface() + || Modifier.isAbstract(type.getModifiers()) + || type.isArray() + || type.isEnum() + || type.isAnonymousClass() + || type.isLocalClass()) { + return false; + } + Constructor[] constructors = type.getDeclaredConstructors(); + if (constructors.length == 0) { + return true; + } + for (Constructor constructor : constructors) { + if (constructor.getParameterCount() == 0) { + return false; + } + } + if (RecordUtils.isRecord(type)) { + return !isRecordConstructorPublicAccessible(type); + } + return true; + } } diff --git a/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java b/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java index d83c40371d..c0e8bce0a1 100644 --- a/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java @@ -19,7 +19,6 @@ package org.apache.fory.util; -import org.apache.fory.reflect.ObjectCreators; import org.apache.fory.util.record.RecordUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -68,7 +67,7 @@ public void testIsRecordConstructorPublicAccessible_WithString() { @Test public void testObjectCreators_IsProblematicForCreation_WithRegularClass() { - boolean result = ObjectCreators.isProblematicForCreation(RegularClass.class); + boolean result = GraalvmSupport.isProblematicForCreation(RegularClass.class); Assert.assertFalse( result, "RegularClass with no-arg constructor should not be problematic for creation"); } @@ -90,13 +89,13 @@ public void testBackwardCompatibility_OnOlderJDK() { @Test public void testObjectCreators_BackwardCompatibility() { - Assert.assertFalse(ObjectCreators.isProblematicForCreation(RegularClass.class)); - Assert.assertFalse(ObjectCreators.isProblematicForCreation(Object.class)); - Assert.assertFalse(ObjectCreators.isProblematicForCreation(String.class)); + Assert.assertFalse(GraalvmSupport.isProblematicForCreation(RegularClass.class)); + Assert.assertFalse(GraalvmSupport.isProblematicForCreation(Object.class)); + Assert.assertFalse(GraalvmSupport.isProblematicForCreation(String.class)); - Assert.assertTrue(ObjectCreators.isProblematicForCreation(PrivateClass.class)); + Assert.assertTrue(GraalvmSupport.isProblematicForCreation(PrivateClass.class)); - Assert.assertFalse(ObjectCreators.isProblematicForCreation(Runnable.class)); + Assert.assertFalse(GraalvmSupport.isProblematicForCreation(Runnable.class)); } @Test diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 2909cfad3c..75a4ff10d7 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -22,8 +22,8 @@ import java.lang.reflect.Field; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.apache.fory.reflect.ObjectCreators; import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.util.GraalvmSupport; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -73,7 +73,7 @@ public String getDescription() { } private void handleForyClass(Class clazz) { - if (ObjectCreators.isProblematicForCreation(clazz)) { + if (GraalvmSupport.isProblematicForCreation(clazz)) { try { RuntimeReflection.registerForReflectiveInstantiation(clazz); for (Field field : clazz.getDeclaredFields()) { diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index 21b63642e0..e538939149 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.*; import org.apache.fory.Fory; -import org.apache.fory.reflect.ObjectCreators; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.util.GraalvmSupport; import org.junit.After; @@ -83,19 +82,19 @@ public void testGetDescription() { public void testObjectCreatorsProblematicDetection() { assertTrue( "Class without no-arg constructor should be problematic", - ObjectCreators.isProblematicForCreation(PrivateParameterizedConstructorClass.class)); + GraalvmSupport.isProblematicForCreation(PrivateParameterizedConstructorClass.class)); assertFalse( "Public no-arg constructor should not be problematic", - ObjectCreators.isProblematicForCreation(PublicNoArgConstructorClass.class)); + GraalvmSupport.isProblematicForCreation(PublicNoArgConstructorClass.class)); assertFalse( "Protected no-arg constructor should not be problematic", - ObjectCreators.isProblematicForCreation(ProtectedNoArgConstructorClass.class)); + GraalvmSupport.isProblematicForCreation(ProtectedNoArgConstructorClass.class)); assertFalse( "Enums should not be considered problematic", - ObjectCreators.isProblematicForCreation(SampleEnum.class)); + GraalvmSupport.isProblematicForCreation(SampleEnum.class)); } @Test From 25d3203ff78dc346c89a02712dcf3bd6150b4781 Mon Sep 17 00:00:00 2001 From: zhou yong kang Date: Tue, 14 Oct 2025 13:16:28 +0800 Subject: [PATCH 16/46] Update java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java Co-authored-by: Shawn Yang --- .../src/main/java/org/apache/fory/util/GraalvmSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index e552d74911..e48f6769f4 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -155,7 +155,7 @@ public static boolean isRecordConstructorPublicAccessible(Class type) { * @param type the class to check * @return true if the class is problematic for creation, false otherwise */ - public static boolean isProblematicForCreation(Class type) { + public static boolean needReflectionRegisterForCreation(Class type) { if (type.isInterface() || Modifier.isAbstract(type.getModifiers()) || type.isArray() From f896fefe743e98b0d7adca2655f5f9bfba2c04b9 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 14 Oct 2025 13:41:15 +0800 Subject: [PATCH 17/46] fix: replace name --- .../apache/fory/util/GraalvmSupportRecordTest.java | 12 ++++++------ .../fory/graalvm/feature/ForyGraalVMFeature.java | 2 +- .../fory/graalvm/feature/ForyGraalVMFeatureTest.java | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java b/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java index c0e8bce0a1..1017ff522b 100644 --- a/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java @@ -67,7 +67,7 @@ public void testIsRecordConstructorPublicAccessible_WithString() { @Test public void testObjectCreators_IsProblematicForCreation_WithRegularClass() { - boolean result = GraalvmSupport.isProblematicForCreation(RegularClass.class); + boolean result = GraalvmSupport.needReflectionRegisterForCreation(RegularClass.class); Assert.assertFalse( result, "RegularClass with no-arg constructor should not be problematic for creation"); } @@ -89,13 +89,13 @@ public void testBackwardCompatibility_OnOlderJDK() { @Test public void testObjectCreators_BackwardCompatibility() { - Assert.assertFalse(GraalvmSupport.isProblematicForCreation(RegularClass.class)); - Assert.assertFalse(GraalvmSupport.isProblematicForCreation(Object.class)); - Assert.assertFalse(GraalvmSupport.isProblematicForCreation(String.class)); + Assert.assertFalse(GraalvmSupport.needReflectionRegisterForCreation(RegularClass.class)); + Assert.assertFalse(GraalvmSupport.needReflectionRegisterForCreation(Object.class)); + Assert.assertFalse(GraalvmSupport.needReflectionRegisterForCreation(String.class)); - Assert.assertTrue(GraalvmSupport.isProblematicForCreation(PrivateClass.class)); + Assert.assertTrue(GraalvmSupport.needReflectionRegisterForCreation(PrivateClass.class)); - Assert.assertFalse(GraalvmSupport.isProblematicForCreation(Runnable.class)); + Assert.assertFalse(GraalvmSupport.needReflectionRegisterForCreation(Runnable.class)); } @Test diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 75a4ff10d7..600b170ad5 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -73,7 +73,7 @@ public String getDescription() { } private void handleForyClass(Class clazz) { - if (GraalvmSupport.isProblematicForCreation(clazz)) { + if (GraalvmSupport.needReflectionRegisterForCreation(clazz)) { try { RuntimeReflection.registerForReflectiveInstantiation(clazz); for (Field field : clazz.getDeclaredFields()) { diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index e538939149..a2fb081480 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -82,19 +82,19 @@ public void testGetDescription() { public void testObjectCreatorsProblematicDetection() { assertTrue( "Class without no-arg constructor should be problematic", - GraalvmSupport.isProblematicForCreation(PrivateParameterizedConstructorClass.class)); + GraalvmSupport.needReflectionRegisterForCreation(PrivateParameterizedConstructorClass.class)); assertFalse( "Public no-arg constructor should not be problematic", - GraalvmSupport.isProblematicForCreation(PublicNoArgConstructorClass.class)); + GraalvmSupport.needReflectionRegisterForCreation(PublicNoArgConstructorClass.class)); assertFalse( "Protected no-arg constructor should not be problematic", - GraalvmSupport.isProblematicForCreation(ProtectedNoArgConstructorClass.class)); + GraalvmSupport.needReflectionRegisterForCreation(ProtectedNoArgConstructorClass.class)); assertFalse( "Enums should not be considered problematic", - GraalvmSupport.isProblematicForCreation(SampleEnum.class)); + GraalvmSupport.needReflectionRegisterForCreation(SampleEnum.class)); } @Test From e4a98524d858aa320c00e44b31a533763b5f6381 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 14 Oct 2025 13:49:30 +0800 Subject: [PATCH 18/46] fix: checkstyle --- .../apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index a2fb081480..674b1c052b 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -82,7 +82,8 @@ public void testGetDescription() { public void testObjectCreatorsProblematicDetection() { assertTrue( "Class without no-arg constructor should be problematic", - GraalvmSupport.needReflectionRegisterForCreation(PrivateParameterizedConstructorClass.class)); + GraalvmSupport.needReflectionRegisterForCreation( + PrivateParameterizedConstructorClass.class)); assertFalse( "Public no-arg constructor should not be problematic", From 39ad1b768acd03d985644ade2d752e17e9c4fc6f Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 15 Oct 2025 17:49:01 +0800 Subject: [PATCH 19/46] fix: move registerClassForGraalvm --- .../src/main/java/org/apache/fory/Fory.java | 3 --- .../apache/fory/resolver/ClassResolver.java | 1 + .../fory/resolver/ClassResolverTest.java | 21 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index 9d1e0b7920..43b7b74dfb 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -177,13 +177,11 @@ public Fory(ForyBuilder builder, ClassLoader classLoader) { @Override public void register(Class cls) { _getTypeResolver().register(cls); - TypeResolver.registerClassForGraalvm(cls, config.getConfigHash()); } @Override public void register(Class cls, int id) { _getTypeResolver().register(cls, id); - TypeResolver.registerClassForGraalvm(cls, config.getConfigHash()); } @Deprecated @@ -215,7 +213,6 @@ public void register(Class cls, String typeName) { public void register(Class cls, String namespace, String typeName) { _getTypeResolver().register(cls, namespace, typeName); - TypeResolver.registerClassForGraalvm(cls, config.getConfigHash()); } @Override 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 62fda33225..bcfab9fcdc 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 @@ -1796,6 +1796,7 @@ public void ensureSerializersCompiled() { fory, JdkProxySerializer.SUBT_PROXY.getClass(), JdkProxySerializer.class); classInfoMap.forEach( (cls, classInfo) -> { + TypeResolver.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); if (classInfo.serializer == null) { if (isSerializable(classInfo.cls)) { createSerializer0(cls); diff --git a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java index 1960466334..1d62496259 100644 --- a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java @@ -539,4 +539,25 @@ public void testAbstractCustomSerializer() { fory.getClassResolver().getSerializer(abs2Test.getClass()).getClass(), AbstractCustomSerializer.class); } + + static class GraalvmRegistrationBean { + int value; + } + + @Test + public void testEnsureSerializersCompiledRegistersClassesForGraalvm() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(true).build(); + Assert.assertFalse( + TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + fory.register(GraalvmRegistrationBean.class); + + Assert.assertFalse( + TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + fory.ensureSerializersCompiled(); + + Assert.assertTrue( + TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + } } From d5c62e57329ac3ed8aea5b55b07e86f554953546 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 15 Oct 2025 18:23:20 +0800 Subject: [PATCH 20/46] fix: ci test bug --- .../main/java/org/apache/fory/resolver/ClassResolver.java | 2 ++ .../main/java/org/apache/fory/resolver/XtypeResolver.java | 2 ++ .../java/org/apache/fory/resolver/ClassResolverTest.java | 6 ++++++ 3 files changed, 10 insertions(+) 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 bcfab9fcdc..9eefed047e 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 @@ -431,6 +431,7 @@ public void register(Class cls, int classId) { registeredId2ClassInfo[id] = classInfo; extRegistry.registeredClasses.put(cls.getName(), cls); extRegistry.classIdGenerator++; + TypeResolver.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); } public void register(String className, int classId) { @@ -476,6 +477,7 @@ public void register(Class cls, String namespace, String name) { compositeNameBytes2ClassInfo.put( new TypeNameBytes(nsBytes.hashCode, nameBytes.hashCode), classInfo); extRegistry.registeredClasses.put(fullname, cls); + TypeResolver.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); } private void checkRegistration(Class cls, short classId, String name) { diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java index 9d56c4829f..80a8c46d95 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java @@ -166,6 +166,7 @@ public void register(Class type, int userTypeId) { ClassInfo classInfo = classInfoMap.get(type); if (type.isArray()) { buildClassInfo(type); + TypeResolver.registerClassForGraalvm(type, fory.getConfig().getConfigHash()); return; } Serializer serializer = null; @@ -255,6 +256,7 @@ private void register( String qualifiedName = qualifiedName(namespace, typeName); qualifiedType2ClassInfo.put(qualifiedName, classInfo); extRegistry.registeredClasses.put(qualifiedName, type); + TypeResolver.registerClassForGraalvm(type, fory.getConfig().getConfigHash()); if (serializer == null) { if (type.isEnum()) { classInfo.serializer = new EnumSerializer(fory, (Class) type); diff --git a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java index 1d62496259..61a141188a 100644 --- a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java @@ -65,6 +65,7 @@ import org.apache.fory.serializer.collection.MapSerializers; import org.apache.fory.test.bean.BeanB; import org.apache.fory.type.TypeUtils; +import org.apache.fory.util.GraalvmSupport; import org.testng.Assert; import org.testng.annotations.Test; @@ -547,11 +548,16 @@ static class GraalvmRegistrationBean { @Test public void testEnsureSerializersCompiledRegistersClassesForGraalvm() { Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(true).build(); + GraalvmSupport.clearRegistrations(); Assert.assertFalse( TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); fory.register(GraalvmRegistrationBean.class); + Assert.assertTrue( + TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + GraalvmSupport.clearRegistrations(); Assert.assertFalse( TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); From fa7c9048d1003e562caf97e4f52e8fe9dae1947f Mon Sep 17 00:00:00 2001 From: zhou yong kang Date: Thu, 16 Oct 2025 11:50:11 +0800 Subject: [PATCH 21/46] Update integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java Co-authored-by: Shawn Yang --- .../main/java/org/apache/fory/graalvm/FeatureTestExample.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index 50e389fde3..f7ffb8d382 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -118,8 +118,7 @@ public static void main(String[] args) { } catch (Exception e) { System.err.println("GraalVM Feature test failed: " + e.getMessage()); - e.printStackTrace(); - System.exit(1); + throw new RuntimeException(e); } } } From 9db3ce4e69b78175bd8283a753edadf10d7e9b6f Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 21 Oct 2025 23:03:50 +0800 Subject: [PATCH 22/46] fix: move something to GraalvmSupport --- .../fory/graalvm/FeatureTestExample.java | 4 +- .../apache/fory/resolver/ClassResolver.java | 6 +- .../apache/fory/resolver/TypeResolver.java | 117 ++---------------- .../apache/fory/resolver/XtypeResolver.java | 5 +- .../org/apache/fory/util/GraalvmSupport.java | 114 ++++++++++++++++- .../fory/resolver/ClassResolverTest.java | 8 +- .../graalvm/feature/ForyGraalVMFeature.java | 5 +- .../feature/ForyGraalVMFeatureTest.java | 7 +- 8 files changed, 137 insertions(+), 129 deletions(-) diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index f7ffb8d382..32aba2daac 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -24,7 +24,7 @@ import java.lang.reflect.Proxy; import org.apache.fory.Fory; import org.apache.fory.config.Language; -import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.util.GraalvmSupport; public class FeatureTestExample { @@ -85,7 +85,7 @@ public static void main(String[] args) { fory.register(TestInvocationHandler.class); // Register proxy interface - TypeResolver.addProxyInterface(TestInterface.class); + GraalvmSupport.registerProxySupport(TestInterface.class); try { // Test 1: Serialize/deserialize class with private constructor 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 9eefed047e..4ae1fccaa8 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 @@ -431,7 +431,7 @@ public void register(Class cls, int classId) { registeredId2ClassInfo[id] = classInfo; extRegistry.registeredClasses.put(cls.getName(), cls); extRegistry.classIdGenerator++; - TypeResolver.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); + GraalvmSupport.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); } public void register(String className, int classId) { @@ -477,7 +477,7 @@ public void register(Class cls, String namespace, String name) { compositeNameBytes2ClassInfo.put( new TypeNameBytes(nsBytes.hashCode, nameBytes.hashCode), classInfo); extRegistry.registeredClasses.put(fullname, cls); - TypeResolver.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); + GraalvmSupport.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); } private void checkRegistration(Class cls, short classId, String name) { @@ -1798,7 +1798,7 @@ public void ensureSerializersCompiled() { fory, JdkProxySerializer.SUBT_PROXY.getClass(), JdkProxySerializer.class); classInfoMap.forEach( (cls, classInfo) -> { - TypeResolver.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); + GraalvmSupport.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); if (classInfo.serializer == null) { if (isSerializable(classInfo.cls)) { createSerializer0(cls); diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java index 6085abd74a..436c1dc7ff 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java @@ -26,9 +26,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -36,7 +34,6 @@ import java.util.Set; import java.util.SortedMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import org.apache.fory.Fory; import org.apache.fory.annotation.Internal; @@ -572,38 +569,18 @@ public void setTypeChecker(TypeChecker typeChecker) { extRegistry.typeChecker = typeChecker; } - private static final ConcurrentMap GRAALVM_REGISTRY = - new ConcurrentHashMap<>(); - // CHECKSTYLE.OFF:MethodName public static void _addGraalvmClassRegistry(int foryConfigHash, ClassResolver classResolver) { // CHECKSTYLE.ON:MethodName if (GraalvmSupport.isGraalBuildtime()) { - GraalvmClassRegistry registry = - GRAALVM_REGISTRY.computeIfAbsent(foryConfigHash, k -> new GraalvmClassRegistry()); + GraalvmSupport.GraalvmClassRegistry registry = + GraalvmSupport.getGraalvmClassRegistry(foryConfigHash); registry.resolvers.add(classResolver); } } - static class GraalvmClassRegistry { - final List resolvers; - final Map, Class> serializerClassMap; - final Map> deserializerClassMap; - final Set> registeredClasses; - final Set> proxyInterfaces; - - private GraalvmClassRegistry() { - resolvers = Collections.synchronizedList(new ArrayList<>()); - serializerClassMap = new ConcurrentHashMap<>(); - deserializerClassMap = new ConcurrentHashMap<>(); - registeredClasses = ConcurrentHashMap.newKeySet(); - proxyInterfaces = ConcurrentHashMap.newKeySet(); - } - } - - final GraalvmClassRegistry getGraalvmClassRegistry() { - return GRAALVM_REGISTRY.computeIfAbsent( - fory.getConfig().getConfigHash(), k -> new GraalvmClassRegistry()); + final GraalvmSupport.GraalvmClassRegistry getGraalvmClassRegistry() { + return GraalvmSupport.getGraalvmClassRegistry(fory.getConfig().getConfigHash()); } final Class getGraalvmSerializerClass(Serializer serializer) { @@ -614,8 +591,9 @@ final Class getGraalvmSerializerClass(Serializer serialize } final Class getSerializerClassFromGraalvmRegistry(Class cls) { - GraalvmClassRegistry registry = getGraalvmClassRegistry(); - List classResolvers = registry.resolvers; + GraalvmSupport.GraalvmClassRegistry registry = getGraalvmClassRegistry(); + @SuppressWarnings("unchecked") + List classResolvers = (List) (List) registry.resolvers; if (classResolvers.isEmpty()) { return null; } @@ -643,8 +621,9 @@ final Class getSerializerClassFromGraalvmRegistry(Class private Class getMetaSharedDeserializerClassFromGraalvmRegistry( Class cls, ClassDef classDef) { - GraalvmClassRegistry registry = getGraalvmClassRegistry(); - List classResolvers = registry.resolvers; + GraalvmSupport.GraalvmClassRegistry registry = getGraalvmClassRegistry(); + @SuppressWarnings("unchecked") + List classResolvers = (List) (List) registry.resolvers; if (classResolvers.isEmpty()) { return null; } @@ -674,82 +653,6 @@ public final MetaStringResolver getMetaStringResolver() { return metaStringResolver; } - /** - * Get all registered classes across all GraalVM registries for native image compilation. - * - * @return unmodifiable set of all registered classes - */ - public static Set> getAllRegisteredClasses() { - Set> allClasses = ConcurrentHashMap.newKeySet(); - for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { - allClasses.addAll(registry.registeredClasses); - } - return Collections.unmodifiableSet(allClasses); - } - - /** - * Get all registered proxy interfaces across all GraalVM registries for native image compilation. - * - * @return unmodifiable set of all registered proxy interfaces - */ - public static Set> getAllProxyInterfaces() { - Set> allInterfaces = ConcurrentHashMap.newKeySet(); - for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { - allInterfaces.addAll(registry.proxyInterfaces); - } - return Collections.unmodifiableSet(allInterfaces); - } - - /** - * Register a class in the GraalVM registry for native image compilation. This should be called - * from {@link org.apache.fory.Fory#register} methods. - * - * @param cls the class to register - * @param configHash the configuration hash for the Fory instance - */ - public static void registerClassForGraalvm(Class cls, int configHash) { - GraalvmClassRegistry registry = - GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); - registry.registeredClasses.add(cls); - } - - /** - * Register a proxy interface in the GraalVM registry for native image compilation. - * - * @param proxyInterface the proxy interface to register - * @param configHash the configuration hash for the Fory instance - */ - public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int configHash) { - if (proxyInterface == null) { - throw new NullPointerException("Proxy interface must not be null"); - } - if (!proxyInterface.isInterface()) { - throw new IllegalArgumentException( - "Proxy type must be an interface: " + proxyInterface.getName()); - } - GraalvmClassRegistry registry = - GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); - registry.proxyInterfaces.add(proxyInterface); - } - - /** - * Register a proxy interface globally for GraalVM native image compilation. - * - *

This is a convenience wrapper over {@link #registerProxyInterfaceForGraalvm(Class, int)} - * with a shared registration scope. - */ - public static void addProxyInterface(Class proxyInterface) { - registerProxyInterfaceForGraalvm(proxyInterface, 0); - } - - /** Clear all GraalVM registrations. This is primarily for testing purposes. */ - public static void clearGraalvmRegistrations() { - for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { - registry.registeredClasses.clear(); - registry.proxyInterfaces.clear(); - } - } - static class ExtRegistry { // Here we set it to 1 because `NO_CLASS_ID` is 0 to avoid calculating it again in // `register(Class cls)`. diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java index 80a8c46d95..26342faff0 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java @@ -97,6 +97,7 @@ import org.apache.fory.type.Generics; import org.apache.fory.type.TypeUtils; import org.apache.fory.type.Types; +import org.apache.fory.util.GraalvmSupport; import org.apache.fory.util.Preconditions; @SuppressWarnings({"unchecked", "rawtypes"}) @@ -166,7 +167,7 @@ public void register(Class type, int userTypeId) { ClassInfo classInfo = classInfoMap.get(type); if (type.isArray()) { buildClassInfo(type); - TypeResolver.registerClassForGraalvm(type, fory.getConfig().getConfigHash()); + GraalvmSupport.registerClassForGraalvm(type, fory.getConfig().getConfigHash()); return; } Serializer serializer = null; @@ -256,7 +257,7 @@ private void register( String qualifiedName = qualifiedName(namespace, typeName); qualifiedType2ClassInfo.put(qualifiedName, classInfo); extRegistry.registeredClasses.put(qualifiedName, type); - TypeResolver.registerClassForGraalvm(type, fory.getConfig().getConfigHash()); + GraalvmSupport.registerClassForGraalvm(type, fory.getConfig().getConfigHash()); if (serializer == null) { if (type.isEnum()) { classInfo.serializer = new EnumSerializer(fory, (Class) type); diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index e48f6769f4..07b437fb7e 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -21,12 +21,16 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.fory.Fory; import org.apache.fory.exception.ForyException; import org.apache.fory.memory.MemoryBuffer; -import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.Serializer; import org.apache.fory.util.record.RecordUtils; @@ -41,6 +45,9 @@ public class GraalvmSupport { private static final String GRAAL_IMAGE_BUILDTIME = "buildtime"; private static final String GRAAL_IMAGE_RUNTIME = "runtime"; + private static final Map GRAALVM_REGISTRY = + new ConcurrentHashMap<>(); + static { String imageCode = System.getProperty(GRAAL_IMAGE_CODE_KEY); IN_GRAALVM_NATIVE_IMAGE = imageCode != null; @@ -62,17 +69,91 @@ public static boolean isGraalRuntime() { * Returns all classes registered for GraalVM native image compilation across all configurations. */ public static Set> getRegisteredClasses() { - return TypeResolver.getAllRegisteredClasses(); + return getAllRegisteredClasses(); } /** Returns all proxy interfaces registered for GraalVM native image compilation. */ public static Set> getProxyInterfaces() { - return TypeResolver.getAllProxyInterfaces(); + return getAllProxyInterfaces(); } /** Clears all GraalVM native image registrations. Primarily for testing purposes. */ public static void clearRegistrations() { - TypeResolver.clearGraalvmRegistrations(); + clearGraalvmRegistrations(); + } + + /** + * Get all registered classes across all GraalVM registries for native image compilation. + * + * @return unmodifiable set of all registered classes + */ + public static Set> getAllRegisteredClasses() { + Set> allClasses = ConcurrentHashMap.newKeySet(); + for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { + allClasses.addAll(registry.registeredClasses); + } + return Collections.unmodifiableSet(allClasses); + } + + /** + * Get all registered proxy interfaces across all GraalVM registries for native image compilation. + * + * @return unmodifiable set of all registered proxy interfaces + */ + public static Set> getAllProxyInterfaces() { + Set> allInterfaces = ConcurrentHashMap.newKeySet(); + for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { + allInterfaces.addAll(registry.proxyInterfaces); + } + return Collections.unmodifiableSet(allInterfaces); + } + + /** + * Register a class in the GraalVM registry for native image compilation. + * + * @param cls the class to register + * @param configHash the configuration hash for the Fory instance + */ + public static void registerClassForGraalvm(Class cls, int configHash) { + GraalvmClassRegistry registry = + GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); + registry.registeredClasses.add(cls); + } + + /** + * Register a proxy interface in the GraalVM registry for native image compilation. + * + * @param proxyInterface the proxy interface to register + * @param configHash the configuration hash for the Fory instance + */ + public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int configHash) { + if (proxyInterface == null) { + throw new NullPointerException("Proxy interface must not be null"); + } + if (!proxyInterface.isInterface()) { + throw new IllegalArgumentException( + "Proxy type must be an interface: " + proxyInterface.getName()); + } + GraalvmClassRegistry registry = + GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); + registry.proxyInterfaces.add(proxyInterface); + } + + /** + * Register proxy support for GraalVM native image compilation. + * + * @param proxyInterface the proxy interface to register + */ + public static void registerProxySupport(Class proxyInterface) { + registerProxyInterfaceForGraalvm(proxyInterface, 0); + } + + /** Clear all GraalVM registrations. This is primarily for testing purposes. */ + public static void clearGraalvmRegistrations() { + for (GraalvmClassRegistry registry : GRAALVM_REGISTRY.values()) { + registry.registeredClasses.clear(); + registry.proxyInterfaces.clear(); + } } public static class GraalvmSerializerHolder extends Serializer { @@ -178,4 +259,29 @@ public static boolean needReflectionRegisterForCreation(Class type) { } return true; } + + /** + * Get the GraalVM class registry for a specific configuration hash. Package-private method for + * use by TypeResolver and ClassResolver. + */ + public static GraalvmClassRegistry getGraalvmClassRegistry(int configHash) { + return GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); + } + + /** GraalVM class registry. */ + public static class GraalvmClassRegistry { + public final List resolvers; + public final Map, Class> serializerClassMap; + public final Map> deserializerClassMap; + public final Set> registeredClasses; + public final Set> proxyInterfaces; + + private GraalvmClassRegistry() { + resolvers = Collections.synchronizedList(new ArrayList<>()); + serializerClassMap = new ConcurrentHashMap<>(); + deserializerClassMap = new ConcurrentHashMap<>(); + registeredClasses = ConcurrentHashMap.newKeySet(); + proxyInterfaces = ConcurrentHashMap.newKeySet(); + } + } } diff --git a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java index 61a141188a..b999ae7d46 100644 --- a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java @@ -550,20 +550,20 @@ public void testEnsureSerializersCompiledRegistersClassesForGraalvm() { Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(true).build(); GraalvmSupport.clearRegistrations(); Assert.assertFalse( - TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); fory.register(GraalvmRegistrationBean.class); Assert.assertTrue( - TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); GraalvmSupport.clearRegistrations(); Assert.assertFalse( - TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); fory.ensureSerializersCompiled(); Assert.assertTrue( - TypeResolver.getAllRegisteredClasses().contains(GraalvmRegistrationBean.class)); + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); } } diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 600b170ad5..0b165e97c1 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -22,7 +22,6 @@ import java.lang.reflect.Field; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.apache.fory.resolver.TypeResolver; import org.apache.fory.util.GraalvmSupport; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -48,14 +47,14 @@ public class ForyGraalVMFeature implements Feature { public void duringAnalysis(DuringAnalysisAccess access) { boolean changed = false; - for (Class clazz : TypeResolver.getAllRegisteredClasses()) { + for (Class clazz : GraalvmSupport.getRegisteredClasses()) { if (processedClasses.add(clazz)) { handleForyClass(clazz); changed = true; } } - for (Class proxyInterface : TypeResolver.getAllProxyInterfaces()) { + for (Class proxyInterface : GraalvmSupport.getProxyInterfaces()) { if (processedProxyInterfaces.add(proxyInterface)) { RuntimeReflection.register(proxyInterface); RuntimeReflection.register(proxyInterface.getMethods()); diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index 674b1c052b..dbf45f7e5d 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.*; import org.apache.fory.Fory; -import org.apache.fory.resolver.TypeResolver; import org.apache.fory.util.GraalvmSupport; import org.junit.After; import org.junit.Before; @@ -115,7 +114,7 @@ public void testFeatureInstantiation() { @Test public void testAddProxyInterfaceRejectsNull() { try { - TypeResolver.addProxyInterface(null); + GraalvmSupport.registerProxySupport(null); fail("Null proxy interface should throw NullPointerException"); } catch (NullPointerException expected) { // expected @@ -125,7 +124,7 @@ public void testAddProxyInterfaceRejectsNull() { @Test public void testAddProxyInterfaceRejectsNonInterface() { try { - TypeResolver.addProxyInterface(NonInterfaceProxy.class); + GraalvmSupport.registerProxySupport(NonInterfaceProxy.class); fail("Non-interface proxy type should throw IllegalArgumentException"); } catch (IllegalArgumentException expected) { // expected @@ -137,7 +136,7 @@ public void testClearRegistrationsResetsState() { Fory builderInstance = Fory.builder().build(); GraalvmSupport.clearRegistrations(); builderInstance.register(PublicNoArgConstructorClass.class); - TypeResolver.addProxyInterface(SampleProxyInterface.class); + GraalvmSupport.registerProxySupport(SampleProxyInterface.class); assertFalse(GraalvmSupport.getRegisteredClasses().isEmpty()); assertFalse(GraalvmSupport.getProxyInterfaces().isEmpty()); From e39fff855f3bb09bf2b2df9060669c0ed49ada4b Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 21 Oct 2025 23:54:09 +0800 Subject: [PATCH 23/46] fix: only init in Graalvm --- .../org/apache/fory/util/GraalvmSupport.java | 6 +++++ .../feature/ForyGraalVMFeatureTest.java | 24 ++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index 07b437fb7e..a862b31b7c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -115,6 +115,9 @@ public static Set> getAllProxyInterfaces() { * @param configHash the configuration hash for the Fory instance */ public static void registerClassForGraalvm(Class cls, int configHash) { + if (!IN_GRAALVM_NATIVE_IMAGE) { + return; + } GraalvmClassRegistry registry = GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); registry.registeredClasses.add(cls); @@ -127,6 +130,9 @@ public static void registerClassForGraalvm(Class cls, int configHash) { * @param configHash the configuration hash for the Fory instance */ public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int configHash) { + if (!IN_GRAALVM_NATIVE_IMAGE) { + return; + } if (proxyInterface == null) { throw new NullPointerException("Proxy interface must not be null"); } diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index dbf45f7e5d..17de7d9a89 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -113,21 +113,29 @@ public void testFeatureInstantiation() { @Test public void testAddProxyInterfaceRejectsNull() { - try { + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { + try { + GraalvmSupport.registerProxySupport(null); + fail("Null proxy interface should throw NullPointerException"); + } catch (NullPointerException expected) { + // expected + } + } else { GraalvmSupport.registerProxySupport(null); - fail("Null proxy interface should throw NullPointerException"); - } catch (NullPointerException expected) { - // expected } } @Test public void testAddProxyInterfaceRejectsNonInterface() { - try { + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { + try { + GraalvmSupport.registerProxySupport(NonInterfaceProxy.class); + fail("Non-interface proxy type should throw IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + // expected + } + } else { GraalvmSupport.registerProxySupport(NonInterfaceProxy.class); - fail("Non-interface proxy type should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // expected } } From cf7f88d3afc975b2545dcbd719be4e864b12f6e3 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 00:27:09 +0800 Subject: [PATCH 24/46] fix: test up --- .../src/main/java/org/apache/fory/util/GraalvmSupport.java | 3 +++ java/fory-graalvm-feature/pom.xml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index a862b31b7c..f87ea8f4b7 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -271,6 +271,9 @@ public static boolean needReflectionRegisterForCreation(Class type) { * use by TypeResolver and ClassResolver. */ public static GraalvmClassRegistry getGraalvmClassRegistry(int configHash) { + if (!IN_GRAALVM_NATIVE_IMAGE) { + return new GraalvmClassRegistry(); + } return GRAALVM_REGISTRY.computeIfAbsent(configHash, k -> new GraalvmClassRegistry()); } diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index 05d9ecafec..6f40d5a65a 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -44,6 +44,7 @@ org.apache.fory fory-core ${project.version} + compile org.graalvm.sdk @@ -74,6 +75,9 @@ org.apache.maven.plugins maven-compiler-plugin + + true + From 761dc7462284d413073b24ab0127a72d1a55f4cf Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 00:36:21 +0800 Subject: [PATCH 25/46] fix: modules bug --- java/pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/pom.xml b/java/pom.xml index 69bc216fc6..36b4c68f52 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -92,6 +92,9 @@ [17,] + fory-core + fory-extensions + fory-test-core fory-simd fory-graalvm-feature From f0790307f534b46ac18ea0387e0f5165bd795e42 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 11:22:10 +0800 Subject: [PATCH 26/46] fix: pom test --- java/pom.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index 36b4c68f52..e781851055 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -86,19 +86,19 @@ fory-testsuite - - jdk17-and-higher - - [17,] - - - fory-core - fory-extensions - fory-test-core - fory-simd - fory-graalvm-feature - - + + jdk17-and-higher + + [17,] + + + fory-core + fory-extensions + fory-test-core + fory-simd + fory-graalvm-feature + + From 9984a8f69967c345c8a5ac557ed8064b9fa3f470 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 11:38:45 +0800 Subject: [PATCH 27/46] fix: pom bug --- java/fory-graalvm-feature/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index 6f40d5a65a..db1fa37220 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -25,7 +25,7 @@ fory-parent org.apache.fory - 0.13.0-SNAPSHOT + 0.13.0 ../pom.xml 4.0.0 From 5992a1ccb46d3d265f79befbae0cbae25dfd755e Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 11:48:50 +0800 Subject: [PATCH 28/46] Update pom.xml --- java/pom.xml | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index e781851055..f4812f8555 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -86,19 +86,21 @@ fory-testsuite - - jdk17-and-higher - - [17,] - - - fory-core - fory-extensions - fory-test-core - fory-simd - fory-graalvm-feature - - + + jdk17-and-higher + + [17,] + + + fory-core + fory-extensions + fory-test-core + fory-simd + fory-graalvm-feature + fory-format + fory-testsuite + + From 545bb1395bc4a3701722686cc176d1ce2daa3fff Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 12:51:04 +0800 Subject: [PATCH 29/46] fix: pom.xml --- integration_tests/graalvm_tests/pom.xml | 2 +- java/fory-graalvm-feature/pom.xml | 2 +- .../feature/ForyGraalVMFeatureTest.java | 20 ++++++++++++++----- java/pom.xml | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index f3c26db307..16b0a72b91 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -25,7 +25,7 @@ org.apache.fory fory-parent - 0.13.0 + 0.13.0-SNAPSHOT ../../java 4.0.0 diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index db1fa37220..6f40d5a65a 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -25,7 +25,7 @@ fory-parent org.apache.fory - 0.13.0 + 0.13.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index 17de7d9a89..4aa16cb31f 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -146,12 +146,22 @@ public void testClearRegistrationsResetsState() { builderInstance.register(PublicNoArgConstructorClass.class); GraalvmSupport.registerProxySupport(SampleProxyInterface.class); - assertFalse(GraalvmSupport.getRegisteredClasses().isEmpty()); - assertFalse(GraalvmSupport.getProxyInterfaces().isEmpty()); + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { + assertFalse(GraalvmSupport.getRegisteredClasses().isEmpty()); + assertFalse(GraalvmSupport.getProxyInterfaces().isEmpty()); - GraalvmSupport.clearRegistrations(); + GraalvmSupport.clearRegistrations(); + + assertTrue(GraalvmSupport.getRegisteredClasses().isEmpty()); + assertTrue(GraalvmSupport.getProxyInterfaces().isEmpty()); + } else { + assertTrue(GraalvmSupport.getRegisteredClasses().isEmpty()); + assertTrue(GraalvmSupport.getProxyInterfaces().isEmpty()); + + GraalvmSupport.clearRegistrations(); - assertTrue(GraalvmSupport.getRegisteredClasses().isEmpty()); - assertTrue(GraalvmSupport.getProxyInterfaces().isEmpty()); + assertTrue(GraalvmSupport.getRegisteredClasses().isEmpty()); + assertTrue(GraalvmSupport.getProxyInterfaces().isEmpty()); + } } } diff --git a/java/pom.xml b/java/pom.xml index f4812f8555..5fe2db587c 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -33,7 +33,7 @@ org.apache.fory fory-parent pom - 0.13.0 + 0.13.0-SNAPSHOT Fory Project Parent POM Apache Fory™ is a blazingly fast multi-language serialization framework powered by jit and zero-copy. From d3d0e2ba764aafc61cf34b025ed36976925f1ea2 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 13:00:55 +0800 Subject: [PATCH 30/46] fix: test bugs --- .../graalvm_tests/native-image.properties | 1 + .../fory/resolver/ClassResolverTest.java | 30 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties index 60369d6826..06dfaea2af 100644 --- a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties +++ b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties @@ -33,4 +33,5 @@ Args=-H:+ReportExceptionStackTraces \ org.apache.fory.graalvm.CollectionExample,\ org.apache.fory.graalvm.ArrayExample,\ org.apache.fory.graalvm.Benchmark,\ + org.apache.fory.graalvm.FeatureTestExample,\ org.apache.fory.graalvm.Main diff --git a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java index b999ae7d46..526dfb0324 100644 --- a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java @@ -554,16 +554,30 @@ public void testEnsureSerializersCompiledRegistersClassesForGraalvm() { fory.register(GraalvmRegistrationBean.class); - Assert.assertTrue( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { + Assert.assertTrue( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - GraalvmSupport.clearRegistrations(); - Assert.assertFalse( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + GraalvmSupport.clearRegistrations(); + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - fory.ensureSerializersCompiled(); + fory.ensureSerializersCompiled(); - Assert.assertTrue( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + Assert.assertTrue( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + } else { + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + GraalvmSupport.clearRegistrations(); + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + fory.ensureSerializersCompiled(); + + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + } } } From 11e4d954c314807635e6edbb54a08556c3bc4e52 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 22 Oct 2025 16:18:03 +0800 Subject: [PATCH 31/46] Update native-image.properties --- .../org.apache.fory/graalvm_tests/native-image.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties index 06dfaea2af..06ae59b42f 100644 --- a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties +++ b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties @@ -34,4 +34,5 @@ Args=-H:+ReportExceptionStackTraces \ org.apache.fory.graalvm.ArrayExample,\ org.apache.fory.graalvm.Benchmark,\ org.apache.fory.graalvm.FeatureTestExample,\ - org.apache.fory.graalvm.Main + org.apache.fory.graalvm.Main,\ + org.apache.fory.util.GraalvmSupport$GraalvmClassRegistry From cb789a5a640f8f0860389280f1fb889c692b7a30 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 23 Oct 2025 21:50:19 +0800 Subject: [PATCH 32/46] fix: move someting --- integration_tests/graalvm_tests/pom.xml | 6 ++++++ .../java/org/apache/fory/graalvm/FeatureTestExample.java | 9 +++++++++ .../graalvm_tests/native-image.properties | 5 +---- .../fory/graalvm/feature/ForyGraalVMFeatureTest.java | 0 java/fory-core/pom.xml | 2 +- .../org.apache.fory/fory-core/native-image.properties | 3 +++ java/pom.xml | 5 ----- 7 files changed, 20 insertions(+), 10 deletions(-) rename {java/fory-graalvm-feature => integration_tests/graalvm_tests}/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java (100%) diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 16b0a72b91..95a547c9b7 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -64,6 +64,12 @@ fory-graalvm-feature ${project.version} + + junit + junit + 4.13.1 + test + diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index 32aba2daac..d1a5ac76b2 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.apache.fory.Fory; +import org.apache.fory.builder.Generated; import org.apache.fory.config.Language; import org.apache.fory.util.GraalvmSupport; @@ -93,6 +94,14 @@ public static void main(String[] args) { byte[] serialized = fory.serialize(original); PrivateConstructorClass deserialized = (PrivateConstructorClass) fory.deserialize(serialized); + // Assert that the serializer is generated + if (!(fory.getClassResolver().getSerializer(PrivateConstructorClass.class) + instanceof Generated)) { + throw new RuntimeException( + "Expected Generated serializer for PrivateConstructorClass but got: " + + fory.getClassResolver().getSerializer(PrivateConstructorClass.class).getClass()); + } + if (!"test-value".equals(deserialized.getValue())) { throw new RuntimeException("Private constructor class test failed"); } diff --git a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties index 06ae59b42f..53e708a917 100644 --- a/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties +++ b/integration_tests/graalvm_tests/src/main/resources/META-INF/native-image/org.apache.fory/graalvm_tests/native-image.properties @@ -32,7 +32,4 @@ Args=-H:+ReportExceptionStackTraces \ org.apache.fory.graalvm.EnsureSerializerExample$CustomSerializer,\ org.apache.fory.graalvm.CollectionExample,\ org.apache.fory.graalvm.ArrayExample,\ - org.apache.fory.graalvm.Benchmark,\ - org.apache.fory.graalvm.FeatureTestExample,\ - org.apache.fory.graalvm.Main,\ - org.apache.fory.util.GraalvmSupport$GraalvmClassRegistry + org.apache.fory.graalvm.Benchmark diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java similarity index 100% rename from java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java rename to integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java diff --git a/java/fory-core/pom.xml b/java/fory-core/pom.xml index 10fd8138a1..3b8a084db4 100644 --- a/java/fory-core/pom.xml +++ b/java/fory-core/pom.xml @@ -25,7 +25,7 @@ org.apache.fory fory-parent - 0.13.0 + 0.13.0-SNAPSHOT 4.0.0 diff --git a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties index e65de39acd..b9fa3bf5a0 100644 --- a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties +++ b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties @@ -488,7 +488,10 @@ Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\ org.apache.fory.util.ClassLoaderUtils,\ org.apache.fory.util.DelayedRef,\ org.apache.fory.util.function.Functions,\ + org.apache.fory.graalvm.FeatureTestExample,\ + org.apache.fory.graalvm.Main,\ org.apache.fory.util.GraalvmSupport,\ + org.apache.fory.util.GraalvmSupport$GraalvmClassRegistry,\ org.apache.fory.util.GraalvmSupport$GraalvmSerializerHolder,\ org.apache.fory.util.LoaderBinding$1,\ org.apache.fory.util.LoaderBinding$StagingType,\ diff --git a/java/pom.xml b/java/pom.xml index 5fe2db587c..ca4dd767b9 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -92,13 +92,8 @@ [17,] - fory-core - fory-extensions - fory-test-core fory-simd fory-graalvm-feature - fory-format - fory-testsuite From 69f3a25e5d778253cf6e0312d469ba7b5ce1bda3 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 23 Oct 2025 22:03:14 +0800 Subject: [PATCH 33/46] Update pom.xml --- java/fory-graalvm-feature/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index 6f40d5a65a..a27c6256c9 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -79,6 +79,13 @@ true + + org.apache.maven.plugins + maven-surefire-plugin + + false + + From d2769151041fd4c4f9905a9de21af9140b86eb46 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 23 Oct 2025 23:06:37 +0800 Subject: [PATCH 34/46] feat: replace junit --- integration_tests/graalvm_tests/pom.xml | 6 ++-- .../feature/ForyGraalVMFeatureTest.java | 36 +++++++++---------- java/fory-graalvm-feature/pom.xml | 4 +-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 95a547c9b7..6f48697551 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -35,7 +35,6 @@ 17 UTF-8 0.9.28 - 5.8.1 org.apache.fory.graalvm.Main main @@ -65,9 +64,8 @@ ${project.version} - junit - junit - 4.13.1 + org.testng + testng test diff --git a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index 4aa16cb31f..a222f52f26 100644 --- a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -19,13 +19,13 @@ package org.apache.fory.graalvm.feature; -import static org.junit.Assert.*; +import static org.testng.Assert.*; import org.apache.fory.Fory; import org.apache.fory.util.GraalvmSupport; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; public class ForyGraalVMFeatureTest { @@ -58,13 +58,13 @@ public enum SampleEnum { VALUE } - @Before + @BeforeMethod public void setUp() { GraalvmSupport.clearRegistrations(); feature = new ForyGraalVMFeature(); } - @After + @AfterMethod public void tearDown() { GraalvmSupport.clearRegistrations(); } @@ -80,35 +80,35 @@ public void testGetDescription() { @Test public void testObjectCreatorsProblematicDetection() { assertTrue( - "Class without no-arg constructor should be problematic", GraalvmSupport.needReflectionRegisterForCreation( - PrivateParameterizedConstructorClass.class)); + PrivateParameterizedConstructorClass.class), + "Class without no-arg constructor should be problematic"); assertFalse( - "Public no-arg constructor should not be problematic", - GraalvmSupport.needReflectionRegisterForCreation(PublicNoArgConstructorClass.class)); + GraalvmSupport.needReflectionRegisterForCreation(PublicNoArgConstructorClass.class), + "Public no-arg constructor should not be problematic"); assertFalse( - "Protected no-arg constructor should not be problematic", - GraalvmSupport.needReflectionRegisterForCreation(ProtectedNoArgConstructorClass.class)); + GraalvmSupport.needReflectionRegisterForCreation(ProtectedNoArgConstructorClass.class), + "Protected no-arg constructor should not be problematic"); assertFalse( - "Enums should not be considered problematic", - GraalvmSupport.needReflectionRegisterForCreation(SampleEnum.class)); + GraalvmSupport.needReflectionRegisterForCreation(SampleEnum.class), + "Enums should not be considered problematic"); } @Test public void testForyStaticMethods() { // Test that Fory static methods are accessible - assertNotNull("Registered classes should not be null", GraalvmSupport.getRegisteredClasses()); + assertNotNull(GraalvmSupport.getRegisteredClasses(), "Registered classes should not be null"); - assertNotNull("Proxy interfaces should not be null", GraalvmSupport.getProxyInterfaces()); + assertNotNull(GraalvmSupport.getProxyInterfaces(), "Proxy interfaces should not be null"); } @Test public void testFeatureInstantiation() { - assertNotNull("Feature should be instantiated", feature); - assertNotNull("Feature description should not be null", feature.getDescription()); + assertNotNull(feature, "Feature should be instantiated"); + assertNotNull(feature.getDescription(), "Feature description should not be null"); } @Test diff --git a/java/fory-graalvm-feature/pom.xml b/java/fory-graalvm-feature/pom.xml index a27c6256c9..bf6fe74014 100644 --- a/java/fory-graalvm-feature/pom.xml +++ b/java/fory-graalvm-feature/pom.xml @@ -53,8 +53,8 @@ provided - junit - junit + org.testng + testng test From 23c81c9ca9386cd46e03c77e376fd351c515b6b0 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Thu, 23 Oct 2025 23:56:15 +0800 Subject: [PATCH 35/46] fix: something error --- integration_tests/graalvm_tests/pom.xml | 6 ++ .../fory/graalvm/GraalvmRegistrationTest.java | 69 +++++++++++++++++++ .../org/apache/fory/util/GraalvmSupport.java | 4 +- .../fory/resolver/ClassResolverTest.java | 41 ----------- 4 files changed, 77 insertions(+), 43 deletions(-) create mode 100644 integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 6f48697551..5ea2113fcb 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -63,6 +63,12 @@ fory-graalvm-feature ${project.version} + + org.graalvm.sdk + graal-sdk + 23.0.0 + provided + org.testng testng diff --git a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java new file mode 100644 index 0000000000..2032465fc6 --- /dev/null +++ b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java @@ -0,0 +1,69 @@ +/* + * 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.graalvm; + +import org.apache.fory.Fory; +import org.apache.fory.config.Language; +import org.apache.fory.util.GraalvmSupport; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class GraalvmRegistrationTest { + + static class GraalvmRegistrationBean { + int value; + } + + @Test + public void testEnsureSerializersCompiledRegistersClassesForGraalvm() { + Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(true).build(); + GraalvmSupport.clearRegistrations(); + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + fory.register(GraalvmRegistrationBean.class); + + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { + Assert.assertTrue( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + GraalvmSupport.clearRegistrations(); + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + fory.ensureSerializersCompiled(); + + Assert.assertTrue( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + } else { + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + GraalvmSupport.clearRegistrations(); + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + + fory.ensureSerializersCompiled(); + + Assert.assertFalse( + GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); + } + } +} \ No newline at end of file diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index f87ea8f4b7..0100514427 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -129,7 +129,7 @@ public static void registerClassForGraalvm(Class cls, int configHash) { * @param proxyInterface the proxy interface to register * @param configHash the configuration hash for the Fory instance */ - public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int configHash) { + public static void registerProxyInterface(Class proxyInterface, int configHash) { if (!IN_GRAALVM_NATIVE_IMAGE) { return; } @@ -151,7 +151,7 @@ public static void registerProxyInterfaceForGraalvm(Class proxyInterface, int * @param proxyInterface the proxy interface to register */ public static void registerProxySupport(Class proxyInterface) { - registerProxyInterfaceForGraalvm(proxyInterface, 0); + registerProxyInterface(proxyInterface, 0); } /** Clear all GraalVM registrations. This is primarily for testing purposes. */ diff --git a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java index 526dfb0324..1960466334 100644 --- a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java @@ -65,7 +65,6 @@ import org.apache.fory.serializer.collection.MapSerializers; import org.apache.fory.test.bean.BeanB; import org.apache.fory.type.TypeUtils; -import org.apache.fory.util.GraalvmSupport; import org.testng.Assert; import org.testng.annotations.Test; @@ -540,44 +539,4 @@ public void testAbstractCustomSerializer() { fory.getClassResolver().getSerializer(abs2Test.getClass()).getClass(), AbstractCustomSerializer.class); } - - static class GraalvmRegistrationBean { - int value; - } - - @Test - public void testEnsureSerializersCompiledRegistersClassesForGraalvm() { - Fory fory = Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(true).build(); - GraalvmSupport.clearRegistrations(); - Assert.assertFalse( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - - fory.register(GraalvmRegistrationBean.class); - - if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { - Assert.assertTrue( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - - GraalvmSupport.clearRegistrations(); - Assert.assertFalse( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - - fory.ensureSerializersCompiled(); - - Assert.assertTrue( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - } else { - Assert.assertFalse( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - - GraalvmSupport.clearRegistrations(); - Assert.assertFalse( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - - fory.ensureSerializersCompiled(); - - Assert.assertFalse( - GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); - } - } } From 332c960791c542393cce5e6be6f8896f5ff0944e Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 24 Oct 2025 10:02:58 +0800 Subject: [PATCH 36/46] fix: test in graalvm --- integration_tests/graalvm_tests/pom.xml | 12 ++++-------- .../org/apache/fory/graalvm/FeatureTestExample.java | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 5ea2113fcb..7fc02af896 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -199,17 +199,13 @@ java-agent - exec + java test - java - ${project.build.directory} - - -classpath - - ${mainClass} - + ${mainClass} + true + false diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java index d1a5ac76b2..4ce7f6075d 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/FeatureTestExample.java @@ -80,7 +80,7 @@ public static void main(String[] args) { System.out.println("Testing Fory GraalVM Feature..."); Fory fory = - Fory.builder().withLanguage(Language.JAVA).withRefTracking(true).withCodegen(false).build(); + Fory.builder().withLanguage(Language.JAVA).withRefTracking(true).withCodegen(true).build(); fory.register(PrivateConstructorClass.class); fory.register(TestInvocationHandler.class); From e787e3f6104259e95fafb941afba7c1d9c360553 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 24 Oct 2025 12:52:09 +0800 Subject: [PATCH 37/46] fix: test --- integration_tests/graalvm_tests/pom.xml | 16 ++-------------- .../fory/util/GraalvmSupportRecordTest.java | 6 ++++-- .../org/apache/fory/resolver/ClassResolver.java | 6 ++++-- .../fory/serializer/JdkProxySerializer.java | 7 ++++++- .../org/apache/fory/util/GraalvmSupport.java | 8 ++++---- .../fory/graalvm/feature/ForyGraalVMFeature.java | 5 +++-- .../graalvm/feature/ForyGraalVMFeatureTest.java | 13 +++++++------ 7 files changed, 30 insertions(+), 31 deletions(-) rename {java/fory-core => integration_tests/graalvm_tests}/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java (95%) rename {integration_tests/graalvm_tests => java/fory-graalvm-feature}/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java (92%) diff --git a/integration_tests/graalvm_tests/pom.xml b/integration_tests/graalvm_tests/pom.xml index 7fc02af896..acf2195897 100644 --- a/integration_tests/graalvm_tests/pom.xml +++ b/integration_tests/graalvm_tests/pom.xml @@ -63,12 +63,6 @@ fory-graalvm-feature ${project.version} - - org.graalvm.sdk - graal-sdk - 23.0.0 - provided - org.testng testng @@ -164,20 +158,14 @@ ${native.maven.plugin.version} true + build-native - build + compile-no-fork package - - test-native - - test - - test - false diff --git a/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java similarity index 95% rename from java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java rename to integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java index 1017ff522b..e1be2f039d 100644 --- a/java/fory-core/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java +++ b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java @@ -66,10 +66,11 @@ public void testIsRecordConstructorPublicAccessible_WithString() { } @Test - public void testObjectCreators_IsProblematicForCreation_WithRegularClass() { + public void testObjectCreators_ReflectiveInstantiationFlag_WithRegularClass() { boolean result = GraalvmSupport.needReflectionRegisterForCreation(RegularClass.class); Assert.assertFalse( - result, "RegularClass with no-arg constructor should not be problematic for creation"); + result, + "RegularClass with no-arg constructor does not require reflective instantiation registration"); } @Test @@ -109,3 +110,4 @@ public void testRecordUtilsIntegration() { Assert.assertFalse(GraalvmSupport.isRecordConstructorPublicAccessible(String.class)); } } + 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 4ae1fccaa8..d31118121e 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 @@ -1794,8 +1794,10 @@ public void ensureSerializersCompiled() { try { fory.getJITContext().lock(); Serializers.newSerializer(fory, LambdaSerializer.STUB_LAMBDA_CLASS, LambdaSerializer.class); - Serializers.newSerializer( - fory, JdkProxySerializer.SUBT_PROXY.getClass(), JdkProxySerializer.class); + if (!GraalvmSupport.isGraalRuntime()) { + Serializers.newSerializer( + fory, JdkProxySerializer.SUBT_PROXY.getClass(), JdkProxySerializer.class); + } classInfoMap.forEach( (cls, classInfo) -> { GraalvmSupport.registerClassForGraalvm(cls, fory.getConfig().getConfigHash()); diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java index 9c2bc7418c..38522d8dcb 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/JdkProxySerializer.java @@ -28,6 +28,7 @@ import org.apache.fory.memory.Platform; import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.resolver.RefResolver; +import org.apache.fory.util.GraalvmSupport; import org.apache.fory.util.Preconditions; /** Serializer for jdk {@link Proxy}. */ @@ -62,7 +63,11 @@ private interface StubInterface { public JdkProxySerializer(Fory fory, Class cls) { super(fory, cls); if (cls != ReplaceStub.class) { - Preconditions.checkArgument(ReflectionUtils.isJdkProxy(cls), "Require a jdk proxy class"); + // Skip proxy class validation in GraalVM native image runtime to avoid issues with proxy + // detection + if (!GraalvmSupport.isGraalRuntime()) { + Preconditions.checkArgument(ReflectionUtils.isJdkProxy(cls), "Require a jdk proxy class"); + } } } diff --git a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java index 0100514427..942fbabc3a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java @@ -234,13 +234,13 @@ public static boolean isRecordConstructorPublicAccessible(Class type) { } /** - * Checks if a class is problematic for object creation and requires special handling in GraalVM. + * Checks whether a class requires reflective instantiation handling in GraalVM. * - *

A class is considered problematic if it lacks a public no-arg constructor and would - * typically require ReflectionFactory or unsafe allocation for instantiation. + *

Returns true when the class does not expose an accessible no-arg constructor and therefore + * needs reflective registration for instantiation during native image builds. * * @param type the class to check - * @return true if the class is problematic for creation, false otherwise + * @return true if reflective instantiation handling is required, false otherwise */ public static boolean needReflectionRegisterForCreation(Class type) { if (type.isInterface() diff --git a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java index 0b165e97c1..7ccaa29a20 100644 --- a/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java +++ b/java/fory-graalvm-feature/src/main/java/org/apache/fory/graalvm/feature/ForyGraalVMFeature.java @@ -33,7 +33,8 @@ * to ensure Fory serialization works correctly at runtime. It handles: * *

    - *
  • Registering problematic classes for unsafe allocation + *
  • Registering classes that require reflective instantiation (no accessible no-arg + * constructor) *
  • Registering field reflection access for serialization *
  • Registering proxy interfaces for dynamic proxy creation *
@@ -68,7 +69,7 @@ public void duringAnalysis(DuringAnalysisAccess access) { } public String getDescription() { - return "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation."; + return "Fory GraalVM Feature: Registers classes for serialization and proxy support."; } private void handleForyClass(Class clazz) { diff --git a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java similarity index 92% rename from integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java rename to java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index a222f52f26..ae0a9aa762 100644 --- a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -73,28 +73,28 @@ public void tearDown() { public void testGetDescription() { String description = feature.getDescription(); assertEquals( - "Fory GraalVM Feature: Registers classes for serialization, proxying, and unsafe allocation.", + "Fory GraalVM Feature: Registers classes for serialization and proxy support.", description); } @Test - public void testObjectCreatorsProblematicDetection() { + public void testObjectCreatorsDetection() { assertTrue( GraalvmSupport.needReflectionRegisterForCreation( PrivateParameterizedConstructorClass.class), - "Class without no-arg constructor should be problematic"); + "Class without no-arg constructor requires reflective instantiation registration"); assertFalse( GraalvmSupport.needReflectionRegisterForCreation(PublicNoArgConstructorClass.class), - "Public no-arg constructor should not be problematic"); + "Public no-arg constructor does not require reflective instantiation registration"); assertFalse( GraalvmSupport.needReflectionRegisterForCreation(ProtectedNoArgConstructorClass.class), - "Protected no-arg constructor should not be problematic"); + "Protected no-arg constructor does not require reflective instantiation registration"); assertFalse( GraalvmSupport.needReflectionRegisterForCreation(SampleEnum.class), - "Enums should not be considered problematic"); + "Enums do not require reflective instantiation registration"); } @Test @@ -165,3 +165,4 @@ public void testClearRegistrationsResetsState() { } } } + From 287df2023709d3d1f0e71896ba01f5efa7dccc1e Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 24 Oct 2025 15:17:22 +0800 Subject: [PATCH 38/46] fix: use unsafe to solve --- .../serializer/ObjectStreamSerializer.java | 272 ++++++++++++++++-- 1 file changed, 241 insertions(+), 31 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java index 48d56e11dd..4edf7a2fae 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java @@ -80,10 +80,60 @@ public class ObjectStreamSerializer extends AbstractObjectSerializer { private static final Logger LOG = LoggerFactory.getLogger(ObjectStreamSerializer.class); - private final SlotsInfo[] slotsInfos; + private final SlotInfo[] slotsInfos; + + /** + * Interface for slot information used in ObjectStreamSerializer. This allows both full SlotsInfo + * and minimal MinimalSlotsInfo implementations. + */ + private interface SlotInfo { + Class getCls(); + + StreamClassInfo getStreamClassInfo(); + + CompatibleSerializerBase getSlotsSerializer(); + + ForyObjectOutputStream getObjectOutputStream(); + + ForyObjectInputStream getObjectInputStream(); + + ObjectArray getFieldPool(); + + ObjectIntMap getFieldIndexMap(); + + FieldResolver getPutFieldsResolver(); + + CompatibleSerializer getCompatibleStreamSerializer(); + } + + /** + * Safe wrapper for ObjectStreamClass.lookup that handles GraalVM native image limitations. In + * GraalVM native image, ObjectStreamClass.lookup may fail for certain classes like Throwable due + * to missing SerializationConstructorAccessor. This method catches such exceptions and returns + * null, allowing the serializer to use alternative approaches like Unsafe.allocateInstance. + */ + private static ObjectStreamClass safeObjectStreamClassLookup(Class type) { + if (GraalvmSupport.isGraalRuntime()) { + try { + return ObjectStreamClass.lookup(type); + } catch (Exception e) { + // In GraalVM native image, ObjectStreamClass.lookup may fail for certain classes + // due to missing SerializationConstructorAccessor. We catch this and return null + // to allow fallback to Unsafe-based object creation. + LOG.debug( + "ObjectStreamClass.lookup failed for {} in GraalVM native image: {}", + type.getName(), + e.getMessage()); + return null; + } + } else { + // In regular JVM, use normal lookup + return ObjectStreamClass.lookup(type); + } + } public ObjectStreamSerializer(Fory fory, Class type) { - super(fory, type); + super(fory, type, createObjectCreatorForGraalVM(type)); if (!Serializable.class.isAssignableFrom(type)) { throw new IllegalArgumentException( String.format("Class %s should implement %s.", type, Serializable.class)); @@ -96,18 +146,46 @@ public ObjectStreamSerializer(Fory fory, Class type) { Externalizable.class.getName()); // stream serializer may be data serializer of ReplaceResolver serializer. fory.getClassResolver().setSerializerIfAbsent(type, this); - List slotsInfoList = new ArrayList<>(); + List slotsInfoList = new ArrayList<>(); Class end = type; // locate closest non-serializable superclass while (end != null && Serializable.class.isAssignableFrom(end)) { end = end.getSuperclass(); } while (type != end) { - slotsInfoList.add(new SlotsInfo(fory, type)); + try { + slotsInfoList.add(new SlotsInfo(fory, type)); + } catch (Exception e) { + if (GraalvmSupport.isGraalRuntime()) { + LOG.warn( + "Failed to create SlotsInfo for {} in GraalVM native image, " + + "using minimal serialization support: {}", + type.getName(), + e.getMessage()); + // Create a minimal SlotsInfo that can work with Unsafe + slotsInfoList.add(new MinimalSlotsInfo(fory, type)); + } else { + throw e; + } + } type = type.getSuperclass(); } Collections.reverse(slotsInfoList); - slotsInfos = slotsInfoList.toArray(new SlotsInfo[0]); + slotsInfos = slotsInfoList.toArray(new SlotInfo[0]); + } + + /** + * Creates an appropriate ObjectCreator for GraalVM native image environment. In GraalVM, we + * prefer UnsafeObjectCreator to avoid serialization constructor issues. + */ + private static ObjectCreator createObjectCreatorForGraalVM(Class type) { + if (GraalvmSupport.isGraalRuntime()) { + // In GraalVM native image, use Unsafe to avoid serialization constructor issues + return new ObjectCreators.UnsafeObjectCreator<>(type); + } else { + // In regular JVM, use the standard object creator + return ObjectCreators.getObjectCreator(type); + } } @Override @@ -117,13 +195,13 @@ public void write(MemoryBuffer buffer, Object value) { for (SlotsInfo slotsInfo : slotsInfos) { // create a classinfo to avoid null class bytes when class id is a // replacement id. - classResolver.writeClassInternal(buffer, slotsInfo.classInfo.getCls()); - StreamClassInfo streamClassInfo = slotsInfo.streamClassInfo; + classResolver.writeClassInternal(buffer, slotsInfo.getCls()); + StreamClassInfo streamClassInfo = slotsInfo.getStreamClassInfo(); Method writeObjectMethod = streamClassInfo.writeObjectMethod; if (writeObjectMethod == null) { - slotsInfo.slotsSerializer.write(buffer, value); + slotsInfo.getSlotsSerializer().write(buffer, value); } else { - ForyObjectOutputStream objectOutputStream = slotsInfo.objectOutputStream; + ForyObjectOutputStream objectOutputStream = slotsInfo.getObjectOutputStream(); Object oldObject = objectOutputStream.targetObject; MemoryBuffer oldBuffer = objectOutputStream.buffer; ForyObjectOutputStream.PutFieldImpl oldPutField = objectOutputStream.curPut; @@ -161,9 +239,9 @@ public Object read(MemoryBuffer buffer) { TreeMap callbacks = new TreeMap<>(Collections.reverseOrder()); for (int i = 0; i < numClasses; i++) { Class currentClass = classResolver.readClassInternal(buffer); - SlotsInfo slotsInfo = slotsInfos[slotIndex++]; - StreamClassInfo streamClassInfo = slotsInfo.streamClassInfo; - while (currentClass != slotsInfo.cls) { + SlotInfo slotsInfo = slotsInfos[slotIndex++]; + StreamClassInfo streamClassInfo = slotsInfo.getStreamClassInfo(); + while (currentClass != slotsInfo.getCls()) { // the receiver's version extends classes that are not extended by the sender's version. Method readObjectNoData = streamClassInfo.readObjectNoData; if (readObjectNoData != null) { @@ -177,14 +255,14 @@ public Object read(MemoryBuffer buffer) { } Method readObjectMethod = streamClassInfo.readObjectMethod; if (readObjectMethod == null) { - slotsInfo.slotsSerializer.readAndSetFields(buffer, obj); + slotsInfo.getSlotsSerializer().readAndSetFields(buffer, obj); } else { - ForyObjectInputStream objectInputStream = slotsInfo.objectInputStream; + ForyObjectInputStream objectInputStream = slotsInfo.getObjectInputStream(); MemoryBuffer oldBuffer = objectInputStream.buffer; Object oldObject = objectInputStream.targetObject; ForyObjectInputStream.GetFieldImpl oldGetField = objectInputStream.getField; ForyObjectInputStream.GetFieldImpl getField = - (ForyObjectInputStream.GetFieldImpl) slotsInfo.getFieldPool.popOrNull(); + (ForyObjectInputStream.GetFieldImpl) slotsInfo.getFieldPool().popOrNull(); if (getField == null) { getField = new ForyObjectInputStream.GetFieldImpl(slotsInfo); } @@ -205,7 +283,7 @@ public Object read(MemoryBuffer buffer) { objectInputStream.buffer = oldBuffer; objectInputStream.targetObject = oldObject; objectInputStream.getField = oldGetField; - slotsInfo.getFieldPool.add(getField); + slotsInfo.getFieldPool().add(getField); objectInputStream.callbacks = null; Arrays.fill(getField.vals, ForyObjectInputStream.NO_VALUE_STUB); } @@ -249,7 +327,7 @@ private static class StreamClassInfo { private StreamClassInfo(Class type) { // ObjectStreamClass.lookup has cache inside, invocation cost won't be big. - ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(type); + ObjectStreamClass objectStreamClass = safeObjectStreamClassLookup(type); // In JDK17, set private jdk method accessible will fail by default, use ObjectStreamClass // instead, since it set accessible. writeObjectMethod = @@ -290,7 +368,7 @@ protected StreamClassInfo computeValue(Class type) { } }; - private static class SlotsInfo { + private static class SlotsInfo implements SlotInfo { private final Class cls; private final ClassInfo classInfo; private final StreamClassInfo streamClassInfo; @@ -306,7 +384,7 @@ private static class SlotsInfo { public SlotsInfo(Fory fory, Class type) { this.cls = type; classInfo = fory.getClassResolver().newClassInfo(type, null, NO_CLASS_ID); - ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(type); + ObjectStreamClass objectStreamClass = safeObjectStreamClassLookup(type); streamClassInfo = STREAM_CLASS_INFO_CACHE.get(type); // `putFields/writeFields` will convert to fields value to be written by // `CompatibleSerializer`, @@ -382,12 +460,143 @@ public SlotsInfo(Fory fory, Class type) { getFieldPool = new ObjectArray(); } + @Override + public Class getCls() { + return cls; + } + + @Override + public StreamClassInfo getStreamClassInfo() { + return streamClassInfo; + } + + @Override + public CompatibleSerializerBase getSlotsSerializer() { + return slotsSerializer; + } + + @Override + public ForyObjectOutputStream getObjectOutputStream() { + return objectOutputStream; + } + + @Override + public ForyObjectInputStream getObjectInputStream() { + return objectInputStream; + } + + @Override + public ObjectArray getFieldPool() { + return getFieldPool; + } + + @Override + public ObjectIntMap getFieldIndexMap() { + return fieldIndexMap; + } + + @Override + public FieldResolver getPutFieldsResolver() { + return putFieldsResolver; + } + + @Override + public CompatibleSerializer getCompatibleStreamSerializer() { + return compatibleStreamSerializer; + } + @Override public String toString() { return "SlotsInfo{" + "cls=" + cls + '}'; } } + /** + * Minimal SlotsInfo implementation for GraalVM native image when ObjectStreamClass.lookup fails. + * This provides basic serialization support using Unsafe-based object creation. + */ + private static class MinimalSlotsInfo implements SlotInfo { + private final Class cls; + private final ClassInfo classInfo; + private final StreamClassInfo streamClassInfo; + private CompatibleSerializerBase slotsSerializer; + private final ObjectIntMap fieldIndexMap; + private final FieldResolver putFieldsResolver; + private final CompatibleSerializer compatibleStreamSerializer; + private final ForyObjectOutputStream objectOutputStream; + private final ForyObjectInputStream objectInputStream; + private final ObjectArray getFieldPool; + + public MinimalSlotsInfo(Fory fory, Class type) { + // Initialize with minimal required fields + this.cls = type; + this.classInfo = fory.getClassResolver().newClassInfo(type, null, NO_CLASS_ID); + this.streamClassInfo = null; // Skip problematic ObjectStreamClass lookup + + // Create a basic CompatibleSerializer for field handling + FieldResolver fieldResolver = FieldResolver.of(fory, type, false, true); + this.slotsSerializer = new CompatibleSerializer(fory, type, fieldResolver); + + // Initialize other fields with safe defaults + this.fieldIndexMap = new ObjectIntMap<>(4, 0.4f); + this.putFieldsResolver = null; + this.compatibleStreamSerializer = null; + this.objectOutputStream = null; + this.objectInputStream = null; + this.getFieldPool = new ObjectArray(); + } + + @Override + public Class getCls() { + return cls; + } + + @Override + public StreamClassInfo getStreamClassInfo() { + return streamClassInfo; + } + + @Override + public CompatibleSerializerBase getSlotsSerializer() { + return slotsSerializer; + } + + @Override + public ForyObjectOutputStream getObjectOutputStream() { + return objectOutputStream; + } + + @Override + public ForyObjectInputStream getObjectInputStream() { + return objectInputStream; + } + + @Override + public ObjectArray getFieldPool() { + return getFieldPool; + } + + @Override + public ObjectIntMap getFieldIndexMap() { + return fieldIndexMap; + } + + @Override + public FieldResolver getPutFieldsResolver() { + return putFieldsResolver; + } + + @Override + public CompatibleSerializer getCompatibleStreamSerializer() { + return compatibleStreamSerializer; + } + + @Override + public String toString() { + return "MinimalSlotsInfo{" + "cls=" + cls + '}'; + } + } + /** * Implement serialization for object output with `writeObject/readObject` defined by java * serialization output spec. @@ -398,15 +607,15 @@ public String toString() { private static class ForyObjectOutputStream extends ObjectOutputStream { private final Fory fory; private final boolean compressInt; - private final SlotsInfo slotsInfo; + private final SlotInfo slotsInfo; private MemoryBuffer buffer; private Object targetObject; private boolean fieldsWritten; - protected ForyObjectOutputStream(SlotsInfo slotsInfo) throws IOException { + protected ForyObjectOutputStream(SlotInfo slotsInfo) throws IOException { super(); this.slotsInfo = slotsInfo; - this.fory = slotsInfo.slotsSerializer.fory; + this.fory = slotsInfo.getSlotsSerializer().fory; this.compressInt = fory.compressInt(); } @@ -438,11 +647,12 @@ private class PutFieldImpl extends PutField { } private void putValue(String name, Object val) { - int index = slotsInfo.fieldIndexMap.get(name, -1); + int index = slotsInfo.getFieldIndexMap().get(name, -1); if (index == -1) { throw new IllegalArgumentException( String.format( - "Field name %s not exist in class %s", name, slotsInfo.slotsSerializer.type)); + "Field name %s not exist in class %s", + name, slotsInfo.getSlotsSerializer().type)); } vals[index] = val; } @@ -683,15 +893,15 @@ public void close() throws IOException {} private static class ForyObjectInputStream extends ObjectInputStream { private final Fory fory; private final boolean compressInt; - private final SlotsInfo slotsInfo; + private final SlotInfo slotsInfo; private MemoryBuffer buffer; private Object targetObject; private GetFieldImpl getField; private boolean fieldsRead; private TreeMap callbacks; - protected ForyObjectInputStream(SlotsInfo slotsInfo) throws IOException { - this.fory = slotsInfo.slotsSerializer.fory; + protected ForyObjectInputStream(SlotInfo slotsInfo) throws IOException { + this.fory = slotsInfo.getSlotsSerializer().fory; this.compressInt = fory.compressInt(); this.slotsInfo = slotsInfo; } @@ -709,18 +919,18 @@ public Object readUnshared() { private static final Object NO_VALUE_STUB = new Object(); private static class GetFieldImpl extends GetField { - private final SlotsInfo slotsInfo; + private final SlotInfo slotsInfo; private final Object[] vals; - GetFieldImpl(SlotsInfo slotsInfo) { + GetFieldImpl(SlotInfo slotsInfo) { this.slotsInfo = slotsInfo; - vals = new Object[slotsInfo.putFieldsResolver.getNumFields()]; + vals = new Object[slotsInfo.getPutFieldsResolver().getNumFields()]; Arrays.fill(vals, NO_VALUE_STUB); } @Override public ObjectStreamClass getObjectStreamClass() { - return ObjectStreamClass.lookup(slotsInfo.cls); + return safeObjectStreamClassLookup(slotsInfo.getCls()); } @Override From b1cb3c8ff232ed8af42c871349947a047c931fcb Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 24 Oct 2025 20:51:12 +0800 Subject: [PATCH 39/46] fix: add ObjectCreator and ObjectCreators imports and use interface instead of concrete class in ObjectStreamSerializer --- .../serializer/ObjectStreamSerializer.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java index 4edf7a2fae..d187437425 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java @@ -55,6 +55,8 @@ import org.apache.fory.logging.LoggerFactory; import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.memory.Platform; +import org.apache.fory.reflect.ObjectCreator; +import org.apache.fory.reflect.ObjectCreators; import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.resolver.ClassInfo; import org.apache.fory.resolver.FieldResolver; @@ -113,14 +115,14 @@ private interface SlotInfo { * null, allowing the serializer to use alternative approaches like Unsafe.allocateInstance. */ private static ObjectStreamClass safeObjectStreamClassLookup(Class type) { - if (GraalvmSupport.isGraalRuntime()) { + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { try { return ObjectStreamClass.lookup(type); } catch (Exception e) { // In GraalVM native image, ObjectStreamClass.lookup may fail for certain classes // due to missing SerializationConstructorAccessor. We catch this and return null // to allow fallback to Unsafe-based object creation. - LOG.debug( + LOG.warn( "ObjectStreamClass.lookup failed for {} in GraalVM native image: {}", type.getName(), e.getMessage()); @@ -156,7 +158,7 @@ public ObjectStreamSerializer(Fory fory, Class type) { try { slotsInfoList.add(new SlotsInfo(fory, type)); } catch (Exception e) { - if (GraalvmSupport.isGraalRuntime()) { + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { LOG.warn( "Failed to create SlotsInfo for {} in GraalVM native image, " + "using minimal serialization support: {}", @@ -179,7 +181,7 @@ public ObjectStreamSerializer(Fory fory, Class type) { * prefer UnsafeObjectCreator to avoid serialization constructor issues. */ private static ObjectCreator createObjectCreatorForGraalVM(Class type) { - if (GraalvmSupport.isGraalRuntime()) { + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { // In GraalVM native image, use Unsafe to avoid serialization constructor issues return new ObjectCreators.UnsafeObjectCreator<>(type); } else { @@ -192,7 +194,7 @@ private static ObjectCreator createObjectCreatorForGraalVM(Class type) public void write(MemoryBuffer buffer, Object value) { buffer.writeInt16((short) slotsInfos.length); try { - for (SlotsInfo slotsInfo : slotsInfos) { + for (SlotInfo slotsInfo : slotsInfos) { // create a classinfo to avoid null class bytes when class id is a // replacement id. classResolver.writeClassInternal(buffer, slotsInfo.getCls()); @@ -643,7 +645,7 @@ private class PutFieldImpl extends PutField { private final Object[] vals; PutFieldImpl() { - vals = new Object[slotsInfo.putFieldsResolver.getNumFields()]; + vals = new Object[slotsInfo.getPutFieldsResolver().getNumFields()]; } private void putValue(String name, Object val) { @@ -705,7 +707,7 @@ public void put(String name, Object val) { @Deprecated @Override public void write(ObjectOutput out) throws IOException { - Class cls = slotsInfo.slotsSerializer.type; + Class cls = slotsInfo.getSlotsSerializer().getType(); throwUnsupportedEncodingException(cls); } } @@ -734,7 +736,7 @@ public void writeFields() throws IOException { if (curPut == null) { throw new NotActiveException("no current PutField object"); } - slotsInfo.compatibleStreamSerializer.writeFieldsValues(buffer, curPut.vals); + slotsInfo.getCompatibleStreamSerializer().writeFieldsValues(buffer, curPut.vals); Arrays.fill(curPut.vals, null); putFieldsCache.add(curPut); this.curPut = null; @@ -746,13 +748,13 @@ public void defaultWriteObject() throws IOException, NotActiveException { if (fieldsWritten) { throw new NotActiveException("not in writeObject invocation or fields already written"); } - slotsInfo.slotsSerializer.write(buffer, targetObject); + slotsInfo.getSlotsSerializer().write(buffer, targetObject); fieldsWritten = true; } @Override public void reset() throws IOException { - Class cls = slotsInfo.slotsSerializer.getType(); + Class cls = slotsInfo.getSlotsSerializer().getType(); // Fory won't invoke this method, throw exception if the user invokes it. throwUnsupportedEncodingException(cls); } @@ -869,7 +871,7 @@ public void writeUTF(String s) throws IOException { @Override public void useProtocolVersion(int version) throws IOException { - Class cls = slotsInfo.cls; + Class cls = slotsInfo.getCls(); throwUnsupportedEncodingException(cls); } @@ -935,7 +937,7 @@ public ObjectStreamClass getObjectStreamClass() { @Override public boolean defaulted(String name) throws IOException { - int index = slotsInfo.fieldIndexMap.get(name, -1); + int index = slotsInfo.getFieldIndexMap().get(name, -1); checkFieldExists(name, index); return vals[index] == NO_VALUE_STUB; } @@ -1022,7 +1024,7 @@ public Object get(String name, Object val) throws IOException { } private Object getFieldValue(String name) { - int index = slotsInfo.fieldIndexMap.get(name, -1); + int index = slotsInfo.getFieldIndexMap().get(name, -1); checkFieldExists(name, index); return vals[index]; } @@ -1031,7 +1033,7 @@ private void checkFieldExists(String name, int index) { if (index == -1) { throw new IllegalArgumentException( String.format( - "Field name %s not exist in class %s", name, slotsInfo.slotsSerializer.type)); + "Field name %s not exist in class %s", name, slotsInfo.getSlotsSerializer().getType())); } } } @@ -1043,7 +1045,7 @@ public GetField readFields() throws IOException { if (fieldsRead) { throw new NotActiveException("not in readObject invocation or fields already read"); } - slotsInfo.compatibleStreamSerializer.readFields(buffer, getField.vals); + slotsInfo.getCompatibleStreamSerializer().readFields(buffer, getField.vals); fieldsRead = true; return getField; } @@ -1053,7 +1055,7 @@ public void defaultReadObject() throws IOException, ClassNotFoundException { if (fieldsRead) { throw new NotActiveException("not in readObject invocation or fields already read"); } - slotsInfo.slotsSerializer.readAndSetFields(buffer, targetObject); + slotsInfo.getSlotsSerializer().readAndSetFields(buffer, targetObject); fieldsRead = true; } From 7bb71eb7b62bb6d86f0f41869054a5718b32cc40 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 24 Oct 2025 21:04:30 +0800 Subject: [PATCH 40/46] style: apply code formatting with spotless --- .../fory/serializer/ObjectStreamSerializer.java | 12 +++++++++++- .../fory/graalvm/feature/ForyGraalVMFeatureTest.java | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java index d187437425..2bc5886d56 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java @@ -84,6 +84,10 @@ public class ObjectStreamSerializer extends AbstractObjectSerializer { private final SlotInfo[] slotsInfos; + /** + * Interface for slot information used in ObjectStreamSerializer. This allows both full SlotsInfo + * and minimal MinimalSlotsInfo implementations. + */ /** * Interface for slot information used in ObjectStreamSerializer. This allows both full SlotsInfo * and minimal MinimalSlotsInfo implementations. @@ -370,6 +374,11 @@ protected StreamClassInfo computeValue(Class type) { } }; + /** + * Full implementation of SlotInfo for handling object stream serialization. This class manages + * all the details of serializing and deserializing a single class in the class hierarchy using + * Java's ObjectInputStream/ObjectOutputStream protocol. + */ private static class SlotsInfo implements SlotInfo { private final Class cls; private final ClassInfo classInfo; @@ -1033,7 +1042,8 @@ private void checkFieldExists(String name, int index) { if (index == -1) { throw new IllegalArgumentException( String.format( - "Field name %s not exist in class %s", name, slotsInfo.getSlotsSerializer().getType())); + "Field name %s not exist in class %s", + name, slotsInfo.getSlotsSerializer().getType())); } } } diff --git a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java index ae0a9aa762..c64f3487a4 100644 --- a/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java +++ b/java/fory-graalvm-feature/src/test/java/org/apache/fory/graalvm/feature/ForyGraalVMFeatureTest.java @@ -165,4 +165,3 @@ public void testClearRegistrationsResetsState() { } } } - From 21cd6165a268c8c574f6624dce8c9ba9a83c3e1c Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 24 Oct 2025 21:14:42 +0800 Subject: [PATCH 41/46] docs: add missing Javadoc comments for inner classes --- .../apache/fory/serializer/ObjectStreamSerializer.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java index 2bc5886d56..1c349a1de7 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java @@ -323,6 +323,10 @@ private static void throwSerializationException(Class type, Exception e) { e); } + /** + * Information about a class's stream methods (writeObject, readObject, readObjectNoData) and + * their optimized MethodHandle equivalents for fast invocation. + */ private static class StreamClassInfo { private final Method writeObjectMethod; private final Method readObjectMethod; @@ -648,8 +652,6 @@ public void writeUnshared(Object obj) throws IOException { * href="https://docs.oracle.com/en/java/javase/18/docs/specs/serialization/input.html#the-objectinputstream.getfield-class">ObjectInputStream.GetField * @see ConcurrentHashMap */ - // See `defaultReadObject` in ConcurrentHashMap#readObject skip fields written by - // `writeFields()`. private class PutFieldImpl extends PutField { private final Object[] vals; @@ -929,6 +931,10 @@ public Object readUnshared() { private static final Object NO_VALUE_STUB = new Object(); + /** + * Implementation of ObjectInputStream.GetField for reading fields that may not exist in the + * current class version. + */ private static class GetFieldImpl extends GetField { private final SlotInfo slotsInfo; private final Object[] vals; From fe8aaa70c030f9f0fe21bd366eb9727e307eaf7f Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Fri, 24 Oct 2025 21:35:37 +0800 Subject: [PATCH 42/46] fix: configure javadoc plugin to handle incubating modules in GraalVM --- .../java/org/apache/fory/graalvm/GraalvmRegistrationTest.java | 2 +- .../java/org/apache/fory/util/GraalvmSupportRecordTest.java | 1 - java/fory-simd/pom.xml | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java index 2032465fc6..167d6685ab 100644 --- a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java +++ b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/graalvm/GraalvmRegistrationTest.java @@ -66,4 +66,4 @@ public void testEnsureSerializersCompiledRegistersClassesForGraalvm() { GraalvmSupport.getRegisteredClasses().contains(GraalvmRegistrationBean.class)); } } -} \ No newline at end of file +} diff --git a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java index e1be2f039d..9ce8db59a2 100644 --- a/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java +++ b/integration_tests/graalvm_tests/src/test/java/org/apache/fory/util/GraalvmSupportRecordTest.java @@ -110,4 +110,3 @@ public void testRecordUtilsIntegration() { Assert.assertFalse(GraalvmSupport.isRecordConstructorPublicAccessible(String.class)); } } - diff --git a/java/fory-simd/pom.xml b/java/fory-simd/pom.xml index 4f90782d42..069e1ea655 100644 --- a/java/fory-simd/pom.xml +++ b/java/fory-simd/pom.xml @@ -83,6 +83,7 @@ org.apache.maven.plugins maven-javadoc-plugin + false --add-modules=jdk.incubator.vector From 7643cec90bc22ee67404f9b1c4e049dc77001059 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Sat, 25 Oct 2025 20:56:17 +0800 Subject: [PATCH 43/46] fix: ci bug --- java/fory-simd/pom.xml | 2 +- java/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/fory-simd/pom.xml b/java/fory-simd/pom.xml index 069e1ea655..a410045a06 100644 --- a/java/fory-simd/pom.xml +++ b/java/fory-simd/pom.xml @@ -25,7 +25,7 @@ org.apache.fory fory-parent - 0.13.0 + 0.13.0-SNAPSHOT 4.0.0 diff --git a/java/pom.xml b/java/pom.xml index ca4dd767b9..5641b5a3d0 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -181,7 +181,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.0 + 3.6.3 attach-javadocs From bb3c3969125a2ee92bfac356aca68771a10dce20 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Sat, 25 Oct 2025 21:19:40 +0800 Subject: [PATCH 44/46] fix: ci bug from pom --- java/pom.xml | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/java/pom.xml b/java/pom.xml index 5641b5a3d0..01f06f2f3e 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -67,6 +67,9 @@ 1.8 1.8 + UTF-8 + UTF-8 + false 32.1.2-jre 3.1.12 1.13 @@ -178,26 +181,6 @@ - - org.apache.maven.plugins - maven-javadoc-plugin - 3.6.3 - - - attach-javadocs - - jar - - - - - none - - Copyright © 2023-2025, The Apache Software Foundation. Apache Fory™, Fory™, and Apache - are either registered trademarks or trademarks of the Apache Software Foundation. - - - org.apache.maven.plugins maven-shade-plugin @@ -280,6 +263,30 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.3 + + + attach-javadocs + + jar + + + + + none + UTF-8 + UTF-8 + UTF-8 + false + + Copyright © 2023-2025, The Apache Software Foundation. Apache Fory™, Fory™, and Apache + are either registered trademarks or trademarks of the Apache Software Foundation. + + + From 411414517e9cd013dc719307d3e0f4237f188547 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Sat, 25 Oct 2025 21:34:12 +0800 Subject: [PATCH 45/46] fix: ci bug use unsafe --- .../fory/serializer/ObjectStreamSerializer.java | 17 ++++++++++++----- java/pom.xml | 2 ++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java index 1c349a1de7..09d6d22e7a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java @@ -115,14 +115,14 @@ private interface SlotInfo { /** * Safe wrapper for ObjectStreamClass.lookup that handles GraalVM native image limitations. In * GraalVM native image, ObjectStreamClass.lookup may fail for certain classes like Throwable due - * to missing SerializationConstructorAccessor. This method catches such exceptions and returns - * null, allowing the serializer to use alternative approaches like Unsafe.allocateInstance. + * to missing SerializationConstructorAccessor. This method catches such errors and returns null, + * allowing the serializer to use alternative approaches like Unsafe.allocateInstance. */ private static ObjectStreamClass safeObjectStreamClassLookup(Class type) { if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { try { return ObjectStreamClass.lookup(type); - } catch (Exception e) { + } catch (Throwable e) { // In GraalVM native image, ObjectStreamClass.lookup may fail for certain classes // due to missing SerializationConstructorAccessor. We catch this and return null // to allow fallback to Unsafe-based object creation. @@ -438,8 +438,15 @@ public SlotsInfo(Fory fory, Class type) { } fieldIndexMap = new ObjectIntMap<>(4, 0.4f); List allFields = new ArrayList<>(); - for (ObjectStreamField serialField : objectStreamClass.getFields()) { - allFields.add(new ClassField(serialField.getName(), serialField.getType(), cls)); + if (objectStreamClass != null) { + for (ObjectStreamField serialField : objectStreamClass.getFields()) { + allFields.add(new ClassField(serialField.getName(), serialField.getType(), cls)); + } + } else { + // Fallback to field resolver when ObjectStreamClass is not available in GraalVM + for (FieldResolver.FieldInfo fieldInfo : fieldResolver.getAllFieldsList()) { + allFields.add(new ClassField(fieldInfo.getName(), fieldInfo.getField().getType(), cls)); + } } if (streamClassInfo.writeObjectMethod != null || streamClassInfo.readObjectMethod != null) { putFieldsResolver = new FieldResolver(fory, cls, true, allFields, new HashSet<>()); diff --git a/java/pom.xml b/java/pom.xml index 01f06f2f3e..9d11435d1c 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -281,6 +281,8 @@ UTF-8 UTF-8 false + false + false Copyright © 2023-2025, The Apache Software Foundation. Apache Fory™, Fory™, and Apache are either registered trademarks or trademarks of the Apache Software Foundation. From 7bc5d400dba6f715fc4ad7224fae26a24549472b Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Sat, 25 Oct 2025 21:55:30 +0800 Subject: [PATCH 46/46] fix: ci bug? --- .../apache/fory/util/function/Functions.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java b/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java index 0fd3f7b87f..40aac69e37 100644 --- a/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java +++ b/java/fory-core/src/main/java/org/apache/fory/util/function/Functions.java @@ -71,8 +71,25 @@ public static List extractCapturedVariables( public static Object makeGetterFunction(Class cls, String methodName) { try { - return makeGetterFunction(cls.getDeclaredMethod(methodName)); + Method method = cls.getDeclaredMethod(methodName); + return makeGetterFunction(method); } catch (NoSuchMethodException e) { + if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) { + // In GraalVM native image, getDeclaredMethod may fail for Record accessor methods + // Try using getMethods() which works for public methods without reflection config + try { + for (Method method : cls.getMethods()) { + if (method.getName().equals(methodName) && method.getParameterCount() == 0) { + return makeGetterFunction(method); + } + } + throw new NoSuchMethodException( + "No public no-arg method found: " + cls.getName() + "." + methodName + "()"); + } catch (NoSuchMethodException ex) { + throw new RuntimeException( + "Failed to create getter for " + cls.getName() + "." + methodName, ex); + } + } throw new RuntimeException(e); } }