Challenge 8 (partial): no-UB proofs and sorting-correctness harnesses for smallsort - #678
Open
samiam713 wants to merge 1 commit into
Open
Challenge 8 (partial): no-UB proofs and sorting-correctness harnesses for smallsort#678samiam713 wants to merge 1 commit into
samiam713 wants to merge 1 commit into
Conversation
…sort Add a #[cfg(kani)] verify module to smallsort.rs: 48 harnesses proving absence of UB for the seven functions named by the challenge's success criteria and asserting sorting correctness (sortedness AND multiset-permutation, fully symbolic contents) for the three small_sort trait impls, one harness per concrete length up to the measured SAT-tractability frontier, with element types (i32, Cell<i32>, a non-Copy wrapper, u128, [u64; 11], u8) chosen to steer every specialization and dispatch branch. Callees of the beyond-frontier branches (sort8_stable, sort9_optimal) are verified directly at their call shapes. No production line changes. Towards model-checking#56. Co-authored-by: Claude (Anthropic AI) <noreply@anthropic.com>
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 is a partial contribution — it does not close Challenge 8. The challenge's
success criteria cover arbitrary valid lengths; the harnesses here verify the
smallsortmodule per concrete length with fully symbolic contents up to a measuredmodel-checking tractability frontier, and past that frontier coverage is per-callee
rather than end-to-end (the exact gap is itemized below). Whether and how a partial
result counts toward the challenge is entirely the maintainers' call; this PR only
reports what was proven — and, quantitatively, what resisted proof.
Two things are offered for Challenge 8
(tracking issue #56):
48 Kani harnesses, verified from a pristine clone: absence-of-UB proofs plus
sorting correctness asserted as sortedness AND multiset-permutation of the
input, per concrete length, with every specialization body steered via distinct
element types. The dual oracle is directly responsive to the Challenge 8: Verify safety and sorting correctness of SmallSort #576 review
(2026-08-16): the degenerate constant-overwrite "sort" that review described
passes a sortedness-only check and is caught only by the permutation oracle
(demonstrated by mutation, below). One file changed; everything added lives in a
#[cfg(kani)] mod verifyblock — zero production lines change, zero verificationescape hatches (no stubs; the only
kani::assumes are documented preconditions,itemized in the Verification section).
A measured tractability frontier — where, concretely, bounded model checking of
this module stops being feasible, per path, element width, and SAT solver. This is
the quantified form of the wall Add contracts for
SmallSort#234 hit ("out of memory or several tens ofminutes") before being abandoned: the wall is now measured rather than merely hit.
It is offered as information bearing on what a bounded model checker can reach on
this module as currently specified — not as an argument about the challenge.
Correctness oracle. Every correctness harness asserts both properties: the output is
non-decreasing under the comparator, and for every input value its multiplicity is
preserved (multiset equality — a degenerate "sort" that overwrote the slice with a
constant could not pass). Contents are fully symbolic (
kani::any()); no valueconstraints anywhere.
What is covered, and how far. The small-sort API is bounded by design
(
SMALL_SORT_FALLBACK_THRESHOLD= 16,SMALL_SORT_GENERAL_THRESHOLD=SMALL_SORT_NETWORK_THRESHOLD= 32). Verification is one harness per concrete lengthwith symbolic contents. Model-checking cost turned out to be the binding constraint (as
#234 found before us; measurements below), so coverage has three tiers:
Freeze)small_sortimplbodies (insertion-sort paths, threshold 16) are verified at lengths 0..=8 via
Cell<i32>instantiations, andinsertion_sort_shift_leftat lengths 1..=4with
offsetsymbolic over its full accepted range (longer lengths exceed thebudget once the insertion loops' data-dependent path conditions compound —
sharply: len 4 verifies in seconds, len 8 exceeds 25 minutes; measurements
below).
Freezebodies end-to-end:small_sort_general_with_scratch(stable, viai32at every length 0..=9 with thecaller-shaped 48-slot scratch, plus
u128and non-Copy/ oversized element typessteering every specialization branch), and
small_sort_network(viai32at everylength 0..=8 and
u8at 9). This exercises thelen < 2no-ops, copy-1,sort4_stable-pair, insertion, andsort9_optimal-region branches, and thethreshold/
has_efficient_in_place_swap/MAX_STACK_ARRAY_SIZEdispatch in allthree trait impls.
reasonable CI budget (measured: stable general ≥ ~10, network ≥ ~9 at 32-bit width;
no width or SAT-solver choice moves the wall materially), each callee the long
lengths execute is verified directly, at full
i32width, at its exact call-siteshape and under its documented precondition:
sort4_stable(criterion 6),sort8_stableat its only call shape (8),swap_if_lesswith symbolic in-bounds positions (criterion 4), and thesort9_optimalnetwork at its guard length (8-bit elements — 32-bit isintractable there). The gap this leaves is stated plainly: the end-to-end
composition at long lengths is not model-checked, and two branches resisted even
direct verification at any width or solver tried —
sort13_optimal(its 45-swapchain) and
bidirectional_mergeat lengths ≥ 16 (even with sorted halvesassumed). Those two, plus the composed long lengths, are the precise unverified
remainder.
Remaining criteria functions:
swap_if_less(pair ordered + preserved, restuntouched; distinctness of the two positions deliberately not assumed),
sort4_stable(sorted permutation, destination fully initialized),insertion_sort_shift_left(permutation unconditional; sortedness under the documentedsorted-prefix premise, asserted as an implication so the unsorted-prefix case is still
explored for UB),
has_efficient_in_place_swap.Measured tractability (why the frontier is where it is)
On a 20-thread / 31 GiB box with the repo-pinned toolchain, single harness per run:
Cell<i32>insertion paths len 15+; u128 general len 8sort8_stable513 s,sort9_optimal270 s, network u8 len 9 258 s; the rest are seconds to a few minutesThe cost driver is CBMC's symbolic-pointer case-splitting (
swap_if_less/sort4_stableselect pointers viahint::select_unpredictable; the merge loopsadvance pointers by data-dependent amounts), which compounds per step — element width
and solver choice barely move it. This is the quantified form of what #234 reported
("out of memory or several tens of minutes") before being abandoned.
Verification
From a pristine clone at current
main(2bd54c9) plus only this change, with therepo-pinned toolchain (Kani 0.67.0 built at
d4df833c,nightly-2025-11-25),re-verified 2026-09-07 immediately before opening this PR:
Assertion liveness was checked by mutation: negating the sortedness oracle makes the
harnesses FAIL, and a production-side degenerate mutation (merge output overwritten with
copies of one element — exactly the "constant overwrite" scenario from the #576 review)
passes sortedness but fails the permutation oracle, which is the property that
review found missing.
Loops are covered with generous
#[kani::unwind]bounds (len + 2); unwindingassertions stay enabled, so a too-small bound fails the proof rather than masking
exploration. The only
kani::assumes are documented preconditions (index bounds,offsetrange). No stubs. Severalharnesses pin
#[kani::solver(kissat)]following the existing in-tree usage(
num/mod.rs,ptr/mod.rs).Scope and known limits
measured frontiers, end-to-end model checking exceeds any per-proof CI budget by an
order of magnitude (table above). Coverage there is per-callee, plus branch coverage
of the dispatch logic. If the committee prefers, the frontier harnesses can be
extended on bigger iron — the harness shapes accept any length by macro invocation.
a < b). UB-freedom underan adversarial comparator (arbitrary results / panics) is not modeled.
#[ensures]attributes on thetrait impls: the permutation property needs the pre-state of a generic
&mut [T]behind specialized trait methods with closure parameters, which function-contract
syntax cannot currently express there.
i32,Cell<i32>,u128, a non-Copywrapper,
[u64; 11],u8), chosen to steer every specialization/dispatch branch.Relationship to #576: that PR covers the same challenge; its review (2026-08-16) found
two blockers — correctness asserted sortedness only (no permutation check), and every
harness pinned
[i32; 4]. This PR is an independent implementation built to thatreview's direction: sortedness ∧ permutation everywhere, and per-length sweeps plus
callee proofs in place of a single pinned shape, with the genuine cost wall measured and
disclosed rather than papered over. Also noting #234 (closed 2025 after hitting exactly
this wall) and #640 (withdrawn unreviewed, 2026-08-26).
AI disclosure: these harnesses were developed with substantial AI assistance (Claude,
Anthropic), with human direction and review; the commit carries a
Co-authored-bytrailer accordingly. All proofs were re-verified from a pristine checkout before
submission.
Toward #56.