From e5dc5529dc75a6c6d5f1c27b69b6ab25012d2d22 Mon Sep 17 00:00:00 2001 From: Zorayr Saroyan Date: Fri, 14 Aug 2026 10:13:36 +0400 Subject: [PATCH] Initialise the internal buffer before handing it to the reader `with_capacity` called `set_len` on a `Vec` that was never written to, and `fill_buf` passes that buffer straight to the wrapped `Read` impl. Since `Read::read` only recommends against reading `buf` rather than forbidding it, an implementation that inspects its buffer observed uninitialised memory, which Miri reports as undefined behaviour. Allocate a zeroed buffer instead. This also drops the `read_initializer` call, whose nightly feature gate no longer exists in the compiler, so that path could not be enabled on any current toolchain anyway. Adds a regression test that fails under Miri if the buffer is left uninitialised. Co-Authored-By: Claude Opus 5 --- src/lib.rs | 30 ++++++++++++-------------- src/tests.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5da234d..28ed294 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,22 +143,20 @@ impl RevBufReader { /// } /// ``` pub fn with_capacity(capacity: usize, mut inner: R) -> RevBufReader { - unsafe { - let mut buffer = Vec::with_capacity(capacity); - buffer.set_len(capacity); - - #[cfg(feature = "read_initializer")] - inner.initializer().initialize(&mut buffer); - - inner - .seek(SeekFrom::End(0)) - .expect("Cannot find the end of the stream."); - RevBufReader { - inner, - buf: buffer.into_boxed_slice(), - pos: 0, - cap: 0, - } + // The buffer is handed to `inner`'s `Read` impl in `fill_buf`, so it + // has to be initialised: `Read::read` only *recommends* against + // reading `buf`, and an implementation that does so would otherwise + // observe uninitialised memory. + let buffer = vec![0; capacity]; + + inner + .seek(SeekFrom::End(0)) + .expect("Cannot find the end of the stream."); + RevBufReader { + inner, + buf: buffer.into_boxed_slice(), + pos: 0, + cap: 0, } } diff --git a/src/tests.rs b/src/tests.rs index 6df9ff3..ca6cf03 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -246,3 +246,63 @@ fn test_lines() { assert_eq!(it.next().unwrap().unwrap(), "a".to_string()); assert!(it.next().is_none()); } + +/// The internal buffer is handed to the wrapped reader in `fill_buf`, so it +/// has to be initialised before it gets there. Under Miri a regression shows +/// up as "reading memory ... but memory is uninitialized"; natively the +/// assertion below catches it. +#[test] +fn test_buffer_handed_to_reader_is_initialized() { + struct PeekingReader { + data: Vec, + pos: usize, + first_read: bool, + } + + impl Read for PeekingReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // A `Read` impl is only *recommended* not to read `buf`, so + // doing so is within contract and must be well defined. + if self.first_read { + self.first_read = false; + let sum = buf.iter().fold(0u64, |a, b| a.wrapping_add(*b as u64)); + assert_eq!(sum, 0, "buffer was not initialized before use"); + } + + let n = buf.len().min(self.data.len() - self.pos); + buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]); + self.pos += n; + Ok(n) + } + } + + impl Seek for PeekingReader { + fn seek(&mut self, from: SeekFrom) -> io::Result { + let len = self.data.len() as i64; + let target = match from { + SeekFrom::Start(n) => n as i64, + SeekFrom::End(n) => len + n, + SeekFrom::Current(n) => self.pos as i64 + n, + }; + if target < 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "negative seek", + )); + } + self.pos = (target as usize).min(self.data.len()); + Ok(self.pos as u64) + } + } + + let inner = PeekingReader { + data: (0u8..64).collect(), + pos: 0, + first_read: true, + }; + let mut reader = RevBufReader::with_capacity(128, inner); + + let mut out = Vec::new(); + reader.read_to_end(&mut out).unwrap(); + assert_eq!(out.len(), 64); +}