This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 7b15a546e feat(java): add dedicated exception serializers (#3536)
7b15a546e is described below
commit 7b15a546ead357497d03d8f137f810d223586b36
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Apr 3 20:09:15 2026 +0800
feat(java): add dedicated exception serializers (#3536)
## Why?
## What does this PR do?
## Related issues
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes` / `no`
- [ ] If `yes`, I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required `AI Usage Disclosure`.
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
.../org/apache/fory/graalvm/ExceptionExample.java | 110 +++++++
.../main/java/org/apache/fory/graalvm/Main.java | 1 +
.../graalvm_tests/native-image.properties | 1 +
.../fory/exception/DeserializationException.java | 2 +-
.../org/apache/fory/resolver/ClassResolver.java | 16 +-
.../fory/serializer/ExceptionSerializers.java | 318 +++++++++++++++++++++
.../java/org/apache/fory/util/GraalvmSupport.java | 3 +
.../fory-core/native-image.properties | 4 +
.../src/test/java/org/apache/fory/ForyTest.java | 16 +-
.../fory/serializer/ExceptionSerializersTest.java | 162 +++++++++++
.../fory/graalvm/feature/ForyGraalVMFeature.java | 15 +-
11 files changed, 636 insertions(+), 12 deletions(-)
diff --git
a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/ExceptionExample.java
b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/ExceptionExample.java
new file mode 100644
index 000000000..11bf934b7
--- /dev/null
+++
b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/ExceptionExample.java
@@ -0,0 +1,110 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.fory.Fory;
+import org.apache.fory.reflect.ReflectionUtils;
+import org.apache.fory.serializer.ExceptionSerializers;
+import org.apache.fory.util.Preconditions;
+
+public class ExceptionExample {
+ private static final Fory FORY =
+ Fory.builder()
+ .withName(ExceptionExample.class.getName())
+ .requireClassRegistration(true)
+ .withRefTracking(false)
+ .build();
+
+ static {
+ FORY.register(CustomException.class);
+ }
+
+ public static void main(String[] args) {
+ testBuiltInException();
+ testCustomException();
+ System.out.println("ExceptionExample succeed");
+ }
+
+ private static void testBuiltInException() {
+ IllegalArgumentException cause = new
IllegalArgumentException("built-in-cause");
+ IllegalStateException value = new IllegalStateException("built-in", cause);
+ IllegalStateException copy = (IllegalStateException)
FORY.deserialize(FORY.serialize(value));
+ Preconditions.checkArgument(
+ FORY.getTypeResolver().getSerializerClass(IllegalStateException.class)
+ == ExceptionSerializers.ExceptionSerializer.class);
+ Preconditions.checkArgument(copy.getMessage().equals(value.getMessage()));
+ Preconditions.checkArgument(copy.getCause() != null);
+ Preconditions.checkArgument(copy.getCause().getClass() ==
cause.getClass());
+
Preconditions.checkArgument(copy.getCause().getMessage().equals(cause.getMessage()));
+ Preconditions.checkArgument(copy.getStackTrace().length ==
value.getStackTrace().length);
+
Preconditions.checkArgument(copy.getStackTrace()[0].equals(value.getStackTrace()[0]));
+ }
+
+ private static void testCustomException() {
+ CustomException value =
+ new CustomException("custom-native")
+ .withParentCode(42)
+ .withTags(new ArrayList<>(Arrays.asList("left", "right")));
+ value.retryable = true;
+ CustomException copy = (CustomException)
FORY.deserialize(FORY.serialize(value));
+ Preconditions.checkArgument(
+ FORY.getTypeResolver().getSerializerClass(CustomException.class)
+ == ExceptionSerializers.ExceptionSerializer.class);
+ Preconditions.checkArgument(copy.getMessage().equals(value.getMessage()));
+ Preconditions.checkArgument(copy.parentCode == value.parentCode);
+ Preconditions.checkArgument(copy.retryable == value.retryable);
+ Preconditions.checkArgument(copy.tags.equals(value.tags));
+ Preconditions.checkArgument(copy.getCause() == null);
+ Preconditions.checkArgument(
+ ReflectionUtils.getObjectFieldValue(
+ copy, ReflectionUtils.getField(Throwable.class, "cause"))
+ == copy);
+ }
+
+ public static class ParentException extends RuntimeException {
+ int parentCode;
+ boolean retryable;
+
+ public ParentException(String message) {
+ super(message);
+ }
+ }
+
+ public static class CustomException extends ParentException {
+ List<String> tags;
+
+ public CustomException(String message) {
+ super(message);
+ }
+
+ CustomException withParentCode(int parentCode) {
+ this.parentCode = parentCode;
+ return this;
+ }
+
+ CustomException withTags(List<String> tags) {
+ this.tags = tags;
+ return this;
+ }
+ }
+}
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 d84cf6561..021f5c833 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
@@ -42,6 +42,7 @@ public class Main {
Benchmark.main(args);
CollectionExample.main(args);
ArrayExample.main(args);
+ ExceptionExample.main(args);
AbstractClassExample.main(args);
FeatureTestExample.main(args);
}
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 c62793701..a39144f2b 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,6 +34,7 @@ Args=-H:+ReportExceptionStackTraces \
org.apache.fory.graalvm.EnsureSerializerExample$CustomSerializer,\
org.apache.fory.graalvm.CollectionExample,\
org.apache.fory.graalvm.ArrayExample,\
+ org.apache.fory.graalvm.ExceptionExample,\
org.apache.fory.graalvm.AbstractClassExample,\
org.apache.fory.graalvm.Benchmark,\
org.apache.fory.graalvm.FeatureTestExample,\
diff --git
a/java/fory-core/src/main/java/org/apache/fory/exception/DeserializationException.java
b/java/fory-core/src/main/java/org/apache/fory/exception/DeserializationException.java
index 1d6099ecb..5cd154c67 100644
---
a/java/fory-core/src/main/java/org/apache/fory/exception/DeserializationException.java
+++
b/java/fory-core/src/main/java/org/apache/fory/exception/DeserializationException.java
@@ -24,7 +24,7 @@ import java.util.List;
/** Exception thrown when a deserialization operation fails. */
public class DeserializationException extends ForyException {
- private List<Object> readObjects;
+ private transient List<Object> readObjects;
public DeserializationException(String message) {
super(message);
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 3836d2ad6..6c93a9ad7 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
@@ -103,6 +103,7 @@ import org.apache.fory.serializer.BufferSerializers;
import org.apache.fory.serializer.CodegenSerializer.LazyInitBeanSerializer;
import org.apache.fory.serializer.CopyOnlyObjectSerializer;
import org.apache.fory.serializer.EnumSerializer;
+import org.apache.fory.serializer.ExceptionSerializers;
import org.apache.fory.serializer.ExternalizableSerializer;
import org.apache.fory.serializer.ForyCopyableSerializer;
import org.apache.fory.serializer.JavaSerializer;
@@ -322,6 +323,9 @@ public class ClassResolver extends TypeResolver {
OptionalSerializers.registerDefaultSerializers(fory);
CollectionSerializers.registerDefaultSerializers(fory);
MapSerializers.registerDefaultSerializers(fory);
+ addDefaultSerializer(
+ StackTraceElement[].class,
+ new ArraySerializers.ObjectArraySerializer<>(fory,
StackTraceElement[].class));
addDefaultSerializer(Locale.class, new LocaleSerializer(fory));
addDefaultSerializer(
SerializedLambda.class, new SerializedLambdaSerializer(fory,
SerializedLambda.class));
@@ -1213,6 +1217,12 @@ public class ClassResolver extends TypeResolver {
throw new UnsupportedOperationException(
String.format("Class %s doesn't support serialization.", cls));
}
+ if (cls == StackTraceElement.class) {
+ return ExceptionSerializers.StackTraceElementSerializer.class;
+ }
+ if (Throwable.class.isAssignableFrom(cls)) {
+ return ExceptionSerializers.ExceptionSerializer.class;
+ }
Class<? extends Serializer> serializerClass =
getSerializerClassFromGraalvmRegistry(cls);
if (serializerClass != null) {
return serializerClass;
@@ -1695,12 +1705,6 @@ public class ClassResolver extends TypeResolver {
} else {
return extRegistry.typeChecker.checkType(this, cls.getName());
}
- // Don't take java Exception as secure in case future JDK introduce
insecure JDK exception.
- // if (Exception.class.isAssignableFrom(cls)
- // && cls.getName().startsWith("java.")
- // && !cls.getName().startsWith("java.sql")) {
- // return true;
- // }
}
/**
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
new file mode 100644
index 000000000..06039728c
--- /dev/null
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.serializer;
+
+import static org.apache.fory.collection.Collections.ofHashSet;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.fory.Fory;
+import org.apache.fory.builder.LayerMarkerClassGenerator;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.memory.Platform;
+import org.apache.fory.meta.TypeDef;
+import org.apache.fory.reflect.ObjectCreator;
+import org.apache.fory.reflect.ObjectCreators;
+import org.apache.fory.reflect.ReflectionUtils;
+import org.apache.fory.resolver.MetaContext;
+import org.apache.fory.util.GraalvmSupport;
+import org.apache.fory.util.unsafe._JDKAccess;
+
+/** Serializers for {@link Throwable} and {@link StackTraceElement}. */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public final class ExceptionSerializers {
+ private static final Set<Class<?>> THROWABLE_SUPER_CLASSES =
ofHashSet(Throwable.class);
+
+ private ExceptionSerializers() {}
+
+ public static final class ExceptionSerializer<T extends Throwable> extends
Serializer<T> {
+ private final ObjectCreator<T> objectCreator;
+ private volatile Serializer[] slotsSerializers;
+ private volatile boolean rebuildSlotsSerializersAtRuntime;
+
+ public ExceptionSerializer(Fory fory, Class<T> type) {
+ super(fory, type);
+ objectCreator = createThrowableObjectCreator(type);
+ slotsSerializers = buildSlotsSerializers(fory, type);
+ // Native-image runtime must rebuild slot serializers once so field
accessors and
+ // descriptors are created against the runtime heap layout instead of
reusing
+ // any build-time initialized state.
+ rebuildSlotsSerializersAtRuntime =
GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE;
+ }
+
+ @Override
+ public void write(MemoryBuffer buffer, T value) {
+ Serializer[] slotsSerializers = getSlotsSerializers();
+ fory.writeRef(buffer, value.getStackTrace());
+ fory.writeRef(buffer, value.getCause());
+ fory.writeStringRef(buffer, value.getMessage());
+ buffer.writeVarUint32(0);
+ for (Serializer slotsSerializer : slotsSerializers) {
+ slotsSerializer.write(buffer, value);
+ }
+ }
+
+ @Override
+ public T read(MemoryBuffer buffer) {
+ Serializer[] slotsSerializers = getSlotsSerializers();
+ T obj = objectCreator.newInstance();
+ refResolver.reference(obj);
+ StackTraceElement[] stackTrace = (StackTraceElement[])
fory.readRef(buffer);
+ Throwable cause = (Throwable) fory.readRef(buffer);
+ String detailMessage = fory.readStringRef(buffer);
+ skipExtraFields(buffer);
+ Platform.putObject(obj, ThrowableOffsets.DETAIL_MESSAGE_FIELD_OFFSET,
detailMessage);
+ Platform.putObject(obj, ThrowableOffsets.CAUSE_FIELD_OFFSET, cause ==
null ? obj : cause);
+ Platform.putObject(obj, ThrowableOffsets.STACK_TRACE_FIELD_OFFSET,
stackTrace);
+ readAndSetFields(fory, buffer, obj, slotsSerializers);
+ return obj;
+ }
+
+ private Serializer[] getSlotsSerializers() {
+ if (!rebuildSlotsSerializersAtRuntime ||
!GraalvmSupport.isGraalRuntime()) {
+ return slotsSerializers;
+ }
+ synchronized (this) {
+ if (rebuildSlotsSerializersAtRuntime) {
+ slotsSerializers = buildSlotsSerializers(fory, type);
+ rebuildSlotsSerializersAtRuntime = false;
+ }
+ return slotsSerializers;
+ }
+ }
+
+ private void skipExtraFields(MemoryBuffer buffer) {
+ int numExtraFields = buffer.readVarUint32();
+ for (int i = 0; i < numExtraFields; i++) {
+ fory.readString(buffer);
+ fory.readRef(buffer);
+ }
+ }
+ }
+
+ public static final class StackTraceElementSerializer extends
Serializer<StackTraceElement> {
+ private static final MethodHandles.Lookup LOOKUP =
+ _JDKAccess._trustedLookup(StackTraceElement.class);
+ private static final MethodHandle CLASS_LOADER_NAME_GETTER =
+ getOptionalGetter("getClassLoaderName");
+ private static final MethodHandle MODULE_NAME_GETTER =
getOptionalGetter("getModuleName");
+ private static final MethodHandle MODULE_VERSION_GETTER =
getOptionalGetter("getModuleVersion");
+ private static final MethodHandle STACK_TRACE_ELEMENT_CTR_V1 =
+ ReflectionUtils.getCtrHandle(
+ StackTraceElement.class, String.class, String.class, String.class,
int.class);
+ private static final MethodHandle STACK_TRACE_ELEMENT_CTR_V2 =
+ getOptionalCtr(
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ int.class);
+
+ public StackTraceElementSerializer(Fory fory) {
+ super(fory, StackTraceElement.class, false, true);
+ }
+
+ @Override
+ public void write(MemoryBuffer buffer, StackTraceElement value) {
+ fory.writeStringRef(buffer, invokeStringGetter(CLASS_LOADER_NAME_GETTER,
value));
+ fory.writeStringRef(buffer, invokeStringGetter(MODULE_NAME_GETTER,
value));
+ fory.writeStringRef(buffer, invokeStringGetter(MODULE_VERSION_GETTER,
value));
+ fory.writeStringRef(buffer, value.getClassName());
+ fory.writeStringRef(buffer, value.getMethodName());
+ fory.writeStringRef(buffer, value.getFileName());
+ buffer.writeInt32(value.getLineNumber());
+ buffer.writeVarUint32(0);
+ }
+
+ @Override
+ public StackTraceElement read(MemoryBuffer buffer) {
+ String classLoaderName = fory.readStringRef(buffer);
+ String moduleName = fory.readStringRef(buffer);
+ String moduleVersion = fory.readStringRef(buffer);
+ String declaringClass = fory.readStringRef(buffer);
+ String methodName = fory.readStringRef(buffer);
+ String fileName = fory.readStringRef(buffer);
+ int lineNumber = buffer.readInt32();
+ int numExtraFields = buffer.readVarUint32();
+ for (int i = 0; i < numExtraFields; i++) {
+ fory.readString(buffer);
+ fory.readRef(buffer);
+ }
+ return newStackTraceElement(
+ classLoaderName,
+ moduleName,
+ moduleVersion,
+ declaringClass,
+ methodName,
+ fileName,
+ lineNumber);
+ }
+
+ private static MethodHandle getOptionalGetter(String methodName) {
+ try {
+ return LOOKUP.findVirtual(
+ StackTraceElement.class, methodName,
MethodType.methodType(String.class));
+ } catch (NoSuchMethodException e) {
+ return null;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static MethodHandle getOptionalCtr(Class<?>... parameterTypes) {
+ try {
+ return LOOKUP.findConstructor(
+ StackTraceElement.class, MethodType.methodType(void.class,
parameterTypes));
+ } catch (NoSuchMethodException e) {
+ return null;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String invokeStringGetter(MethodHandle getter,
StackTraceElement value) {
+ if (getter == null) {
+ return null;
+ }
+ try {
+ return (String) getter.invoke(value);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ private static StackTraceElement newStackTraceElement(
+ String classLoaderName,
+ String moduleName,
+ String moduleVersion,
+ String declaringClass,
+ String methodName,
+ String fileName,
+ int lineNumber) {
+ try {
+ if (STACK_TRACE_ELEMENT_CTR_V2 != null) {
+ return (StackTraceElement)
+ STACK_TRACE_ELEMENT_CTR_V2.invoke(
+ classLoaderName,
+ moduleName,
+ moduleVersion,
+ declaringClass,
+ methodName,
+ fileName,
+ lineNumber);
+ }
+ return (StackTraceElement)
+ STACK_TRACE_ELEMENT_CTR_V1.invoke(declaringClass, methodName,
fileName, lineNumber);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+
+ private static <T extends Throwable> ObjectCreator<T>
createThrowableObjectCreator(
+ Class<T> type) {
+ if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+ return new ObjectCreators.UnsafeObjectCreator<>(type);
+ }
+ if (ReflectionUtils.getCtrHandle(type, false) != null) {
+ return ObjectCreators.getObjectCreator(type);
+ }
+ return new ObjectCreators.ParentNoArgCtrObjectCreator<>(type);
+ }
+
+ private static <T> Serializer[] buildSlotsSerializers(Fory fory, Class<T>
type) {
+ List<Serializer> serializers = new ArrayList<>();
+ int layerIndex = 0;
+ while (!THROWABLE_SUPER_CLASSES.contains(type)) {
+ Serializer slotsSerializer;
+ if (fory.getConfig().isCompatible()) {
+ TypeDef layerTypeDef = fory.getTypeResolver().getTypeDef(type, false);
+ Class<?> layerMarkerClass =
LayerMarkerClassGenerator.getOrCreate(fory, type, layerIndex);
+ slotsSerializer = new MetaSharedLayerSerializer(fory, type,
layerTypeDef, layerMarkerClass);
+ } else {
+ slotsSerializer = new ObjectSerializer<>(fory, type, false);
+ }
+ serializers.add(slotsSerializer);
+ type = (Class<T>) type.getSuperclass();
+ layerIndex++;
+ }
+ Collections.reverse(serializers);
+ return serializers.toArray(new Serializer[0]);
+ }
+
+ private static void readAndSetFields(
+ Fory fory, MemoryBuffer buffer, Object target, Serializer[]
slotsSerializers) {
+ for (Serializer slotsSerializer : slotsSerializers) {
+ if (slotsSerializer instanceof MetaSharedLayerSerializer) {
+ MetaSharedLayerSerializer metaSerializer = (MetaSharedLayerSerializer)
slotsSerializer;
+ if (fory.getConfig().isMetaShareEnabled()) {
+ readAndSkipLayerClassMeta(fory, buffer);
+ }
+ metaSerializer.readAndSetFields(buffer, target);
+ } else {
+ ((ObjectSerializer) slotsSerializer).readAndSetFields(buffer, target);
+ }
+ }
+ }
+
+ private static void readAndSkipLayerClassMeta(Fory fory, MemoryBuffer
buffer) {
+ MetaContext metaContext = fory.getSerializationContext().getMetaContext();
+ if (metaContext == null) {
+ return;
+ }
+ int indexMarker = buffer.readVarUint32Small14();
+ boolean isRef = (indexMarker & 1) == 1;
+ if (isRef) {
+ return;
+ }
+ long typeDefId = buffer.readInt64();
+ TypeDef.skipTypeDef(buffer, typeDefId);
+ metaContext.readTypeInfos.add(null);
+ }
+
+ private static final class ThrowableOffsets {
+ // Graalvm unsafe offset substitution support: Make the call followed by a
field store
+ // directly or by a sign extend node followed directly by a field store.
+ private static final long DETAIL_MESSAGE_FIELD_OFFSET;
+ private static final long CAUSE_FIELD_OFFSET;
+ private static final long STACK_TRACE_FIELD_OFFSET;
+
+ static {
+ try {
+ Field detailMessageField =
Throwable.class.getDeclaredField("detailMessage");
+ DETAIL_MESSAGE_FIELD_OFFSET =
Platform.UNSAFE.objectFieldOffset(detailMessageField);
+ Field causeField = Throwable.class.getDeclaredField("cause");
+ CAUSE_FIELD_OFFSET = Platform.UNSAFE.objectFieldOffset(causeField);
+ Field stackTraceField = Throwable.class.getDeclaredField("stackTrace");
+ STACK_TRACE_FIELD_OFFSET =
Platform.UNSAFE.objectFieldOffset(stackTraceField);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
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 ad6c976b3..388f06905 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
@@ -34,6 +34,7 @@ import org.apache.fory.serializer.ArraySerializers;
import org.apache.fory.serializer.BufferSerializers;
import org.apache.fory.serializer.CodegenSerializer;
import org.apache.fory.serializer.EnumSerializer;
+import org.apache.fory.serializer.ExceptionSerializers;
import org.apache.fory.serializer.ExternalizableSerializer;
import org.apache.fory.serializer.JavaSerializer;
import org.apache.fory.serializer.JdkProxySerializer;
@@ -106,6 +107,8 @@ public class GraalvmSupport {
registerDefaultSerializerClass(MapSerializer.class);
registerDefaultSerializerClass(CodegenSerializer.LazyInitBeanSerializer.class);
registerDefaultSerializerClass(ExternalizableSerializer.class);
+
registerDefaultSerializerClass(ExceptionSerializers.ExceptionSerializer.class);
+
registerDefaultSerializerClass(ExceptionSerializers.StackTraceElementSerializer.class);
registerDefaultSerializerClass(ObjectStreamSerializer.class);
registerDefaultSerializerClass(JavaSerializer.class);
registerDefaultSerializerClass(ObjectSerializer.class);
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 c2a4e70be..0435038e2 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
@@ -342,6 +342,10 @@
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
org.apache.fory.serializer.MetaSharedSerializer,\
org.apache.fory.serializer.MetaSharedLayerSerializer,\
org.apache.fory.serializer.EnumSerializer,\
+ org.apache.fory.serializer.ExceptionSerializers,\
+ org.apache.fory.serializer.ExceptionSerializers$ExceptionSerializer,\
+
org.apache.fory.serializer.ExceptionSerializers$StackTraceElementSerializer,\
+ org.apache.fory.serializer.ExceptionSerializers$ThrowableOffsets,\
org.apache.fory.serializer.collection.SubListSerializers,\
org.apache.fory.serializer.collection.SubListSerializers$SubListViewSerializer,\
org.apache.fory.serializer.collection.SubListSerializers$SubListSerializer,\
diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyTest.java
b/java/fory-core/src/test/java/org/apache/fory/ForyTest.java
index 508b00fa0..fba4505c1 100644
--- a/java/fory-core/src/test/java/org/apache/fory/ForyTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/ForyTest.java
@@ -69,6 +69,7 @@ import org.apache.fory.memory.Platform;
import org.apache.fory.resolver.MetaContext;
import org.apache.fory.serializer.ArraySerializersTest;
import org.apache.fory.serializer.EnumSerializerTest;
+import org.apache.fory.serializer.ExceptionSerializers;
import org.apache.fory.serializer.ObjectSerializer;
import org.apache.fory.serializer.Serializer;
import org.apache.fory.test.bean.BeanA;
@@ -232,8 +233,19 @@ public class ForyTest extends ForyTestBase {
@Test
public void testSerializeException() {
- Fory fory =
Fory.builder().withLanguage(Language.JAVA).withRefTracking(true).build();
- fory.serialize(new Exception());
+ Fory fory =
+ Fory.builder()
+ .withLanguage(Language.JAVA)
+ .withRefTracking(true)
+ .requireClassRegistration(true)
+ .build();
+ Exception value = new Exception("test-serialize-exception");
+ Exception copy = serDe(fory, value);
+ Assert.assertEquals(
+ fory.getTypeResolver().getSerializerClass(Exception.class),
+ ExceptionSerializers.ExceptionSerializer.class);
+ Assert.assertEquals(copy.getMessage(), value.getMessage());
+ Assert.assertEquals(copy.getStackTrace()[0], value.getStackTrace()[0]);
}
@Test(dataProvider = "referenceTrackingConfig")
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
new file mode 100644
index 000000000..2868b3a6b
--- /dev/null
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.serializer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.fory.Fory;
+import org.apache.fory.ForyTestBase;
+import org.apache.fory.config.CompatibleMode;
+import org.apache.fory.reflect.ReflectionUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ExceptionSerializersTest extends ForyTestBase {
+ @Test(dataProvider = "javaFory")
+ public void testBuiltInThrowableRoundTrip(Fory fory) {
+ IllegalArgumentException cause = new
IllegalArgumentException("inner-cause");
+ IllegalStateException value = new IllegalStateException("outer-message",
cause);
+
+ IllegalStateException copy = serDe(fory, value);
+
+ Assert.assertEquals(
+ fory.getTypeResolver().getSerializerClass(value.getClass()),
+ ExceptionSerializers.ExceptionSerializer.class);
+ Assert.assertEquals(copy.getClass(), value.getClass());
+ Assert.assertEquals(copy.getMessage(), value.getMessage());
+ Assert.assertNotNull(copy.getCause());
+ Assert.assertEquals(copy.getCause().getClass(), cause.getClass());
+ Assert.assertEquals(copy.getCause().getMessage(), cause.getMessage());
+ Assert.assertEquals(copy.getStackTrace().length,
value.getStackTrace().length);
+ Assert.assertEquals(copy.getStackTrace()[0], value.getStackTrace()[0]);
+ }
+
+ @Test(dataProvider = "javaFory")
+ public void testStackTraceElementRoundTrip(Fory fory) {
+ StackTraceElement value = new Exception().getStackTrace()[0];
+
+ StackTraceElement copy = serDe(fory, value);
+
+ Assert.assertEquals(
+ fory.getTypeResolver().getSerializerClass(StackTraceElement.class),
+ ExceptionSerializers.StackTraceElementSerializer.class);
+ Assert.assertEquals(copy, value);
+ }
+
+ @Test
+ public void testThrowableWithoutRefTrackingKeepsSelfCauseField() {
+ Fory fory = builder().withRefTracking(false).withCodegen(false).build();
+ CustomException value =
+ new CustomException("self-cause")
+ .withParentCode(7)
+ .withTags(new ArrayList<>(Arrays.asList("a", "b")));
+
+ CustomException copy = serDe(fory, value);
+
+ Assert.assertNull(copy.getCause());
+ Assert.assertSame(
+ ReflectionUtils.getObjectFieldValue(
+ copy, ReflectionUtils.getField(Throwable.class, "cause")),
+ copy);
+ Assert.assertEquals(copy.parentCode, value.parentCode);
+ Assert.assertEquals(copy.tags, value.tags);
+ }
+
+ @Test
+ public void testBuiltInThrowableWithClassRegistrationRequired() {
+ Fory fory =
+
builder().requireClassRegistration(true).withRefTracking(false).withCodegen(false).build();
+ IllegalStateException value =
+ new IllegalStateException("registered-built-in", new
IllegalArgumentException("cause"));
+
+ IllegalStateException copy = serDe(fory, value);
+
+ Assert.assertEquals(copy.getMessage(), value.getMessage());
+ Assert.assertNotNull(copy.getCause());
+ Assert.assertEquals(copy.getCause().getClass(),
value.getCause().getClass());
+ Assert.assertEquals(copy.getCause().getMessage(),
value.getCause().getMessage());
+ Assert.assertEquals(copy.getStackTrace()[0], value.getStackTrace()[0]);
+ }
+
+ @Test
+ public void testThrowableCompatibleModeRoundTrip() {
+ Fory fory =
+ builder()
+ .withRefTracking(true)
+ .withCodegen(false)
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .build();
+ CustomException cause =
+ new CustomException("cause")
+ .withParentCode(1)
+ .withTags(new ArrayList<>(Arrays.asList("x")));
+ CustomException value =
+ new CustomException("custom", cause)
+ .withParentCode(9)
+ .withTags(new ArrayList<>(Arrays.asList("left", "right")));
+ value.retryable = true;
+
+ CustomException copy = serDe(fory, value);
+
+ Assert.assertEquals(copy.getMessage(), value.getMessage());
+ Assert.assertNotNull(copy.getCause());
+ Assert.assertEquals(copy.getCause().getClass(), cause.getClass());
+ Assert.assertEquals(copy.getCause().getMessage(), cause.getMessage());
+ Assert.assertEquals(copy.parentCode, value.parentCode);
+ Assert.assertEquals(copy.tags, value.tags);
+ Assert.assertEquals(copy.retryable, value.retryable);
+ }
+
+ public static class ParentException extends RuntimeException {
+ int parentCode;
+ boolean retryable;
+
+ public ParentException(String message) {
+ super(message);
+ }
+
+ public ParentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ public static class CustomException extends ParentException {
+ List<String> tags;
+
+ public CustomException(String message) {
+ super(message);
+ }
+
+ public CustomException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ CustomException withParentCode(int parentCode) {
+ this.parentCode = parentCode;
+ return this;
+ }
+
+ CustomException withTags(List<String> tags) {
+ this.tags = tags;
+ return this;
+ }
+ }
+}
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 6d84d3fe0..9aae3424b 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
@@ -107,9 +107,7 @@ public class ForyGraalVMFeature implements Feature {
private void registerClass(Class<?> clazz) {
RuntimeReflection.register(clazz);
- registerFields(clazz);
- registerMethods(clazz);
- registerConstructors(clazz);
+ registerHierarchyMembers(clazz);
if (Serializable.class.isAssignableFrom(clazz)) {
registerSerializableHierarchy(clazz);
}
@@ -121,6 +119,17 @@ public class ForyGraalVMFeature implements Feature {
}
}
+ private void registerHierarchyMembers(Class<?> clazz) {
+ for (Class<?> current = clazz;
+ current != null && current != Object.class;
+ current = current.getSuperclass()) {
+ RuntimeReflection.register(current);
+ registerFields(current);
+ registerMethods(current);
+ registerConstructors(current);
+ }
+ }
+
private void registerSerializerClass(Class<?> clazz) {
registerConstructors(clazz);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]