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 ba6aecfd7 fix(Rust): prevent obtaining generic type metadata on custom
types(struct/enum) (#3057)
ba6aecfd7 is described below
commit ba6aecfd7b82a6fce7ff502f2786e8854ffa8b59
Author: urlyy <[email protected]>
AuthorDate: Wed Dec 17 17:11:17 2025 +0800
fix(Rust): prevent obtaining generic type metadata on custom
types(struct/enum) (#3057)
## Why?
Issue #3049 reveals a problem. For example, when serializing the field
`my_vec: Vec<Wrapper<Inner>>`, the current Fory-Rust writes
```
(id: Vec::type_id, generics: [
( id: Wrapper<Inner>::type_id, generics: [] )
])
```
That is, the concrete generic type parameters on structs/enums are not
written into the `FieldType` metadata. This results in inconsistent
metadata between the sender and the receiver, and causes
`assign_field_ids()` to assign `remote_field.field_id = -1`, leading to
the issue.
## What does this PR do?
When obtaining the field type on the sender side, for field_type
`Vec<Wrapper<Inner>>`, the `FieldType` objects for `Vec` and
`Wrapper<Inner>` have already been created, so there is no need to
further create a `FieldType` object for `Inner`. Therefore, further
recursion over generic types on structs/enums is truncated in the
runtime.
Meanwhile, because different languages provide different levels of
support for generic types on classes, serializing structs/enums with
generics is `not supported in xlang mode`. A `runtime check` has been
added for this.
## Related issues
Fixes #3049
---
rust/fory-core/src/error.rs | 6 +-
rust/fory-core/src/fory.rs | 1 +
rust/fory-core/src/resolver/type_resolver.rs | 12 +++
rust/fory-derive/src/object/util.rs | 8 ++
rust/tests/tests/compatible/test_struct.rs | 111 +++++++++++++++++++++++++++
5 files changed, 135 insertions(+), 3 deletions(-)
diff --git a/rust/fory-core/src/error.rs b/rust/fory-core/src/error.rs
index 12c119364..a27fb4d85 100644
--- a/rust/fory-core/src/error.rs
+++ b/rust/fory-core/src/error.rs
@@ -163,7 +163,7 @@ pub enum Error {
///
/// Do not construct this variant directly; use [`Error::unsupported`]
instead.
#[error("{0}")]
- Uunsupported(Cow<'static, str>),
+ Unsupported(Cow<'static, str>),
/// Operation not allowed in current context.
///
@@ -381,7 +381,7 @@ impl Error {
err
}
- /// Creates a new [`Error::Uunsupported`] from a string or static message.
+ /// Creates a new [`Error::Unsupported`] from a string or static message.
///
/// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic
with the error message.
///
@@ -396,7 +396,7 @@ impl Error {
#[cold]
#[track_caller]
pub fn unsupported<S: Into<Cow<'static, str>>>(s: S) -> Self {
- let err = Error::Uunsupported(s.into());
+ let err = Error::Unsupported(s.into());
if PANIC_ON_ERROR {
panic!("FORY_PANIC_ON_ERROR: {}", err);
}
diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs
index a0081f8a6..4a6632879 100644
--- a/rust/fory-core/src/fory.rs
+++ b/rust/fory-core/src/fory.rs
@@ -183,6 +183,7 @@ impl Fory {
if !self.config.check_struct_version {
self.config.check_struct_version = !self.config.compatible;
}
+ self.type_resolver.set_xlang(xlang);
self
}
diff --git a/rust/fory-core/src/resolver/type_resolver.rs
b/rust/fory-core/src/resolver/type_resolver.rs
index 37cf10735..4a88f8d2c 100644
--- a/rust/fory-core/src/resolver/type_resolver.rs
+++ b/rust/fory-core/src/resolver/type_resolver.rs
@@ -458,6 +458,7 @@ pub struct TypeResolver {
// Fast lookup by numeric ID for common types
type_id_index: Vec<u32>,
compatible: bool,
+ xlang: bool,
}
// Safety: TypeResolver instances are only shared through higher-level
synchronization that
@@ -478,6 +479,7 @@ impl Default for TypeResolver {
type_id_index: Vec::new(),
partial_type_infos: HashMap::new(),
compatible: false,
+ xlang: false,
};
registry.register_builtin_types().unwrap();
registry
@@ -1000,6 +1002,14 @@ impl TypeResolver {
self.compatible = compatible;
}
+ pub(crate) fn set_xlang(&mut self, xlang: bool) {
+ self.xlang = xlang;
+ }
+
+ pub fn is_xlang(&self) -> bool {
+ self.xlang
+ }
+
/// Builds the final TypeResolver by completing all partial type infos
created during registration.
///
/// This method processes all types that were registered with lazy
initialization enabled.
@@ -1061,6 +1071,7 @@ impl TypeResolver {
partial_type_infos: HashMap::new(),
type_id_index,
compatible: self.compatible,
+ xlang: self.xlang,
})
}
@@ -1142,6 +1153,7 @@ impl TypeResolver {
partial_type_infos: HashMap::new(),
type_id_index: self.type_id_index.clone(),
compatible: self.compatible,
+ xlang: self.xlang,
}
}
}
diff --git a/rust/fory-derive/src/object/util.rs
b/rust/fory-derive/src/object/util.rs
index c2f16ebdb..c814a1dde 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -534,6 +534,14 @@ pub(super) fn generic_tree_to_tokens(node: &TypeNode) ->
TokenStream {
vec![]
));
}
+ let is_custom = !fory_core::types::is_internal_type(type_id &
0xff);
+ if is_custom {
+ if type_resolver.is_xlang() && generics.len() > 0 {
+ return
Err(fory_core::error::Error::unsupported("serialization of generic structs and
enums is not supported in xlang mode"));
+ } else {
+ generics = vec![];
+ }
+ }
fory_core::meta::FieldType::new(type_id, #nullable, generics)
}
}
diff --git a/rust/tests/tests/compatible/test_struct.rs
b/rust/tests/tests/compatible/test_struct.rs
index 62f3f5b59..44c32726b 100644
--- a/rust/tests/tests/compatible/test_struct.rs
+++ b/rust/tests/tests/compatible/test_struct.rs
@@ -16,8 +16,10 @@
// under the License.
use fory_core::fory::Fory;
+use fory_core::{Error, ForyDefault, ReadContext, Serializer, TypeResolver,
WriteContext};
use fory_derive::ForyObject;
use std::collections::{HashMap, HashSet};
+use std::marker::PhantomData;
// RUSTFLAGS="-Awarnings" cargo expand -p tests --test test_struct
#[test]
@@ -599,3 +601,112 @@ fn boxed() {
let item2_f6: Option<i32> = fory2.deserialize(&bytes).unwrap();
assert_eq!(item2.f6, item2_f6);
}
+
+#[test]
+fn test_struct_with_generic() {
+ #[derive(Debug, PartialEq)]
+ struct Wrapper<T> {
+ value: String,
+ _marker: PhantomData<T>,
+ data: T,
+ }
+
+ #[derive(ForyObject, Debug, PartialEq)]
+ #[fory(debug)]
+ struct MyStruct {
+ my_vec: Vec<Wrapper<Another>>,
+ my_vec1: Vec<Wrapper<i32>>,
+ }
+
+ #[derive(ForyObject, Debug, PartialEq)]
+ #[fory(debug)]
+ struct Another {
+ f1: i32,
+ }
+
+ impl<T: 'static + Serializer + ForyDefault> Serializer for Wrapper<T> {
+ fn fory_write_data(&self, context: &mut WriteContext) -> Result<(),
Error> {
+ context.writer.write_varuint32(self.value.len() as u32);
+ context.writer.write_utf8_string(&self.value);
+ self.data.fory_write_data(context)?;
+ Ok(())
+ }
+
+ fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
+ let len = context.reader.read_varuint32()? as usize;
+ let value = context.reader.read_utf8_string(len)?;
+ let data = T::fory_read_data(context)?;
+ Ok(Self {
+ value,
+ _marker: PhantomData,
+ data,
+ })
+ }
+
+ fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) ->
Result<u32, Error> {
+ Self::fory_get_type_id(type_resolver)
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+ }
+
+ impl<T: ForyDefault> ForyDefault for Wrapper<T> {
+ fn fory_default() -> Self {
+ Self {
+ value: "".into(),
+ _marker: PhantomData,
+ data: T::fory_default(),
+ }
+ }
+ }
+
+ let mut fory1 = Fory::default().compatible(true);
+ let mut fory2 = Fory::default(); // Without compatible it works fine.
+ let mut fory3 = Fory::default().xlang(true); // Works fine with xlang
enabled
+
+ fn inner_test(fory: &mut Fory) -> Result<(), Error> {
+ fory.register::<MyStruct>(1)?;
+ fory.register::<Another>(2)?;
+ fory.register_serializer::<Wrapper<Another>>(3)?;
+ fory.register_serializer::<Wrapper<i32>>(4)?;
+
+ let w1 = Wrapper::<Another> {
+ value: "Value1".into(),
+ _marker: PhantomData,
+ data: Another { f1: 10 },
+ };
+ let w2 = Wrapper::<Another> {
+ value: "Value2".into(),
+ _marker: PhantomData,
+ data: Another { f1: 11 },
+ };
+
+ let w3 = Wrapper::<i32> {
+ value: "Value3".into(),
+ _marker: PhantomData,
+ data: 12,
+ };
+ let w4 = Wrapper::<i32> {
+ value: "Value4".into(),
+ _marker: PhantomData,
+ data: 13,
+ };
+
+ let ms = MyStruct {
+ my_vec: vec![w1, w2],
+ my_vec1: vec![w3, w4],
+ };
+
+ let bytes = fory.serialize(&ms)?;
+ let new_ms = fory.deserialize::<MyStruct>(&bytes)?;
+ assert_eq!(ms, new_ms);
+ Ok(())
+ }
+
+ for fory in [&mut fory1, &mut fory2] {
+ assert!(inner_test(fory).is_ok());
+ }
+ assert!(inner_test(&mut fory3).is_err());
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]