Skip to content

Challenge 21: Verify safety of StrSearcher with Kani - #621

Open
v3risec wants to merge 1 commit into
model-checking:mainfrom
v3risec:challenge-21-string-searcher
Open

Challenge 21: Verify safety of StrSearcher with Kani#621
v3risec wants to merge 1 commit into
model-checking:mainfrom
v3risec:challenge-21-string-searcher

Conversation

@v3risec

@v3risec v3risec commented Aug 3, 2026

Copy link
Copy Markdown

Summary

This PR adds Kani verification for the substring searcher in core::str::pattern, covering the empty-needle implementation and the forward and reverse Two-Way search methods required by Challenge 21.

Verification Coverage Report (12/12 Harnesses Verified)

Searcher Target Coverage
Empty needle next Verifies forward Match/Reject alternation, completion, cursor movement by one UTF-8 character, valid returned ranges, and invariant preservation.
Empty needle next_match Verifies the finite Match filter, including the path that skips one Reject, without a loop contract or fixed unwind bound.
Empty needle next_reject Verifies the finite Reject filter, including the path that skips one Match, without a loop contract or fixed unwind bound.
Empty needle next_back Verifies reverse Match/Reject alternation, completion, cursor movement by one UTF-8 character, valid returned ranges, and invariant preservation.
Empty needle next_match_back Verifies the finite reverse Match filter, including the path that skips one Reject.
Empty needle next_reject_back Verifies the finite reverse Reject filter, including the path that skips one Match.
Two-Way next Verifies a representative real RejectAndMatch candidate, summarized forward/reverse byte scans, rejection boundary repair, forward progress, valid Match/Reject ranges, and invariant preservation.
Two-Way next_match Verifies MatchOnly search from an arbitrary failed-candidate prefix through one real candidate and a conservative suffix summary that covers a later Match or exhaustion.
Two-Way next_reject Verifies an arbitrary prefix of Match results followed by one real next step, covering every Reject or Done exit while checking progress and invariant preservation.
Two-Way next_back Verifies the reverse RejectAndMatch path, summarized reverse/forward byte scans, rejection boundary repair, reverse progress, valid Match/Reject ranges, and invariant preservation.
Two-Way next_match_back Verifies reverse MatchOnly search from an arbitrary failed-candidate prefix through one real candidate and a conservative suffix summary.
Two-Way next_reject_back Verifies an arbitrary prefix of reverse Match results followed by one real next_back step, covering every Reject or Done exit.

Verification Approach

The verification defines a stable-state invariant C for both StrSearcher implementations.

For EmptyNeedle, C requires the forward and reverse cursors to remain in bounds and on UTF-8 boundaries. The constructor harness establishes C, and each method harness starts from an arbitrary state satisfying C, checks the returned transition, and proves that C is preserved. The Kani-only character step nondeterministically selects a width from 1 through 4, bounds it by the remaining bytes, and imports the UTF-8 boundary fact permitted by Challenge 21. Because empty-needle results strictly alternate between Match and Reject, the four filtering methods need at most two concrete calls to next or next_back; this removes their production loops without adding a loop invariant or unwind bound.

For TwoWaySearcher, C captures the safety-relevant stable state: bounded UTF-8 cursor positions, nonzero bounded periods, bounded critical positions, consistent long-period sentinels, valid short-period memory states, and UTF-8 boundary facts for the short-period cuts. Each of the six method harnesses starts from a symbolic state satisfying this invariant and proves valid output ranges, cursor progress, preservation of preprocessing fields, valid memory-state transitions, and restoration of C before returning.

The Two-Way candidate loops use loop stubbing. A nondeterministic loop head represents any prefix of failed candidates, one representative candidate retains the real production control flow and arithmetic, and a suffix summary over-approximates any number of later failed candidates followed by either a Match, a Reject, or exhaustion. The summaries havoc only loop-carried state and constrain it with the corresponding loop-state relation.

The forward and reverse byte-scan loops are summarized by KaniTwoWayScan. A nondeterministic representative scan index checks the real indexing operations over the complete scan interval. The summary then conservatively chooses a mismatch position or a completed scan while retaining the first and last UTF-8 character bytes and the period probe needed to justify safe Match boundaries.

Two-Way Reject results may initially stop at byte positions that are not character boundaries. The Kani-only boundary-repair summaries first prove that the raw cursor is within the haystack, execute the real increment/decrement operation at a representative non-boundary loop head, and then use the Challenge 21 UTF-8 assumption that a character has at most three continuation bytes to summarize arrival at a nearby character boundary.

Verification Tradeoffs

Directly unwinding the nested Two-Way candidate and byte-scan loops does not finish within a practical verification budget and would make the proof depend on a fixed search length. The loop summaries avoid fixed unwind bounds for those loops and over-approximate their safety-relevant behavior.

The scan and candidate summaries are proof cuts for memory safety and valid UTF-8 result boundaries. They do not prove that every reported Match is the first or semantically correct substring match, nor do they prove the full functional correctness of the Two-Way algorithm.

The concrete harness inputs are symbolic valid UTF-8 subslices of 16-byte storage arrays. The loop summaries cover arbitrary loop prefixes and suffixes without fixed unwinding, but the current harness models do not constitute an arbitrary-length input proof.

Scope Assumptions

  • This PR verifies safety properties: absence of the challenge-listed undefined behavior, valid UTF-8 output boundaries, ordered in-bounds ranges, and preservation of the stated stable-state invariant.
  • UTF-8 decoding and boundary facts are imported only where permitted by the Challenge 21 assumptions.
  • The six Two-Way method proofs are conditional on type_invariant_two_way_searcher.
  • All Kani-specific implementations and proof summaries are isolated with #[cfg(kani)] and do not affect non-Kani builds.

Verification

All added Challenge 21 harnesses pass locally with Kani.

Resolves #278

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@v3risec
v3risec marked this pull request as ready for review August 6, 2026 13:57
@v3risec
v3risec requested a review from a team as a code owner August 6, 2026 13:57
@feliperodri

Copy link
Copy Markdown
Member

@v3risec is this a complement for #538?

@feliperodri feliperodri added the Challenge Used to tag a challenge label Aug 6, 2026
@v3risec

v3risec commented Aug 7, 2026

Copy link
Copy Markdown
Author

@feliperodri Not exactly. The main differences are:

  • Invariant: Verify safety of StrSearcher (Challenge 21) #538 mainly constrains the Two-Way cursors to be in bounds and on UTF-8 boundaries. This PR additionally constrains the period, critical positions, long/short-period states, and valid memory/memory_back states.
  • Two-Way verification: Verify safety of StrSearcher (Challenge 21) #538 replaces TwoWaySearcher::new, next, and next_back with nondeterministic abstractions satisfying output constraints. This PR preserves a representative iteration of the real control flow, indexing, arithmetic, and state updates, while summarizing the scans and remaining loop iterations.
  • Properties checked: In addition to valid UTF-8 result ranges and invariant preservation, this PR checks cursor progress, preprocessing-field preservation, memory transitions, and the relationship between returned ranges and the resulting state.
  • EmptyNeedle: The coverage mostly overlaps, although this PR keeps the finite filtering behavior more concrete instead of replacing it with a general nondeterministic result.

So I would describe this PR as an alternative and strengthening of the Challenge 21 portion of #538, rather than a direct complement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Challenge Used to tag a challenge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Challenge 21: Verify the safety of substring-related functions in str::pattern

2 participants