diff --git a/tonic/src/codec/encode.rs b/tonic/src/codec/encode.rs index bf985fe9b..c428659e5 100644 --- a/tonic/src/codec/encode.rs +++ b/tonic/src/codec/encode.rs @@ -172,9 +172,7 @@ where let offset = buf.len(); buf.reserve(HEADER_SIZE); - unsafe { - buf.advance_mut(HEADER_SIZE); - } + buf.put_slice(&[0u8; HEADER_SIZE]); if let Some(encoding) = compression_encoding { uncompression_buf.clear(); @@ -399,3 +397,56 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::panic::catch_unwind; + + struct PanickingEncoder; + + impl Encoder for PanickingEncoder { + type Item = String; + type Error = Status; + + fn encode( + &mut self, + _item: Self::Item, + _dst: &mut EncodeBuf<'_>, + ) -> Result<(), Self::Error> { + panic!("encoder deliberate panic for testing exception safety"); + } + } + + #[test] + fn encode_item_exception_safety_on_panic() { + let mut encoder = PanickingEncoder; + let mut buf = BytesMut::new(); + let mut uncompression_buf = BytesMut::new(); + + let result = catch_unwind(std::panic::AssertUnwindSafe(|| { + encode_item( + &mut encoder, + &mut buf, + &mut uncompression_buf, + None, + None, + BufferSettings::default(), + "test".to_string(), + ) + })); + + assert!(result.is_err(), "Encoder panic should unwind correctly."); + // Buffer must only contain initialized bytes (5 zero bytes written by put_slice). + assert_eq!( + buf.len(), + HEADER_SIZE, + "Buffer length should reflect reserved header bytes." + ); + assert_eq!( + &buf[..], + &[0u8; HEADER_SIZE], + "Buffer must contain only initialized zero bytes." + ); + } +}