Skip to content

Commit 1d7979f

Browse files
brianpanefolkertdev
authored andcommitted
Declare BitReader::refill unsafe and document safety prerequisites at callsites
1 parent 9b2b015 commit 1d7979f

File tree

2 files changed

+76
-14
lines changed

2 files changed

+76
-14
lines changed

zlib-rs/src/inflate.rs

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,8 @@ impl State<'_> {
515515
let avail_out = self.writer.remaining();
516516

517517
if avail_in >= INFLATE_FAST_MIN_HAVE && avail_out >= INFLATE_FAST_MIN_LEFT {
518-
inflate_fast_help(self, 0);
518+
// SAFETY: INFLATE_FAST_MIN_HAVE is enough bytes remaining to satisfy the precondition.
519+
unsafe { inflate_fast_help(self, 0) };
519520
match self.mode {
520521
Mode::Len => {}
521522
_ => return ControlFlow::Continue(()),
@@ -571,7 +572,10 @@ impl State<'_> {
571572
// bytes
572573
if avail_in >= INFLATE_FAST_MIN_HAVE && avail_out >= INFLATE_FAST_MIN_LEFT {
573574
restore!();
574-
inflate_fast_help(self, 0);
575+
// SAFETY: INFLATE_FAST_MIN_HAVE >= 15.
576+
// Note that the restore macro does not do anything that would
577+
// reduce the number of bytes available.
578+
unsafe { inflate_fast_help(self, 0) };
575579
return ControlFlow::Continue(());
576580
}
577581

@@ -1817,32 +1821,52 @@ impl State<'_> {
18171821
}
18181822
}
18191823

1820-
fn inflate_fast_help(state: &mut State, start: usize) {
1824+
/// # Safety
1825+
///
1826+
/// `state.bit_reader` must have at least 15 bytes available to read, as
1827+
/// indicated by `state.bit_reader.bytes_remaining() >= 15`
1828+
unsafe fn inflate_fast_help(state: &mut State, start: usize) {
18211829
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
18221830
if crate::cpu_features::is_enabled_avx2_and_bmi2() {
1823-
// SAFETY: we've verified the target features
1831+
// SAFETY: we've verified the target features and the caller ensured enough bytes_remaining
18241832
return unsafe { inflate_fast_help_avx2(state, start) };
18251833
}
18261834

1827-
inflate_fast_help_vanilla(state, start);
1835+
// SAFETY: The caller ensured enough bytes_remaining
1836+
unsafe { inflate_fast_help_vanilla(state, start) };
18281837
}
18291838

1839+
/// # Safety
1840+
///
1841+
/// `state.bit_reader` must have at least 15 bytes available to read, as
1842+
/// indicated by `state.bit_reader.bytes_remaining() >= 15`
18301843
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
18311844
#[target_feature(enable = "avx2")]
18321845
#[target_feature(enable = "bmi2")]
18331846
#[target_feature(enable = "bmi1")]
18341847
unsafe fn inflate_fast_help_avx2(state: &mut State, start: usize) {
1835-
inflate_fast_help_impl::<{ CpuFeatures::AVX2 }>(state, start);
1848+
// SAFETY: `bytes_remaining` checked by our caller
1849+
unsafe { inflate_fast_help_impl::<{ CpuFeatures::AVX2 }>(state, start) };
18361850
}
18371851

1838-
fn inflate_fast_help_vanilla(state: &mut State, start: usize) {
1839-
inflate_fast_help_impl::<{ CpuFeatures::NONE }>(state, start);
1852+
/// # Safety
1853+
///
1854+
/// `state.bit_reader` must have at least 15 bytes available to read, as
1855+
/// indicated by `state.bit_reader.bytes_remaining() >= 15`
1856+
unsafe fn inflate_fast_help_vanilla(state: &mut State, start: usize) {
1857+
// SAFETY: `bytes_remaining` checked by our caller
1858+
unsafe { inflate_fast_help_impl::<{ CpuFeatures::NONE }>(state, start) };
18401859
}
18411860

1861+
/// # Safety
1862+
///
1863+
/// `state.bit_reader` must have at least 15 bytes available to read, as
1864+
/// indicated by `state.bit_reader.bytes_remaining() >= 15`
18421865
#[inline(always)]
1843-
fn inflate_fast_help_impl<const FEATURES: usize>(state: &mut State, _start: usize) {
1866+
unsafe fn inflate_fast_help_impl<const FEATURES: usize>(state: &mut State, _start: usize) {
18441867
let mut bit_reader = BitReader::new(&[]);
18451868
core::mem::swap(&mut bit_reader, &mut state.bit_reader);
1869+
debug_assert!(bit_reader.bytes_remaining() >= 15);
18461870

18471871
let mut writer = Writer::new(&mut []);
18481872
core::mem::swap(&mut writer, &mut state.writer);
@@ -1862,15 +1886,40 @@ fn inflate_fast_help_impl<const FEATURES: usize>(state: &mut State, _start: usiz
18621886
let mut bad = None;
18631887

18641888
if bit_reader.bits_in_buffer() < 10 {
1865-
bit_reader.refill();
1889+
debug_assert!(bit_reader.bytes_remaining() >= 15);
1890+
// Safety: Caller ensured that bit_reader has >= 15 bytes available; refill only needs 8.
1891+
unsafe { bit_reader.refill() };
18661892
}
1893+
// We had at least 15 bytes in the slice, plus whatever was in the buffer. After filling the
1894+
// buffer from the slice, we now have at least 8 bytes remaining in the slice, plus a full buffer.
1895+
debug_assert!(
1896+
bit_reader.bytes_remaining() >= 8 && bit_reader.bytes_remaining_including_buffer() >= 15
1897+
);
18671898

18681899
'outer: loop {
1900+
// This condition is ensured above for the first iteration of the `outer` loop. For
1901+
// subsequent iterations, the loop continuation condition is
1902+
// `bit_reader.bytes_remaining_including_buffer() > 15`. And because the buffer
1903+
// contributes at most 7 bytes to the result of bit_reader.bytes_remaining_including_buffer(),
1904+
// that means that the slice contains at least 8 bytes.
1905+
debug_assert!(
1906+
bit_reader.bytes_remaining() >= 8
1907+
&& bit_reader.bytes_remaining_including_buffer() >= 15
1908+
);
1909+
18691910
let mut here = {
18701911
let bits = bit_reader.bits_in_buffer();
18711912
let hold = bit_reader.hold();
18721913

1873-
bit_reader.refill();
1914+
// Safety: As described in the comments for the debug_assert at the start of
1915+
// the `outer` loop, it is guaranteed that `bit_reader.bytes_remaining() >= 8` here,
1916+
// which satisfies the safety precondition for `refill`. And, because the total
1917+
// number of bytes in `bit_reader`'s buffer plus its slice is at least 15, and
1918+
// `refill` moves at most 7 bytes from the slice to the buffer, the slice will still
1919+
// contain at least 8 bytes after this `refill` call.
1920+
unsafe { bit_reader.refill() };
1921+
// After the refill, there will be at least 8 bytes left in the bit_reader's slice.
1922+
debug_assert!(bit_reader.bytes_remaining() >= 8);
18741923

18751924
// in most cases, the read can be interleaved with the logic
18761925
// based on benchmarks this matters in practice. wild.
@@ -1909,7 +1958,14 @@ fn inflate_fast_help_impl<const FEATURES: usize>(state: &mut State, _start: usiz
19091958
// we have two fast-path loads: 10+10 + 15+5 = 40,
19101959
// but we may need to refill here in the worst case
19111960
if bit_reader.bits_in_buffer() < MAX_BITS + MAX_DIST_EXTRA_BITS {
1912-
bit_reader.refill();
1961+
debug_assert!(bit_reader.bytes_remaining() >= 8);
1962+
// Safety: On the first iteration of the `dolen` loop, we can rely on the
1963+
// invariant documented for the previous `refill` call above: after that
1964+
// operation, `bit_reader.bytes_remining >= 8`, which satisfies the safety
1965+
// precondition for this call. For subsequent iterations, this invariant
1966+
// remains true because nothing else within the `dolen` loop consumes data
1967+
// from the slice.
1968+
unsafe { bit_reader.refill() };
19131969
}
19141970

19151971
'dodist: loop {

zlib-rs/src/inflate/bitreader.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,16 @@ impl<'a> BitReader<'a> {
114114
}
115115

116116
#[inline(always)]
117-
pub fn refill(&mut self) {
117+
/// Copy enough bytes from the BitReader's underlying slice to fill the internal
118+
/// bit buffer with 7 bytes of data.
119+
///
120+
/// Safety:
121+
///
122+
/// `self.ptr` must point to at least 8 readable bytes, as indicated by `bytes_remaining()`
123+
pub unsafe fn refill(&mut self) {
118124
debug_assert!(self.bytes_remaining() >= 8);
119125

120-
// SAFETY: assertion above ensures we have 8 bytes to read for a u64.
126+
// SAFETY: caller ensures we have 8 bytes to read for a u64.
121127
let read = unsafe { core::ptr::read_unaligned(self.ptr.cast::<u64>()) }.to_le();
122128
self.bit_buffer |= read << self.bits_used;
123129
// this xor was previously a subtraction but was changed for performance reasons.

0 commit comments

Comments
 (0)