fix: drop unreachable sub-conditions from the cbor_decode corrupt-meta guard - #269
Open
thedavidmeister wants to merge 1 commit into
Open
fix: drop unreachable sub-conditions from the cbor_decode corrupt-meta guard#269thedavidmeister wants to merge 1 commit into
thedavidmeister wants to merge 1 commit into
Conversation
…a guard Closes #190 serde_cbor 0.11.2 reports every eof error over a slice at the slice length: SliceRead::end raises it at self.slice.len(), and the Deserializer::error path only fires once next()/peek() return None, which is index == len. So error.offset() == len whenever is_eof(), and the else arm mapping "eof elsewhere" to SerdeCborError was unreachable. track and metas were pushed in the same iteration with an early return between them, so track.len() != metas.len() could never hold at the guard and track.is_empty() could never differ from metas.is_empty(). track only ever had its last element read, so it is now a single offset. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 27 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Aug 25, 2026
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.
Closes #190.
The issue asked for a decision: keep the two unreachable sub-conditions as defensive redundancy, or simplify so every guard is exercisable. This takes the second option — a guard that reads as if it enforces a check it cannot enforce is the liability, and a comment saying so would be a second one.
error.offset() == lenis not a checkRe-derived from the serde_cbor 0.11.2 source rather than only empirically. Over a slice there are exactly two ways an eof error is built:
SliceRead::end(src/read.rs) raisesEofWhileParsingValuewith offsetself.slice.len();Deserializer::error(src/de.rs) stampsself.read.offset(), and the eof codes only reach it from anext()/peek()that returnedNone, which forSliceReadmeansindex == slice.len().Deserializer::from_sliceusesSliceRead, andlenincbor_decodeis exactly that slice's length (data.len(), minus the 8 prefix bytes when the slice isdata[8..]). Sois_eof()impliesoffset() == len, and theelsearm mapping "eof at the wrong offset" toSerdeCborErrorcould not run. The eof arm now just ends the loop; trailing truncation is caught where it always actually was, bylen != consumed.trackcollapses to one offsettrack.pushandmetas.pushare in the same iteration with an early return between them, sotrack.len() != metas.len()cannot hold at the guard andtrack.is_empty()cannot differ frommetas.is_empty(). Removing only the length cross-check would have lefttrack.is_empty()as a fresh equivalent mutant, so the wholeVecgoes: it only ever had its last element read, and it is now a singleconsumed: usize. That also drops thetrack[track.len() - 1]index whose bounds safety rested on the redundanttrack.is_empty().Both surviving sub-conditions are exercisable, and the mutation evidence below shows each one killed.
Behaviour
None intended. No input changes result or error variant; the diff removes conditions no input can make evaluate differently.
Overlap with in-flight work
Textual conflicts in
cbor_decodeare likely with #186, #188, #191 and #203, which change what the decode loop does with items (drop unknown magics, drop items missing keys 0/1, reject duplicate map keys) and with the document prefix. This change is orthogonal in intent — it removes dead sub-conditions and changes no accept/reject decision — so whichever lands second should keep the other's semantics and rebase the guard onto them. #187 (adding aKnownMagicvariant) does not touch this function.QA
test_cbor_decode_truncated_item_is_corrupt— does NOT fail on base, and cannot: the diff removes sub-conditions no input can make observable, so it is behaviour-preserving by construction, which is the issue's own finding. Verified by reconstructing the pre-fixcbor_decodebyte-for-byte in the tree with the new test present and runningcargo test -p rain-metadata --lib meta::tests::test_cbor_decode— 7 passed, 0 failed. It discriminates against mutants of the surviving guard instead (next line). What it adds over the existing tests is theEofWhileParsingMappath ([0xa2, 0x00, 0x41, 0x01], a map header promising an entry the input does not carry), which no existing test reaches; the other two cases pin eof viaSliceRead::endfor a truncated sole item and for a truncated item after a complete one.cbor_decode, suite run, mutation reverted.if metas.is_empty() || len != consumed→if len != consumed→ killed bytest_cbor_decode_empty_is_corrupt(1 failed).if metas.is_empty() || len != consumed→if metas.is_empty()→ killed bytest_cbor_decode_trailing_truncated_is_corruptandtest_cbor_decode_truncated_item_is_corrupt(2 failed).false→Err(Error::SerdeCborError(error))?(loop no longer ends on eof) → killed bytest_cbor_decode_empty_is_corrupt,test_cbor_decode_trailing_truncated_is_corrupt,test_cbor_decode_truncated_item_is_corrupt(3 failed).consumed = deserializer.byte_offset()→consumed = len→ killed bytest_cbor_decode_trailing_truncated_is_corruptandtest_cbor_decode_truncated_item_is_corrupt(2 failed).SliceRead::endandDeserializer::error, cited above) — not the behaviour ofcbor_decode; and RFC 8949 head bytes for the test inputs, which are handwritten byte by byte (handwritten_map()plus0xa2 0x00 …) rather than produced bycbor_encode.offset() == lenequality, (2)track.len() != metas.len()— and offers keep-with-comment or simplify. Both covered by simplifying; the third sub-conditiontrack.is_empty(), which (2)'s removal would have turned into a new equivalent mutant, is covered too.Also run locally:
cargo test -p rain-metadata --lib meta::tests::51 passed,cargo clippy -p rain-metadata --lib --all-targetsclean,cargo fmt --checkclean. The full suite was not run locally by request (many sibling agents on this machine); CI runs it.