Skip to content

Commit ea6595c

Browse files
zh-jqcursoragent
andcommitted
vey-io-ext: use MaybeUninit buffers in Flex/Limited BufReader
Avoid exposing uninitialized spare capacity through ReadBuf::new by storing Box<[MaybeUninit<u8>]> and filling via ReadBuf::uninit. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent aec7209 commit ea6595c

2 files changed

Lines changed: 57 additions & 22 deletions

File tree

lib/vey-io-ext/src/stream/buf/flex.rs

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use std::io;
88
use std::io::IoSlice;
9+
use std::mem::MaybeUninit;
910
use std::pin::Pin;
1011
use std::task::{Context, Poll, ready};
1112

@@ -20,7 +21,7 @@ pin_project! {
2021
pub struct FlexBufReader<R> {
2122
#[pin]
2223
inner: R,
23-
buf: Box<[u8]>,
24+
buf: Box<[MaybeUninit<u8>]>,
2425
pos: usize,
2526
cap: usize,
2627
}
@@ -35,7 +36,12 @@ impl<R> FlexBufReader<R> {
3536

3637
/// Creates a new `BufReader` with the specified buffer capacity.
3738
pub fn with_capacity(capacity: usize, inner: R) -> Self {
38-
Self::with_buffer(vec![0; capacity], 0, inner)
39+
Self {
40+
inner,
41+
buf: Box::new_uninit_slice(capacity),
42+
pos: 0,
43+
cap: 0,
44+
}
3945
}
4046

4147
/// Creates a new `BufReader` with BytesMut
@@ -45,15 +51,27 @@ impl<R> FlexBufReader<R> {
4551
Self::with_buffer(vec, len, inner)
4652
}
4753

48-
/// Creates a new `BufReader` with a existed buffer
49-
pub fn with_buffer(mut buffer: Vec<u8>, len: usize, inner: R) -> Self {
50-
unsafe {
51-
// safe here as we didn't use the uninitialized data
52-
buffer.set_len(buffer.capacity())
54+
/// Creates a new `BufReader` with an existing buffer.
55+
///
56+
/// The first `len` bytes of `buffer` are treated as initialized; any spare
57+
/// capacity becomes uninitialized space for later reads via [`ReadBuf::uninit`].
58+
pub fn with_buffer(buffer: Vec<u8>, len: usize, inner: R) -> Self {
59+
let len = len.min(buffer.len());
60+
let mut buffer = buffer;
61+
buffer.truncate(len);
62+
let (ptr, init_len, capacity) = buffer.into_raw_parts();
63+
debug_assert_eq!(init_len, len);
64+
// SAFETY: `into_raw_parts` yields `init_len` initialized bytes and spare
65+
// capacity that we expose as `MaybeUninit`. Ownership moves into `Box`.
66+
let buf = unsafe {
67+
Box::from_raw(std::ptr::slice_from_raw_parts_mut(
68+
ptr as *mut MaybeUninit<u8>,
69+
capacity,
70+
))
5371
};
5472
Self {
5573
inner,
56-
buf: buffer.into_boxed_slice(),
74+
buf,
5775
pos: 0,
5876
cap: len,
5977
}
@@ -86,8 +104,14 @@ impl<R> FlexBufReader<R> {
86104

87105
pub fn into_parts(self) -> (Bytes, R) {
88106
if self.pos < self.cap {
89-
let mut bytes = Bytes::from(self.buf);
90-
let _ = bytes.split_off(self.cap);
107+
// SAFETY: `buf[..cap]` was initialized by `with_buffer` and/or
108+
// `ReadBuf::uninit` fills; spare capacity beyond `cap` stays unused.
109+
let vec = unsafe {
110+
let capacity = self.buf.len();
111+
let ptr = Box::into_raw(self.buf) as *mut u8;
112+
Vec::from_raw_parts(ptr, self.cap, capacity)
113+
};
114+
let mut bytes = Bytes::from(vec);
91115
bytes.advance(self.pos);
92116
(bytes, self.inner)
93117
} else {
@@ -99,7 +123,8 @@ impl<R> FlexBufReader<R> {
99123
///
100124
/// Unlike `fill_buf`, this will not attempt to fill the buffer if it is empty.
101125
pub fn buffer(&self) -> &[u8] {
102-
&self.buf[self.pos..self.cap]
126+
// SAFETY: `pos..cap` is always initialized (constructor or last fill).
127+
unsafe { self.buf[self.pos..self.cap].assume_init_ref() }
103128
}
104129

105130
/// Invalidates all data in the internal buffer.
@@ -143,12 +168,13 @@ impl<R: AsyncRead> AsyncBufRead for FlexBufReader<R> {
143168
// to tell the compiler that the pos..cap slice is always valid.
144169
if *me.pos >= *me.cap {
145170
debug_assert!(*me.pos == *me.cap);
146-
let mut buf = ReadBuf::new(me.buf);
171+
let mut buf = ReadBuf::uninit(me.buf);
147172
ready!(me.inner.poll_read(cx, &mut buf))?;
148173
*me.cap = buf.filled().len();
149174
*me.pos = 0;
150175
}
151-
Poll::Ready(Ok(&me.buf[*me.pos..*me.cap]))
176+
// SAFETY: `pos..cap` is initialized by the fill above or by construction.
177+
Poll::Ready(Ok(unsafe { me.buf[*me.pos..*me.cap].assume_init_ref() }))
152178
}
153179

154180
fn consume(self: Pin<&mut Self>, amt: usize) {
@@ -257,4 +283,14 @@ mod tests {
257283
restored.read_to_end(&mut out).await.unwrap();
258284
assert_eq!(out, b"prefrest");
259285
}
286+
287+
#[tokio::test]
288+
async fn with_capacity_reads_without_prior_zeroing() {
289+
let content = b"hello uninit";
290+
let stream = tokio_test::io::Builder::new().read(content).build();
291+
let mut v = FlexBufReader::with_capacity(64, stream);
292+
let mut out = Vec::new();
293+
v.read_to_end(&mut out).await.unwrap();
294+
assert_eq!(out, content);
295+
}
260296
}

lib/vey-io-ext/src/stream/buf/limited.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use std::io;
77
use std::io::IoSlice;
8+
use std::mem::MaybeUninit;
89
use std::pin::Pin;
910
use std::sync::Arc;
1011
use std::task::{Context, Poll, ready};
@@ -21,7 +22,7 @@ pin_project! {
2122
#[pin]
2223
inner: LimitedReader<R>,
2324
stats: ArcLimitedReaderStats,
24-
buf: Box<[u8]>,
25+
buf: Box<[MaybeUninit<u8>]>,
2526
pos: usize,
2627
cap: usize,
2728
}
@@ -73,11 +74,10 @@ where
7374
direct_stats: ArcLimitedReaderStats,
7475
buffer_stats: ArcLimitedReaderStats,
7576
) -> Self {
76-
let buffer = vec![0; capacity];
7777
LimitedBufReader {
7878
inner: LimitedReader::local_limited(inner, shift_millis, max_bytes, direct_stats),
7979
stats: buffer_stats,
80-
buf: buffer.into_boxed_slice(),
80+
buf: Box::new_uninit_slice(capacity),
8181
pos: 0,
8282
cap: 0,
8383
}
@@ -88,11 +88,10 @@ where
8888
from: LimitedReader<R>,
8989
buffer_stats: ArcLimitedReaderStats,
9090
) -> Self {
91-
let buffer = vec![0; capacity];
9291
LimitedBufReader {
9392
inner: from,
9493
stats: buffer_stats,
95-
buf: buffer.into_boxed_slice(),
94+
buf: Box::new_uninit_slice(capacity),
9695
pos: 0,
9796
cap: 0,
9897
}
@@ -104,11 +103,10 @@ where
104103
direct_stats: ArcLimitedReaderStats,
105104
buffer_stats: ArcLimitedReaderStats,
106105
) -> Self {
107-
let buffer = vec![0; capacity];
108106
LimitedBufReader {
109107
inner: LimitedReader::new(inner, direct_stats),
110108
stats: buffer_stats,
111-
buf: buffer.into_boxed_slice(),
109+
buf: Box::new_uninit_slice(capacity),
112110
pos: 0,
113111
cap: 0,
114112
}
@@ -180,13 +178,14 @@ impl<R: AsyncRead> AsyncBufRead for LimitedBufReader<R> {
180178
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
181179
let me = self.project();
182180
if *me.pos >= *me.cap {
183-
let mut buf = ReadBuf::new(me.buf);
181+
let mut buf = ReadBuf::uninit(me.buf);
184182
ready!(me.inner.poll_read(cx, &mut buf))?;
185183
*me.cap = buf.filled().len();
186184
*me.pos = 0;
187185
}
188186

189-
Poll::Ready(Ok(&me.buf[*me.pos..*me.cap]))
187+
// SAFETY: `pos..cap` is initialized by `ReadBuf::uninit` fills.
188+
Poll::Ready(Ok(unsafe { me.buf[*me.pos..*me.cap].assume_init_ref() }))
190189
}
191190

192191
fn consume(self: Pin<&mut Self>, amt: usize) {

0 commit comments

Comments
 (0)