From 7e31257f2ef01eb8a3381f92bfdaf5ec728a0981 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Thu, 16 Apr 2026 23:28:25 +0530 Subject: [PATCH 1/4] feat(rust): add configurable size guardrails --- rust/fory-core/src/config.rs | 20 ++++++ rust/fory-core/src/error.rs | 30 ++++++++ rust/fory-core/src/fory.rs | 69 +++++++++++++++++++ rust/fory-core/src/resolver/context.rs | 16 +++++ rust/fory-core/src/serializer/collection.rs | 14 ++++ rust/fory-core/src/serializer/map.rs | 14 ++++ .../src/serializer/primitive_list.rs | 7 ++ 7 files changed, 170 insertions(+) diff --git a/rust/fory-core/src/config.rs b/rust/fory-core/src/config.rs index 991db1d724..13342700de 100644 --- a/rust/fory-core/src/config.rs +++ b/rust/fory-core/src/config.rs @@ -38,6 +38,12 @@ pub struct Config { /// When enabled, shared references and circular references are tracked /// and preserved during serialization/deserialization. pub track_ref: bool, + /// Maximum allowed size for binary data in bytes. + /// Prevents excessive memory allocation from untrusted payloads. + pub max_binary_size: u32, + /// Maximum allowed number of elements in a collection or entries in a map. + /// Prevents excessive memory allocation from untrusted payloads. + pub max_collection_size: u32, } impl Default for Config { @@ -50,6 +56,8 @@ impl Default for Config { max_dyn_depth: 5, check_struct_version: false, track_ref: false, + max_binary_size: 64 * 1024 * 1024, // 64MB default + max_collection_size: 1024 * 1024, // 1M elements default } } } @@ -101,4 +109,16 @@ impl Config { pub fn is_track_ref(&self) -> bool { self.track_ref } + + /// Get maximum allowed binary data size in bytes. + #[inline(always)] + pub fn max_binary_size(&self) -> u32 { + self.max_binary_size + } + + /// Get maximum allowed collection/map element count. + #[inline(always)] + pub fn max_collection_size(&self) -> u32 { + self.max_collection_size + } } diff --git a/rust/fory-core/src/error.rs b/rust/fory-core/src/error.rs index 10455ee792..c198a98da9 100644 --- a/rust/fory-core/src/error.rs +++ b/rust/fory-core/src/error.rs @@ -196,6 +196,15 @@ pub enum Error { /// Do not construct this variant directly; use [`Error::struct_version_mismatch`] instead. #[error("{0}")] StructVersionMismatch(Cow<'static, str>), + + /// Deserialization size limit exceeded. + /// + /// Returned when a payload-driven length exceeds a configured guardrail + /// (e.g. `max_binary_size` or `max_collection_size`). + /// + /// Do not construct this variant directly; use [`Error::size_limit_exceeded`] instead. + #[error("{0}")] + SizeLimitExceeded(Cow<'static, str>), } impl Error { @@ -495,6 +504,27 @@ impl Error { err } + /// Creates a new [`Error::SizeLimitExceeded`] from a string or static message. + /// + /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic with the error message. + /// + /// # Example + /// ``` + /// use fory_core::error::Error; + /// + /// let err = Error::size_limit_exceeded("Collection size 2000000 exceeds limit 1048576"); + /// ``` + #[inline(always)] + #[cold] + #[track_caller] + pub fn size_limit_exceeded>>(s: S) -> Self { + let err = Error::SizeLimitExceeded(s.into()); + if PANIC_ON_ERROR { + panic!("FORY_PANIC_ON_ERROR: {}", err); + } + err + } + /// Enhances a [`Error::TypeError`] with additional type name information. /// /// If the error is a `TypeError`, appends the type name to the message. diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs index d4e2359b33..e97fe93d67 100644 --- a/rust/fory-core/src/fory.rs +++ b/rust/fory-core/src/fory.rs @@ -321,6 +321,65 @@ impl Fory { self } + /// Sets the maximum allowed size for binary data during deserialization. + /// + /// # Arguments + /// + /// * `max_binary_size` - The maximum number of bytes allowed for a single binary/primitive-array + /// payload during deserialization. Payloads exceeding this limit will cause a + /// `SizeLimitExceeded` error. + /// + /// # Returns + /// + /// Returns `self` for method chaining. + /// + /// # Default + /// + /// The default value is `64 * 1024 * 1024` (64 MB). + /// + /// # Examples + /// + /// ```rust + /// use fory_core::Fory; + /// + /// // Limit binary payloads to 1 MB + /// let fory = Fory::default().max_binary_size(1024 * 1024); + /// ``` + pub fn max_binary_size(mut self, max_binary_size: u32) -> Self { + self.config.max_binary_size = max_binary_size; + self + } + + /// Sets the maximum allowed number of elements in a collection or entries in a map + /// during deserialization. + /// + /// # Arguments + /// + /// * `max_collection_size` - The maximum number of elements/entries allowed for a single + /// collection or map during deserialization. Payloads exceeding this limit will cause a + /// `SizeLimitExceeded` error. + /// + /// # Returns + /// + /// Returns `self` for method chaining. + /// + /// # Default + /// + /// The default value is `1024 * 1024` (1 million elements). + /// + /// # Examples + /// + /// ```rust + /// use fory_core::Fory; + /// + /// // Limit collections to 10000 elements + /// let fory = Fory::default().max_collection_size(10000); + /// ``` + pub fn max_collection_size(mut self, max_collection_size: u32) -> Self { + self.config.max_collection_size = max_collection_size; + self + } + /// Returns whether cross-language serialization is enabled. pub fn is_xlang(&self) -> bool { self.config.xlang @@ -358,6 +417,16 @@ impl Fory { self.config.max_dyn_depth } + /// Returns the maximum allowed binary data size in bytes. + pub fn get_max_binary_size(&self) -> u32 { + self.config.max_binary_size + } + + /// Returns the maximum allowed collection/map element count. + pub fn get_max_collection_size(&self) -> u32 { + self.config.max_collection_size + } + /// Returns whether class version checking is enabled. /// /// # Returns diff --git a/rust/fory-core/src/resolver/context.rs b/rust/fory-core/src/resolver/context.rs index 4f7f08c835..42bd19783d 100644 --- a/rust/fory-core/src/resolver/context.rs +++ b/rust/fory-core/src/resolver/context.rs @@ -315,6 +315,8 @@ pub struct ReadContext<'a> { xlang: bool, max_dyn_depth: u32, check_struct_version: bool, + max_binary_size: u32, + max_collection_size: u32, // Context-specific fields pub reader: Reader<'a>, @@ -342,6 +344,8 @@ impl<'a> ReadContext<'a> { xlang: config.xlang, max_dyn_depth: config.max_dyn_depth, check_struct_version: config.check_struct_version, + max_binary_size: config.max_binary_size, + max_collection_size: config.max_collection_size, reader: Reader::default(), meta_resolver: MetaReaderResolver::default(), meta_string_resolver: MetaStringReaderResolver::default(), @@ -386,6 +390,18 @@ impl<'a> ReadContext<'a> { self.max_dyn_depth } + /// Get maximum allowed binary data size in bytes. + #[inline(always)] + pub fn max_binary_size(&self) -> u32 { + self.max_binary_size + } + + /// Get maximum allowed collection/map element count. + #[inline(always)] + pub fn max_collection_size(&self) -> u32 { + self.max_collection_size + } + #[inline(always)] pub fn attach_reader(&mut self, reader: Reader<'a>) { self.reader = reader; diff --git a/rust/fory-core/src/serializer/collection.rs b/rust/fory-core/src/serializer/collection.rs index ae16620c7e..e5826d87ea 100644 --- a/rust/fory-core/src/serializer/collection.rs +++ b/rust/fory-core/src/serializer/collection.rs @@ -243,6 +243,13 @@ where if len == 0 { return Ok(C::from_iter(std::iter::empty())); } + let max = context.max_collection_size(); + if len > max { + return Err(Error::size_limit_exceeded(format!( + "Collection size {} exceeds limit {}", + len, max + ))); + } if T::fory_is_polymorphic() || T::fory_is_shared_ref() { return read_collection_data_dyn_ref(context, len); } @@ -285,6 +292,13 @@ where if len == 0 { return Ok(Vec::new()); } + let max = context.max_collection_size(); + if len > max { + return Err(Error::size_limit_exceeded(format!( + "Collection size {} exceeds limit {}", + len, max + ))); + } if T::fory_is_polymorphic() || T::fory_is_shared_ref() { return read_vec_data_dyn_ref(context, len); } diff --git a/rust/fory-core/src/serializer/map.rs b/rust/fory-core/src/serializer/map.rs index f8c7997c07..c18c7aad91 100644 --- a/rust/fory-core/src/serializer/map.rs +++ b/rust/fory-core/src/serializer/map.rs @@ -560,6 +560,13 @@ impl max { + return Err(Error::size_limit_exceeded(format!( + "Map size {} exceeds limit {}", + len, max + ))); + } check_map_len(context, len)?; if K::fory_is_polymorphic() || K::fory_is_shared_ref() @@ -712,6 +719,13 @@ impl max { + return Err(Error::size_limit_exceeded(format!( + "Map size {} exceeds limit {}", + len, max + ))); + } check_map_len(context, len)?; let mut map = BTreeMap::::new(); if K::fory_is_polymorphic() diff --git a/rust/fory-core/src/serializer/primitive_list.rs b/rust/fory-core/src/serializer/primitive_list.rs index cd381a7dbf..4aa9373355 100644 --- a/rust/fory-core/src/serializer/primitive_list.rs +++ b/rust/fory-core/src/serializer/primitive_list.rs @@ -83,6 +83,13 @@ pub fn fory_read_data(context: &mut ReadContext) -> Result if size_bytes % std::mem::size_of::() != 0 { return Err(Error::invalid_data("Invalid data length")); } + let max = context.max_binary_size() as usize; + if size_bytes > max { + return Err(Error::size_limit_exceeded(format!( + "Binary size {} exceeds limit {}", + size_bytes, max + ))); + } let remaining = context.reader.slice_after_cursor().len(); if size_bytes > remaining { let cursor = context.reader.get_cursor(); From 8e058cf119c4fbdb534f665f547904b1f4b62cd3 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Thu, 16 Apr 2026 23:32:22 +0530 Subject: [PATCH 2/4] feat: added size guard tests --- rust/tests/tests/test_collection.rs | 26 +++++++++++++++ rust/tests/tests/test_fory.rs | 34 +++++++++++++++++++- rust/tests/tests/test_list.rs | 22 +++++++++++++ rust/tests/tests/test_map.rs | 50 +++++++++++++++++++++++++++++ rust/tests/tests/test_unsigned.rs | 42 ++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 1 deletion(-) diff --git a/rust/tests/tests/test_collection.rs b/rust/tests/tests/test_collection.rs index e8b3c2f72d..0d6264793a 100644 --- a/rust/tests/tests/test_collection.rs +++ b/rust/tests/tests/test_collection.rs @@ -120,3 +120,29 @@ fn test_heap_container() { assert_eq!(deserialized.binary_heap.len(), 3); assert_eq!(deserialized.binary_heap.peek(), Some(&3)); } + +#[test] +fn test_hashset_max_collection_size_guardrail() { + let fory = Fory::default(); + let original = HashSet::from([ + "apple".to_string(), + "banana".to_string(), + "cherry".to_string(), + ]); + let serialized = fory.serialize(&original).unwrap(); + + let limited_fory = Fory::default().max_collection_size(2); + let err = limited_fory + .deserialize::>(&serialized) + .expect_err("expected collection size guardrail to reject the payload"); + + assert!( + matches!(err, fory_core::Error::SizeLimitExceeded(_)), + "expected SizeLimitExceeded, got: {err}" + ); + assert!( + err.to_string() + .contains("Collection size 3 exceeds limit 2"), + "unexpected error message: {err}" + ); +} diff --git a/rust/tests/tests/test_fory.rs b/rust/tests/tests/test_fory.rs index 1c8be4cb46..332428f9e7 100644 --- a/rust/tests/tests/test_fory.rs +++ b/rust/tests/tests/test_fory.rs @@ -16,6 +16,7 @@ // under the License. use fory_core::buffer::Reader; +use fory_core::error::Error; use fory_core::fory::Fory; use fory_derive::ForyObject; @@ -289,7 +290,6 @@ fn test_unregistered_type_error_message() { #[test] fn test_type_mismatch_error_shows_type_name() { - use fory_core::error::Error; use fory_core::types::{format_type_id, TypeId}; // Test internal type (BOOL = 1), no registered_id @@ -331,3 +331,35 @@ fn test_type_mismatch_error_shows_type_name() { err_str ); } + +#[test] +fn test_size_guardrail_configuration_accessors() { + let default_fory = Fory::default(); + assert_eq!(default_fory.get_max_binary_size(), 64 * 1024 * 1024); + assert_eq!(default_fory.get_max_collection_size(), 1024 * 1024); + + let configured_fory = Fory::default() + .max_binary_size(4096) + .max_collection_size(128); + assert_eq!(configured_fory.get_max_binary_size(), 4096); + assert_eq!(configured_fory.get_max_collection_size(), 128); +} + +#[test] +fn test_max_binary_size_does_not_limit_string_reads() { + let fory = Fory::default(); + let original = "this string should not be treated as binary".repeat(4); + let serialized = fory.serialize(&original).unwrap(); + + let limited_fory = Fory::default().max_binary_size(4); + let deserialized: String = limited_fory.deserialize(&serialized).unwrap(); + + assert_eq!(deserialized, original); +} + +#[test] +fn test_size_limit_exceeded_error_display() { + let err = Error::size_limit_exceeded("Collection size 3 exceeds limit 2"); + assert!(matches!(err, Error::SizeLimitExceeded(_))); + assert_eq!(err.to_string(), "Collection size 3 exceeds limit 2"); +} diff --git a/rust/tests/tests/test_list.rs b/rust/tests/tests/test_list.rs index 627dd62646..0c8f8f5d29 100644 --- a/rust/tests/tests/test_list.rs +++ b/rust/tests/tests/test_list.rs @@ -190,3 +190,25 @@ fn test_vec_float16_empty() { .expect("deserialize empty float16 vec"); assert_eq!(obj.len(), 0); } + +#[test] +fn test_vec_max_collection_size_guardrail() { + let fory = Fory::default(); + let original = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()]; + let serialized = fory.serialize(&original).unwrap(); + + let limited_fory = Fory::default().max_collection_size(2); + let err = limited_fory + .deserialize::>(&serialized) + .expect_err("expected vec deserialization to fail on max_collection_size"); + + assert!( + matches!(err, fory_core::Error::SizeLimitExceeded(_)), + "expected SizeLimitExceeded, got: {err}" + ); + assert!( + err.to_string() + .contains("Collection size 3 exceeds limit 2"), + "unexpected error message: {err}" + ); +} diff --git a/rust/tests/tests/test_map.rs b/rust/tests/tests/test_map.rs index f619800a77..832dca516d 100644 --- a/rust/tests/tests/test_map.rs +++ b/rust/tests/tests/test_map.rs @@ -67,3 +67,53 @@ fn test_struct_with_maps() { let obj: MapContainer = fory.deserialize(&bin).expect("deserialize"); assert_eq!(container, obj); } + +#[test] +fn test_hashmap_max_collection_size_guardrail() { + let fory = Fory::default(); + let map = HashMap::from([ + ("key1".to_string(), 1_i32), + ("key2".to_string(), 2_i32), + ("key3".to_string(), 3_i32), + ]); + let serialized = fory.serialize(&map).unwrap(); + + let limited_fory = Fory::default().max_collection_size(2); + let err = limited_fory + .deserialize::>(&serialized) + .expect_err("expected hashmap deserialization to fail on max_collection_size"); + + assert!( + matches!(err, fory_core::Error::SizeLimitExceeded(_)), + "expected SizeLimitExceeded, got: {err}" + ); + assert!( + err.to_string().contains("Map size 3 exceeds limit 2"), + "unexpected error message: {err}" + ); +} + +#[test] +fn test_btreemap_max_collection_size_guardrail() { + let fory = Fory::default(); + let map = BTreeMap::from([ + ("key1".to_string(), 1_i32), + ("key2".to_string(), 2_i32), + ("key3".to_string(), 3_i32), + ]); + let serialized = fory.serialize(&map).unwrap(); + + let limited_fory = Fory::default().max_collection_size(2); + let err = limited_fory + .deserialize::>(&serialized) + .expect_err("expected btreemap deserialization to fail on max_collection_size"); + + assert!( + matches!(err, fory_core::Error::SizeLimitExceeded(_)), + "expected SizeLimitExceeded, got: {err}" + ); + assert!( + err.to_string().contains("Map size 3 exceeds limit 2"), + "unexpected error message: {err}" + ); +} diff --git a/rust/tests/tests/test_unsigned.rs b/rust/tests/tests/test_unsigned.rs index 66b1b751ea..c3a853ed67 100644 --- a/rust/tests/tests/test_unsigned.rs +++ b/rust/tests/tests/test_unsigned.rs @@ -72,6 +72,48 @@ fn test_binary_when_xlang() { assert_eq!(data, result); } +#[test] +fn test_binary_max_size_guardrail_for_vec_u8() { + let fory = Fory::default(); + let original = vec![1_u8, 2, 3, 4, 5]; + let serialized = fory.serialize(&original).unwrap(); + + let limited_fory = Fory::default().max_binary_size(4); + let err = limited_fory + .deserialize::>(&serialized) + .expect_err("expected binary size guardrail to reject the payload"); + + assert!( + matches!(err, fory_core::Error::SizeLimitExceeded(_)), + "expected SizeLimitExceeded, got: {err}" + ); + assert!( + err.to_string().contains("Binary size 5 exceeds limit 4"), + "unexpected error message: {err}" + ); +} + +#[test] +fn test_binary_max_size_guardrail_for_vec_u32() { + let fory = Fory::default(); + let original = vec![10_u32, 20, 30]; + let serialized = fory.serialize(&original).unwrap(); + + let limited_fory = Fory::default().max_binary_size(8); + let err = limited_fory + .deserialize::>(&serialized) + .expect_err("expected primitive array size guardrail to reject the payload"); + + assert!( + matches!(err, fory_core::Error::SizeLimitExceeded(_)), + "expected SizeLimitExceeded, got: {err}" + ); + assert!( + err.to_string().contains("Binary size 12 exceeds limit 8"), + "unexpected error message: {err}" + ); +} + #[test] fn test_unsigned_struct_non_compatible() { #[derive(ForyObject, Debug, PartialEq)] From a4f38ad01d0be726178cc4ee16dee670ceeb4023 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 17 Apr 2026 13:11:02 +0530 Subject: [PATCH 3/4] fix: moved size check errors to a cold helper --- rust/fory-core/src/serializer/collection.rs | 15 +++++++-------- rust/fory-core/src/serializer/map.rs | 15 +++++++-------- rust/fory-core/src/serializer/primitive_list.rs | 10 ++++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/rust/fory-core/src/serializer/collection.rs b/rust/fory-core/src/serializer/collection.rs index e5826d87ea..51ec826ac8 100644 --- a/rust/fory-core/src/serializer/collection.rs +++ b/rust/fory-core/src/serializer/collection.rs @@ -32,6 +32,11 @@ pub const DECL_ELEMENT_TYPE: u8 = 0b100; // Whether collection elements type same. pub const IS_SAME_TYPE: u8 = 0b1000; +#[cold] +fn collection_size_limit_exceeded(len: u32, max: u32) -> Error { + Error::size_limit_exceeded(format!("Collection size {} exceeds limit {}", len, max)) +} + fn check_collection_len(context: &ReadContext, len: u32) -> Result<(), Error> { if std::mem::size_of::() == 0 { return Ok(()); @@ -245,10 +250,7 @@ where } let max = context.max_collection_size(); if len > max { - return Err(Error::size_limit_exceeded(format!( - "Collection size {} exceeds limit {}", - len, max - ))); + return Err(collection_size_limit_exceeded(len, max)); } if T::fory_is_polymorphic() || T::fory_is_shared_ref() { return read_collection_data_dyn_ref(context, len); @@ -294,10 +296,7 @@ where } let max = context.max_collection_size(); if len > max { - return Err(Error::size_limit_exceeded(format!( - "Collection size {} exceeds limit {}", - len, max - ))); + return Err(collection_size_limit_exceeded(len, max)); } if T::fory_is_polymorphic() || T::fory_is_shared_ref() { return read_vec_data_dyn_ref(context, len); diff --git a/rust/fory-core/src/serializer/map.rs b/rust/fory-core/src/serializer/map.rs index c18c7aad91..6677679ff6 100644 --- a/rust/fory-core/src/serializer/map.rs +++ b/rust/fory-core/src/serializer/map.rs @@ -34,6 +34,11 @@ const TRACKING_VALUE_REF: u8 = 0b1000; pub const VALUE_NULL: u8 = 0b10000; pub const DECL_VALUE_TYPE: u8 = 0b100000; +#[cold] +fn map_size_limit_exceeded(len: u32, max: u32) -> Error { + Error::size_limit_exceeded(format!("Map size {} exceeds limit {}", len, max)) +} + fn check_map_len(context: &ReadContext, len: u32) -> Result<(), Error> { let len = len as usize; let remaining = context.reader.slice_after_cursor().len(); @@ -562,10 +567,7 @@ impl max { - return Err(Error::size_limit_exceeded(format!( - "Map size {} exceeds limit {}", - len, max - ))); + return Err(map_size_limit_exceeded(len, max)); } check_map_len(context, len)?; if K::fory_is_polymorphic() @@ -721,10 +723,7 @@ impl max { - return Err(Error::size_limit_exceeded(format!( - "Map size {} exceeds limit {}", - len, max - ))); + return Err(map_size_limit_exceeded(len, max)); } check_map_len(context, len)?; let mut map = BTreeMap::::new(); diff --git a/rust/fory-core/src/serializer/primitive_list.rs b/rust/fory-core/src/serializer/primitive_list.rs index 4aa9373355..5110c8d6d8 100644 --- a/rust/fory-core/src/serializer/primitive_list.rs +++ b/rust/fory-core/src/serializer/primitive_list.rs @@ -22,6 +22,11 @@ use crate::resolver::context::WriteContext; use crate::serializer::Serializer; use crate::types::TypeId; +#[cold] +fn binary_size_limit_exceeded(size_bytes: usize, max: usize) -> Error { + Error::size_limit_exceeded(format!("Binary size {} exceeds limit {}", size_bytes, max)) +} + pub fn fory_write_data(this: &[T], context: &mut WriteContext) -> Result<(), Error> { // U128, USIZE, ISIZE, INT128 are Rust-specific and not supported in xlang mode if context.is_xlang() { @@ -85,10 +90,7 @@ pub fn fory_read_data(context: &mut ReadContext) -> Result } let max = context.max_binary_size() as usize; if size_bytes > max { - return Err(Error::size_limit_exceeded(format!( - "Binary size {} exceeds limit {}", - size_bytes, max - ))); + return Err(binary_size_limit_exceeded(size_bytes, max)); } let remaining = context.reader.slice_after_cursor().len(); if size_bytes > remaining { From 91c120de3c6737298c67e75d97da3f201b9907c1 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 17 Apr 2026 19:29:13 +0530 Subject: [PATCH 4/4] fix: move config.clone() to creation closure and defined a pre-reserved buffer space --- rust/fory-core/src/fory.rs | 12 ++++++++---- rust/fory-core/src/serializer/collection.rs | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs index e97fe93d67..0aeded559e 100644 --- a/rust/fory-core/src/fory.rs +++ b/rust/fory-core/src/fory.rs @@ -668,12 +668,14 @@ impl Fory { WRITE_CONTEXTS.with(|cache| { let cache = unsafe { &mut *cache.get() }; let id = self.id; - let config = self.config.clone(); let context = cache.get_or_insert_result(id, || { // Only fetch type resolver when creating a new context let type_resolver = self.get_final_type_resolver()?; - Ok(Box::new(WriteContext::new(type_resolver.clone(), config))) + Ok(Box::new(WriteContext::new( + type_resolver.clone(), + self.config.clone(), + ))) })?; f(context) }) @@ -1084,12 +1086,14 @@ impl Fory { READ_CONTEXTS.with(|cache| { let cache = unsafe { &mut *cache.get() }; let id = self.id; - let config = self.config.clone(); let context = cache.get_or_insert_result(id, || { // Only fetch type resolver when creating a new context let type_resolver = self.get_final_type_resolver()?; - Ok(Box::new(ReadContext::new(type_resolver.clone(), config))) + Ok(Box::new(ReadContext::new( + type_resolver.clone(), + self.config.clone(), + ))) })?; f(context) }) diff --git a/rust/fory-core/src/serializer/collection.rs b/rust/fory-core/src/serializer/collection.rs index 51ec826ac8..cdd3c1dc6b 100644 --- a/rust/fory-core/src/serializer/collection.rs +++ b/rust/fory-core/src/serializer/collection.rs @@ -100,6 +100,8 @@ where context.writer.write_u8(header); T::fory_write_type_info(context)?; } + // Pre-reserve buffer space to avoid per-element capacity checks in the write loop. + context.writer.reserve(len * T::fory_reserved_space()); if !has_null { for item in iter { item.fory_write_data_generic(context, has_generics)?;