From 5124344d862930eb507879fc602089b3e8bb4e96 Mon Sep 17 00:00:00 2001 From: discord9 Date: Fri, 14 Nov 2025 01:45:32 +0800 Subject: [PATCH 1/5] fix: default impl of `Buf` Signed-off-by: discord9 --- src/buf/buf_impl.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/buf/buf_impl.rs b/src/buf/buf_impl.rs index 192034fbe..4ec9d12fe 100644 --- a/src/buf/buf_impl.rs +++ b/src/buf/buf_impl.rs @@ -2365,9 +2365,15 @@ pub trait Buf { }); } - let mut ret = crate::BytesMut::with_capacity(len); - ret.put(self.take(len)); - ret.freeze() + if len == 0 { + // return empty Bytes here to avoid stack overflow by prevent + // `BytesMut::with_capacity(0)` trying to reuse allocation of `self`. + return crate::Bytes::new(); + } else { + let mut ret = crate::BytesMut::with_capacity(len); + ret.put(self.take(len)); + ret.freeze() + } } /// Creates an adaptor which will read at most `limit` bytes from `self`. From a7b317d3ab8110bc3ef2047b22cb3dcc6c22246f Mon Sep 17 00:00:00 2001 From: discord9 Date: Fri, 14 Nov 2025 01:53:56 +0800 Subject: [PATCH 2/5] test: custom impl Buf&put Signed-off-by: discord9 --- src/buf/buf_impl.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/buf/buf_impl.rs b/src/buf/buf_impl.rs index 4ec9d12fe..4509386a8 100644 --- a/src/buf/buf_impl.rs +++ b/src/buf/buf_impl.rs @@ -2368,7 +2368,7 @@ pub trait Buf { if len == 0 { // return empty Bytes here to avoid stack overflow by prevent // `BytesMut::with_capacity(0)` trying to reuse allocation of `self`. - return crate::Bytes::new(); + crate::Bytes::new() } else { let mut ret = crate::BytesMut::with_capacity(len); ret.put(self.take(len)); @@ -2966,3 +2966,33 @@ impl> Buf for std::io::Cursor { // The existence of this function makes the compiler catch if the Buf // trait is "object-safe" or not. fn _assert_trait_object(_b: &dyn Buf) {} + +#[cfg(test)] +mod tests { + use crate::{Buf, BufMut as _, Bytes, BytesMut}; + + #[test] + fn default_copy_to_bytes_impl() { + #[derive(Clone)] + struct Buffer(Bytes); + + impl Buf for Buffer { + fn remaining(&self) -> usize { + self.0.len() + } + + fn chunk(&self) -> &[u8] { + self.0.chunk() + } + + fn advance(&mut self, cnt: usize) { + self.0.advance(cnt); + } + } + + let buf = Buffer(Bytes::new()); + + let mut ret = BytesMut::with_capacity(0); + ret.put(buf.clone()); + } +} From 222ce86fcc1c634e5615493e050a19b656091a4c Mon Sep 17 00:00:00 2001 From: discord9 Date: Fri, 14 Nov 2025 10:14:58 +0800 Subject: [PATCH 3/5] fix: bytes mut reuse only if has remaining Signed-off-by: discord9 --- src/buf/buf_impl.rs | 42 +++--------------------------------------- src/bytes_mut.rs | 8 +++++++- 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/src/buf/buf_impl.rs b/src/buf/buf_impl.rs index 4509386a8..192034fbe 100644 --- a/src/buf/buf_impl.rs +++ b/src/buf/buf_impl.rs @@ -2365,15 +2365,9 @@ pub trait Buf { }); } - if len == 0 { - // return empty Bytes here to avoid stack overflow by prevent - // `BytesMut::with_capacity(0)` trying to reuse allocation of `self`. - crate::Bytes::new() - } else { - let mut ret = crate::BytesMut::with_capacity(len); - ret.put(self.take(len)); - ret.freeze() - } + let mut ret = crate::BytesMut::with_capacity(len); + ret.put(self.take(len)); + ret.freeze() } /// Creates an adaptor which will read at most `limit` bytes from `self`. @@ -2966,33 +2960,3 @@ impl> Buf for std::io::Cursor { // The existence of this function makes the compiler catch if the Buf // trait is "object-safe" or not. fn _assert_trait_object(_b: &dyn Buf) {} - -#[cfg(test)] -mod tests { - use crate::{Buf, BufMut as _, Bytes, BytesMut}; - - #[test] - fn default_copy_to_bytes_impl() { - #[derive(Clone)] - struct Buffer(Bytes); - - impl Buf for Buffer { - fn remaining(&self) -> usize { - self.0.len() - } - - fn chunk(&self) -> &[u8] { - self.0.chunk() - } - - fn advance(&mut self, cnt: usize) { - self.0.advance(cnt); - } - } - - let buf = Buffer(Bytes::new()); - - let mut ret = BytesMut::with_capacity(0); - ret.put(buf.clone()); - } -} diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 333a0ae1e..663ba9557 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1203,7 +1203,7 @@ unsafe impl BufMut for BytesMut { Self: Sized, { // When capacity is zero, try reusing allocation of `src`. - if self.capacity() == 0 { + if self.capacity() == 0 && src.has_remaining() { let src_copy = src.copy_to_bytes(src.remaining()); drop(src); match src_copy.try_into_mut() { @@ -1512,6 +1512,12 @@ fn original_capacity_from_repr(repr: usize) -> usize { mod tests { use super::*; + #[test] + fn test_bytes_mut_reuse() { + let mut buf = BytesMut::new(); + buf.put(&[] as &[u8]); + } + #[test] fn test_original_capacity_to_repr() { assert_eq!(original_capacity_to_repr(0), 0); From 1332de7611f12e37b2fc6e0e365fef127ac8e14b Mon Sep 17 00:00:00 2001 From: discord9 Date: Fri, 14 Nov 2025 17:25:48 +0800 Subject: [PATCH 4/5] per review Signed-off-by: discord9 --- src/bytes_mut.rs | 13 +++++-------- tests/test_buf_mut.rs | 9 +++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 663ba9557..b0d1caebc 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1202,8 +1202,11 @@ unsafe impl BufMut for BytesMut { where Self: Sized, { - // When capacity is zero, try reusing allocation of `src`. - if self.capacity() == 0 && src.has_remaining() { + if !src.has_remaining() { + // prevent calling `copy_to_bytes`->`put`->`copy_to_bytes` infintely when src is empty + return; + } else if self.capacity() == 0 && src.has_remaining() { + // When capacity is zero, try reusing allocation of `src`. let src_copy = src.copy_to_bytes(src.remaining()); drop(src); match src_copy.try_into_mut() { @@ -1512,12 +1515,6 @@ fn original_capacity_from_repr(repr: usize) -> usize { mod tests { use super::*; - #[test] - fn test_bytes_mut_reuse() { - let mut buf = BytesMut::new(); - buf.put(&[] as &[u8]); - } - #[test] fn test_original_capacity_to_repr() { assert_eq!(original_capacity_to_repr(0), 0); diff --git a/tests/test_buf_mut.rs b/tests/test_buf_mut.rs index 9eb0bffdc..f1bd3b0a0 100644 --- a/tests/test_buf_mut.rs +++ b/tests/test_buf_mut.rs @@ -273,3 +273,12 @@ fn copy_from_slice_panics_if_different_length_2() { let slice = unsafe { UninitSlice::from_raw_parts_mut(data.as_mut_ptr(), 3) }; slice.copy_from_slice(b"abcd"); } + +/// Test if with zero capacity BytesMut does not infinitely recurse in put from Buf +#[test] +fn test_bytes_mut_reuse() { + let mut buf = BytesMut::new(); + buf.put(&[] as &[u8]); + let mut buf = BytesMut::new(); + buf.put(&[1u8, 2, 3] as &[u8]); +} From 90522952446343bea54bbd3ac1df1e8eb7b34717 Mon Sep 17 00:00:00 2001 From: discord9 Date: Fri, 14 Nov 2025 17:27:09 +0800 Subject: [PATCH 5/5] chore Signed-off-by: discord9 --- src/bytes_mut.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index b0d1caebc..565e91d9b 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1205,7 +1205,7 @@ unsafe impl BufMut for BytesMut { if !src.has_remaining() { // prevent calling `copy_to_bytes`->`put`->`copy_to_bytes` infintely when src is empty return; - } else if self.capacity() == 0 && src.has_remaining() { + } else if self.capacity() == 0 { // When capacity is zero, try reusing allocation of `src`. let src_copy = src.copy_to_bytes(src.remaining()); drop(src);