This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to tag v0.14.1-rc1 in repository https://gitbox.apache.org/repos/asf/fory.git
commit 86e123e0c3f4406ea20b48a781140a00996a4154 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]
