From 2ad234bc81a44572ef1f542e167dcc0ee457c497 Mon Sep 17 00:00:00 2001 From: "Y.Horie" Date: Fri, 18 Sep 2026 00:25:35 +0900 Subject: [PATCH] fix: grow NgxString on write! instead of failing within capacity fmt::Write was wired to append_within_capacity, so a write! into a freshly created NgxString failed on the first byte, and one into a partly filled one wrote what fit and returned an error alongside the truncated value. Use try_append, which reserves before it appends. Allocation stays checked, so a genuine failure still surfaces as fmt::Error rather than a panic, and a failed write leaves the string untouched rather than half written. Reserving up front is unaffected: try_reserve_exact does nothing when the spare capacity already covers the write, so the existing test asserting that no reallocation occurs still holds. Closes #326 Signed-off-by: Y.Horie --- src/core/string.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/core/string.rs b/src/core/string.rs index ad245df0..0b4ca75a 100644 --- a/src/core/string.rs +++ b/src/core/string.rs @@ -514,7 +514,7 @@ mod _alloc { A: Allocator + Clone, { fn write_str(&mut self, s: &str) -> fmt::Result { - self.append_within_capacity(s).map_err(|_| fmt::Error) + self.try_append(s).map_err(|_| fmt::Error) } } @@ -618,6 +618,53 @@ mod tests { assert_eq!((s.as_bytes().as_ptr(), s.capacity()), saved); } + /// An allocator that always refuses, so that a write can be made to fail + /// without relying on exhausting real memory. + #[cfg(feature = "alloc")] + #[derive(Clone)] + struct Oom; + + #[cfg(feature = "alloc")] + unsafe impl crate::allocator::Allocator for Oom { + fn allocate( + &self, + _layout: core::alloc::Layout, + ) -> Result, crate::allocator::AllocError> { + Err(crate::allocator::AllocError) + } + + unsafe fn deallocate(&self, _ptr: core::ptr::NonNull, _layout: core::alloc::Layout) {} + } + + #[test] + #[cfg(feature = "alloc")] + fn test_string_write_grows() { + use core::fmt::Write; + + use crate::allocator::Global; + + let w = NgxStr::from_bytes(b"world"); + + // No capacity reserved up front. + let mut s = NgxString::new_in(Global); + + write!(s, "Hello {w}!").expect("write"); + + assert_eq!(s, b"Hello world!"); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_string_write_reports_allocation_failure() { + use core::fmt::Write; + + let mut s = NgxString::new_in(Oom); + + // An error rather than a panic, and nothing written. + write!(s, "Hello").expect_err("write should fail"); + assert!(s.is_empty()); + } + #[test] fn test_lifetimes() { let a: &NgxStr = "Hello World!".into();