buf: assert Buf contract invariants in default methods - #847
Open
puretechteam wants to merge 1 commit into
Open
puretechteam wants to merge 1 commit into
puretechteam wants to merge 1 commit into
Conversation
Custom Buf implementations that violate the documented contract (remaining() > 0 with chunk() == &[], or chunks_vectored returning n > dst.len()) cause opaque index out of bounds panics in the default method bodies and the IntoIter / Take / Chain adapters. Add debug_assert!s at the affected sites so violating impls surface a named panic message naming the violated contract. Release behavior is unchanged for well-behaved impls; the safe slice / index ops continue to bounds-check before any unsafe, preserving the maintainer's position that panics are acceptable but UB is not. Also tighten the rustdoc on Buf::chunk and Buf::chunks_vectored to spell out the chunk().len() <= remaining() and n <= dst.len() invariants explicitly. Add regression tests covering each of the five sites from tokio-rs#833 plus the get_u8 / get_i8 parallels, using a custom Buf impl (InconsistentChunk) and a chunks_vectored-overreporting impl (OverreportingChunks). Refs: tokio-rs#833
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Custom
Bufimplementations that violate the documented contract currentlyproduce opaque
index out of boundspanics from inside the default methodbodies and the
IntoIter/Take/Chainadapters. This patch addsdebug_assert!s at each affected site so a violating impl surfaces a panicnaming the violated contract, and tightens the rustdoc to spell out the
invariants explicitly.
Closes #833.
Invariant being defended
A custom
Bufimplementation must satisfy:chunk() == &[]iffremaining() == 0chunk().len() <= remaining()chunks_vectoredreturnsn <= dst.len()When a custom impl breaks any of these, the consumer previously panicked with
the raw
index out of bounds: the len is 0 but the index is 0form, makingthe contract violation hard to diagnose.
What the assertions protect
debug_assert!s added at:Buf::get_u8andBuf::get_i8(default method bodies insrc/buf/buf_impl.rs) — assert!chunk.is_empty()after theremaining() >= 1guard.Buf::try_get_u8andBuf::try_get_i8(default method bodies insrc/buf/buf_impl.rs) — same shape.IntoIter::next(src/buf/iter.rs) — same shape after thehas_remaining()guard.Take::chunks_vectored(src/buf/take.rs) — assertcnt <= dst.len()immediately after the innerchunks_vectoredreturns, before the
dst[..cnt]slice and before the twounsafe { transmute }lifetime extensions.Chain::chunks_vectored(src/buf/chain.rs) — two assertions:one after
a.chunks_vectored(dst)and one after the cumulativea + bwrite.Release-mode behavior is unchanged.
debug_assert!is compiled out inrelease; the original safe slice / index operations continue to bounds-check
before any
unsafeblock is reached. Per the maintainer's stated positionin #833 (comment 4776265881),
panics from contract violations are acceptable; UB is not.
get_u8andget_i8are included as obvious parallels oftry_get_u8and
try_get_i8even though they are not explicitly listed in theissue body — flagging here so this can be discussed.
Documentation clarified
Two sentences added to the
Buftrait rustdoc:Buf::chunk(after the existing iff clause): "chunk().len()mustsatisfy
chunk().len() <= remaining()."Buf::chunks_vectored(in the Implementer notes section): "Thereturn value must satisfy
n <= dst.len()."These are non-tautological additions; the existing iff clause on
chunkdid not preclude an impl that returns a chunk longer than
remaining().Regression tests
Added in
tests/test_buf.rsandtests/test_iter.rs. Each uses a customBufimpl that violates the contract and asserts that the newdebug_assert!fires via#[should_panic(expected = "Buf contract")].The matching string locks in the panic message — a regression to the
opaque
index out of boundspanic would fail the test.InconsistentChunk—remaining() = 1,chunk() = &[].Drives
try_get_u8,try_get_i8,get_u8,get_i8,IntoIter::next.OverreportingChunks—chunks_vectoredreturns 2 from a1-element
dst.Drives
Take::chunks_vectoredandChain::chunks_vectored.Each test is deterministic, isolated, and gated with
#[cfg(feature = "std")]where it depends on
IoSlice.How tests and validation were run
Pre-existing
cargo clippy --all-features -- -D warningserrors in theupstream tree (
items_after_test_module, etc.) are unrelated to thispatch; clippy is not enforced in CI for this repo.
Scope
This change is scoped to issue #833. No public API surface changes; no
behavior change in release builds for well-behaved
Bufimpls.Note: this patch hardens the consumer side (default method bodies and
adapters), which is complementary to — and not in conflict with — the
implementer-side flexibility preserved by
#754 (which weakened the
chunks_vectoreddoc claim rather than adding runtime defense atimplementer call sites).
Refs: #833