1//! An encapsulation of `BufReader`'s buffer management logic.
2//!
3//! This module factors out the basic functionality of `BufReader` in order to protect two core
4//! invariants:
5//! * `filled` bytes of `buf` are always initialized
6//! * `pos` is always <= `filled`
7//! Since this module encapsulates the buffer management logic, we can ensure that the range
8//! `pos..filled` is always a valid index into the initialized region of the buffer. This means
9//! that user code which wants to do reads from a `BufReader` via `buffer` + `consume` can do so
10//! without encountering any runtime bounds checks.
1112use crate::cmp;
13use crate::io::{self, BorrowedBuf, ErrorKind, Read};
14use crate::mem::MaybeUninit;
1516pub struct Buffer {
17// The buffer.
18buf: Box<[MaybeUninit<u8>]>,
19// The current seek offset into `buf`, must always be <= `filled`.
20pos: usize,
21// Each call to `fill_buf` sets `filled` to indicate how many bytes at the start of `buf` are
22 // initialized with bytes from a read.
23filled: usize,
24// This is the max number of bytes returned across all `fill_buf` calls. We track this so that we
25 // can accurately tell `read_buf` how many bytes of buf are initialized, to bypass as much of its
26 // defensive initialization as possible. Note that while this often the same as `filled`, it
27 // doesn't need to be. Calls to `fill_buf` are not required to actually fill the buffer, and
28 // omitting this is a huge perf regression for `Read` impls that do not.
29initialized: bool,
30}
3132impl Buffer {
33#[inline]
34pub fn with_capacity(capacity: usize) -> Self {
35let buf = Box::new_uninit_slice(capacity);
36Self { buf, pos: 0, filled: 0, initialized: false }
37 }
3839#[inline]
40pub fn try_with_capacity(capacity: usize) -> io::Result<Self> {
41match Box::try_new_uninit_slice(capacity) {
42Ok(buf) => Ok(Self { buf, pos: 0, filled: 0, initialized: false }),
43Err(_) => {
44Err(crate::hint::must_use(crate::io::Error::from_static_message(const {
&crate::io::SimpleMessage {
kind: ErrorKind::OutOfMemory,
message: "failed to allocate read buffer",
}
}))io::const_error!(ErrorKind::OutOfMemory, "failed to allocate read buffer"))
45 }
46 }
47 }
4849#[inline]
50pub fn buffer(&self) -> &[u8] {
51// SAFETY: self.pos and self.filled are valid, and self.filled >= self.pos, and
52 // that region is initialized because those are all invariants of this type.
53unsafe { self.buf.get_unchecked(self.pos..self.filled).assume_init_ref() }
54 }
5556#[inline]
57pub fn capacity(&self) -> usize {
58self.buf.len()
59 }
6061#[inline]
62pub fn filled(&self) -> usize {
63self.filled
64 }
6566#[inline]
67pub fn pos(&self) -> usize {
68self.pos
69 }
7071// This is only used by a test which asserts that the initialization-tracking is correct.
72#[cfg(test)]
73pub fn initialized(&self) -> bool {
74self.initialized
75 }
7677#[inline]
78pub fn discard_buffer(&mut self) {
79self.pos = 0;
80self.filled = 0;
81 }
8283#[inline]
84pub fn consume(&mut self, amt: usize) {
85self.pos = cmp::min(self.pos + amt, self.filled);
86 }
8788/// If there are `amt` bytes available in the buffer, pass a slice containing those bytes to
89 /// `visitor` and return true. If there are not enough bytes available, return false.
90#[inline]
91pub fn consume_with<V>(&mut self, amt: usize, mut visitor: V) -> bool92where
93V: FnMut(&[u8]),
94 {
95if let Some(claimed) = self.buffer().get(..amt) {
96visitor(claimed);
97// If the indexing into self.buffer() succeeds, amt must be a valid increment.
98self.pos += amt;
99true
100} else {
101false
102}
103 }
104105#[inline]
106pub fn unconsume(&mut self, amt: usize) {
107self.pos = self.pos.saturating_sub(amt);
108 }
109110/// Read more bytes into the buffer without discarding any of its contents
111pub fn read_more(&mut self, mut reader: impl Read) -> io::Result<usize> {
112let mut buf = BorrowedBuf::from(&mut self.buf[self.filled..]);
113114if self.initialized {
115unsafe { buf.set_init() };
116 }
117118reader.read_buf(buf.unfilled())?;
119self.filled += buf.len();
120self.initialized = buf.is_init();
121Ok(buf.len())
122 }
123124/// Remove bytes that have already been read from the buffer.
125pub fn backshift(&mut self) {
126self.buf.copy_within(self.pos..self.filled, 0);
127self.filled -= self.pos;
128self.pos = 0;
129 }
130131#[inline]
132pub fn fill_buf(&mut self, mut reader: impl Read) -> io::Result<&[u8]> {
133// If we've reached the end of our internal buffer then we need to fetch
134 // some more data from the reader.
135 // Branch using `>=` instead of the more correct `==`
136 // to tell the compiler that the pos..cap slice is always valid.
137if self.pos >= self.filled {
138if true {
if !(self.pos == self.filled) {
::core::panicking::panic("assertion failed: self.pos == self.filled")
};
};debug_assert!(self.pos == self.filled);
139140let mut buf = BorrowedBuf::from(&mut *self.buf);
141// SAFETY: `self.filled` bytes will always have been initialized.
142143if self.initialized {
144unsafe { buf.set_init() };
145 }
146147let result = reader.read_buf(buf.unfilled());
148149self.pos = 0;
150self.filled = buf.len();
151self.initialized = buf.is_init();
152153result?;
154 }
155Ok(self.buffer())
156 }
157}