Challenge 20: Verify Char Searcher with Kani - #620
Draft
v3risec wants to merge 5 commits into
Draft
Conversation
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
This PR solves Challenge 20 by adding Kani verification for the char-related searchers in
core::str::pattern.The verification covers
CharSearcher, the genericMultiCharEqSearcher, and the public searchers for owned character arrays, borrowed character arrays, character slices, andFnMut(char) -> boolpredicates.For each searcher family, the proofs check that construction establishes a safety invariant, forward and reverse search operations preserve the safety-relevant state, and returned ranges are ordered, in bounds, and located on valid UTF-8 boundaries in the original haystack.
All verification-only implementations and abstractions are gated behind
#[cfg(kani)]. Normal library behavior is unchanged.Verification Coverage Report (36/36 Methods Verified, 42 Harnesses)
CharSearcherinto_searcher,next,next_match,next_reject,next_back,next_match_back, andnext_reject_back. The proofs cover arbitrary invariant-admitted cursor states within the bounded symbolic haystack, cached UTF-8 encodings for all character widths, direction-specific progress, and returned-range safety.MultiCharEqSearcherCharIndicesiterator to the exact active window of the original haystack and requires both ends of that window to be UTF-8 boundaries.CharArraySearcher[char; N]wrapper, including construction, delegation to the inner searcher, invariant preservation, and forward and reverse result ranges.CharArrayRefSearcher&[char; N]wrapper with the same constructor, state-preservation, and range-safety obligations.CharSliceSearcher&[char]patterns with symbolic slice length and all required search methods.CharPredicateSearcherFnMut(char) -> boolwhose result is symbolic on each executed call. Predicate state semantics are intentionally outside the proof.Each searcher has one constructor harness and six method harnesses, for a total of 42 Challenge 20 harnesses.
Verification Approach
The proofs define a safety invariant
Cfor each searcher family.For
CharSearcher,Cestablishes that:finger..finger_backis an ordered, in-bounds range in the haystack;utf8_sizeis in1..=4;utf8_encoded[..utf8_size]is exactly the UTF-8 encoding ofneedle.The constructor harness proves that the production
char::into_searcherimplementation establishes this invariant. Method harnesses start from arbitrary states satisfyingCwithin the bounded symbolic input and prove the required state transition and invariant preservation.For
MultiCharEqSearcher,Cestablishes that the internal byte iterator is safe and points to exactly the remainingCharIndiceswindow in the original haystack. The wrapper invariants for arrays, slices, and predicates reduce to this inner invariant because matcher output only selectsMatchversusReject; it does not determine the already-consumed UTF-8 range.The
nextandnext_backharnesses execute the production implementations and prove their exact safety projection: one complete UTF-8 character is consumed, only the appropriate end of the active window moves, and the returned range matches that movement. Filtered method harnesses additionally prove thatSomereturns a non-empty safe range and thatNoneexhausts the active window.CharSearcher::nextandnext_backhave verified Kani contracts. The Kani-onlynext_rejectandnext_reject_backoverrides use those contracts while verifying their surrounding default-search loops.Loop Verification
The search loops are handled with loop contracts or safety-only loop stubs instead of relying on a fixed unwind count.
For
CharSearcher::next_match, the Kani path checks a real representative loop iteration from an arbitrary valid loop-head state, proves strict progress, and uses a conservative stub for the unexecuted suffix.next_match_back,next_reject, andnext_reject_backuse inductive loop contracts that preserve the cached character representation and direction-specific cursor constraints.The four filtered
MultiCharEqSearchermethods (next_match,next_reject,next_match_back, andnext_reject_back) use loop stubbing:CharIndiceswindow.nextornext_backiteration, including the real matcher call for that iteration.Some/Noneexit satisfying the safety-relevant range and exhaustion conditions.CharIndicesstate for the summarized exit.The loop summary deliberately forgets matcher-internal state and does not claim first-match, first-reject, or rightmost-result semantics. It proves only the state and range properties required for searcher safety.
Verification Abstractions and Tradeoffs
The
memchrandmemrchrcalls used byCharSearcherare replaced with a shared conservative stub. It may returnNoneor any in-bounds occurrence of the requested byte and does not assume first- or last-occurrence semantics. A successful character match is accepted only after checking the complete candidate against the cached UTF-8 encoding ofneedle.The Kani path for symbolic-length
&[char]membership returns a nondeterministic boolean. The predicate harness likewise returns a fresh nondeterministic boolean on every executed predicate call. These are safety over-approximations because classification occurs afterCharIndiceshas already computed and consumed the UTF-8 range.For summarized predicate iterations, the loop stub does not execute the omitted
FnMutcalls or model their captured-state transitions. The PR therefore does not prove predicate call counts, captured state, side effects, panic behavior, or exact matching semantics. It proves only the normally returning classification outcomes relevant to cursor and returned-range safety.The proofs use Challenge 20's permitted assumptions about slice operations, valid UTF-8 haystacks, and the functional correctness of
str::validations. UTF-8 facts are imported only after the associated ordering and state-transition facts have been asserted.Scope Assumptions
SearcherandReverseSearchercontracts: returned indices are valid UTF-8 boundaries in the original haystack and preserve a valid search state.#[cfg(kani)].Notes
CharSearcher::next_rejectandnext_reject_backoverrides allow loop contracts to refer directly to concrete searcher state; non-Kani builds continue to use the trait defaults.memcmpmodel while checking the same 1-to-4-byte cached representation.MultiCharEqSearcher.Verification
All 42 added Challenge 20 harnesses pass locally with Kani. The seven
CharPredicateSearcherharnesses also pass after routing the filtered methods through the safety loop stubs.Resolves #277
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.