Challenge 21 (partial): bounded Kani PROBE of shipped TwoWaySearcher::next_back short-period arms - #664
Challenge 21 (partial): bounded Kani PROBE of shipped TwoWaySearcher::next_back short-period arms#664ivmat wants to merge 2 commits into
Conversation
…:next_back short-period arms
The repo's `upstream_test` CI job runs `./x fmt --check` inside a rust-lang/rust checkout, which uses that repo's rustfmt.toml (style_edition 2024, use_small_heuristics = "Max"). This crate has no rustfmt.toml of its own, so a plain `cargo fmt` does not reproduce it. Formatting only: no harness, assertion, cover string, or bound changed.
|
Reviewed as part of Challenge 21 triage. This is a sound, honest diagnostic probe (your own header labels it But as a Ch21 solution it's 1/6 functions ( |
What this is
A single, bounded Kani harness (
check_twoway_search_back_arm_covers) that drives the unmodified,shipped
TwoWaySearcher::next_backthrough the real publicReverseSearcherAPI and proves — withinan explicitly stated bound — per-step memory safety, functional soundness of every returned
Match,bounded completeness (no silently-missed match at the fixture), and reachability of all five backward
search-loop arms.
Honest status up front: this is a PROBE, not a solution to Challenge 21. It does not satisfy the
challenge's success criteria on its own. It is offered as a differentiated partial contribution with
its residuals named at the headline, not buried.
What is proved (within the bound)
Fixture: needle
"abab"(fixed; short-period, non-degenerate on both critical factorizations),HAY_LEN = 5(fixed), haystack content symbolic over the 3-symbol representative alphabet{a,b,z},#[kani::unwind(6)](VM-confirmed, every unwinding assertion SUCCESS at this bound).start (adjacency/no-skip), re-derived from the source, not assumed.
Matchslice is byte-for-byte equal to the needle (not merelyin-bounds).
expected match set (
Option<usize>; the two candidate positions 0 and 1 are mutually exclusive at thisfixture) — a silently-missed real match fails an
assert_eq!directly.kani::covers witness that all five backward arms (byteset-skip,left-mismatch, memory_back-guarded fast-forward, match, terminal) genuinely fire within the bound.
Oracle is proven genuine (negative controls)
The soundness oracle was shown to catch a real defect at this exact fixture (HAY_LEN=5, needle
"abab"). The completeness (no-miss) oracle's design was validated the same way, but not at this exact
fixture size — disclosed honestly below rather than glossed over.
match_pos = self.end - needle.len() + 1, an off-by-one in theMatcharm)was run against this fixture and is VM-confirmed RED: the adjacency/slice-equality oracle catches
it directly (3 of 5 cover properties satisfied,
rc=1, FAILED).self.end -= self.period→self.end -= self.period + 1) does not diverge from the correctalgorithm at this exact HAY_LEN=5 fixture — confirmed by exhaustive offline enumeration over all 3^5
haystacks (0 divergent cases; the mutation is provably undetectable at this fixture size). At the
smallest fixture size that does discriminate it (HAY_LEN=6, otherwise the same harness design and the
same no-miss oracle shape), the same one-line mutation is VM-confirmed RED (5 of 5 cover properties
satisfied,
rc=1, FAILED on the no-miss assertion). That HAY_LEN=6 harness is a control only — it isnot part of this submission. It confirms the no-miss oracle design is discriminating, not that it
fires on this exact submitted fixture's completeness axis, which remains an open, disclosed residual
(this harness's HAY_LEN=5 fixture happens to be too small for this particular Galil-rule defect class
to surface observably; a different defect or a larger fixture might not have this gap).
What is NOT proved (residuals — headline, not footnote)
next_back.MatchOnlybackward path (next_match_back/rfind, whereuse_early_reject() == falseisgenuinely different control flow) — the 5-arm reachability here is witnessed under the
RejectAndMatchinstantiation only.
next_backsafety contract is future work: it needs a symbolic-bound quantified loopinvariant that this pinned toolchain cannot discharge (see the corrected note below).
Alternative direction toward a generic contract (diagnostic — not a commitment)
The bounded probe is not the only conceivable contribution here; the natural next rung is a generic
next_backsafety contract (symbolic needle, arbitrary length). The concrete blocker is specific andworth stating for other contributors: a generic proof needs a symbolic-bound quantified loop
invariant — a
forall-quantified "no match skipped in the region already scanned" invariant on thetwo-way search loop, whose stride is data-dependent (
needle.len(),self.period,i - crit_pos + 1). [Corrected 2026-08-30] At this submission's pinned toolchain (Kanid4df833/ CBMC 6.8.0), aforall-quantified predicate whose range is not a compile-time constant is not encoded by the defaultSAT backend — the quantifier is silently dropped, and this affects a flat assertion just as much as one
inside a
#[kani::loop_invariant]. A quantified loop invariant over a constant range does discharge.A generic
next_backcontract needs the data-dependent case, which is therefore out of scope here. Westate this as an observed limitation of this pinned toolchain, not a proof of impossibility. (An
earlier revision of this paragraph attributed the limitation to the quantifier's position — flat
assertion vs. loop invariant; a controlled 2×2 measurement at this pin showed it tracks the bound kind
instead.)
We are not claiming that adding quantified-loop-invariant support to Kani would be sufficient to
land the generic contract. The harder, still-open question is whether the two-way skip lemmas —
byteset-skip, the
crit_posleft-part shift, and the period/memoryfast-forward each preserving "nomatch in the skipped span" — are themselves tractable as bounded or contract-backed obligations. That
is a separate analysis. This note flags the capability gap so the frontier is visible; it is not a
promise of a tool change.
Reproduce
Verified at this branch's head with Kani 0.67.0 (d4df833) / CBMC 6.8.0 (the repo's pinned
toolchain):
VERIFICATION:- SUCCESSFUL, 5 of 5 cover properties satisfied.(Unwind is pinned in-source via
#[kani::unwind(6)].)Acceptance manifest
A machine-readable statement of exactly what these proofs establish and do not establish — one entry
per claim, with declared bounds, evidence, and assurance band, checked by a standalone validator — is
shipped as a real-world example in the format's own repo:
examples/verify-rust-std-pr664/acceptance.toml(format). It records the submitted probe (graded A3:
a red mutation control plus explicit
assumesnarrowing what the result covers) and an explicit A0gap entry for what this PR does not prove (the
MatchOnlybackward path; a generic symbolic-needlecontract). Restamped and re-verified at this branch's head (
09a3d026f66).Edited 2026-08-30: corrected the quantifier characterization under "Alternative direction" after a
controlled measurement at this PR's pinned toolchain, and added the acceptance-manifest section.