Challenge 2 (partial): safety contracts + verification for 15 of 20 raw-pointer core::intrinsics - #618
Challenge 2 (partial): safety contracts + verification for 15 of 20 raw-pointer core::intrinsics#618ivmat wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds partial Kani verification for Challenge 2, targeting 15 raw-pointer intrinsics while preserving five unsupported harnesses.
Changes:
- Adds safety-contract wrappers and Kani proofs.
- Adds independent-oracle and non-vacuity checks.
- Documents unsupported volatile intrinsic residuals.
Suppressed comments (3)
library/core/src/intrinsics/mod.rs:3576
- This postcondition has the same indexing defect in
check_copy_untyped: the selected destination element is compared withsrc[0], not the correspondingsrc[elem](lines 2963-2966). Mixed initialized/uninitialized source elements can make a correctcopyfail the contract, so the helper must offset both pointers byelem.
#[ensures(|_| check_copy_untyped(src, dst, count))]
library/core/src/intrinsics/mod.rs:4556
- This excludes the documented MMIO use case:
write_volatilepermits aligned, non-trapping writes outside Rust allocations, butcan_writeand the ordinary-dereference postcondition only describe Rust-backed memory. Add a model for external volatile memory or list this as an unverified residual instead of treating this as the completevolatile_storesafety contract.
#[requires(ub_checks::can_write(dst))]
#[ensures(|_| unsafe { *dst } == val)]
library/core/src/intrinsics/mod.rs:4594
- This contract is false for valid vtables whose erased type is not aligned like
u32; adyn Debugvtable for[u8; 8], for example, meets the readable-memory precondition but reports alignment 1 rather than 4. Readability also does not prove that the pointer is a vtable. Preserve the erased type's expected alignment in the wrapper/fixture and encode genuine vtable validity, or do not count this monomorphic probe as the intrinsic contract.
#[requires(ub_checks::can_dereference(ptr as *const [usize; 3]))]
#[ensures(|result| *result == core::mem::align_of::<u32>())]
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| unsafe fn typed_swap_fallback_wrapper<T>(x: *mut T, y: *mut T) { | ||
| unsafe { crate::ptr::swap_nonoverlapping(x, y, 1) } |
| && ub_checks::can_dereference(core::ptr::slice_from_raw_parts(src as *const crate::mem::MaybeUninit<T>, count)) | ||
| && ub_checks::can_write(core::ptr::slice_from_raw_parts_mut(dst, count)) | ||
| && ub_checks::maybe_is_nonoverlapping(src as *const (), dst as *const (), size_of::<T>(), count))] | ||
| #[ensures(|_| check_copy_untyped(src, dst, count))] |
| #[requires(offset >= 0 && offset <= 8)] | ||
| #[ensures(|result| *result as usize == (dst as usize).wrapping_add(offset as usize))] |
| #[requires(bytes <= COMPARE_BYTES_CAP | ||
| && ub_checks::can_dereference(crate::ptr::slice_from_raw_parts(left, bytes)) | ||
| && ub_checks::can_dereference(crate::ptr::slice_from_raw_parts(right, bytes)))] |
| #[requires(ub_checks::can_dereference(ptr))] | ||
| #[ensures(|result| *result == core::mem::size_of::<T>())] | ||
| #[allow(dead_code)] | ||
| unsafe fn size_of_val_wrapper<T>(ptr: *const T) -> usize { |
| #[requires(ub_checks::can_dereference(src))] | ||
| #[ensures(|result| *result == unsafe { *src })] |
| #[requires(ub_checks::can_dereference(ptr as *const [usize; 3]))] | ||
| #[ensures(|result| *result == core::mem::size_of::<u32>())] |
feliperodri
left a comment
There was a problem hiding this comment.
Kani-verification review — PR #618 (Challenge 2, partial: 15/20 raw-pointer intrinsics)
Bottom line
The engineering is careful and unusually honest, the wrapper-around-#[rustc_intrinsic] pattern is the right workaround for kani#3325/rust-lang#3345 (already blessed in-tree via transmute_unchecked_wrapper), and the #[cfg(not(kani))] gates are legitimate — not the fatal body-swap pattern. I'm requesting changes only on contract faithfulness / over-constraint grounds against success criterion 5, which several contracts do not meet as written. Nothing here makes an unsafe operation look safe (all deviations are conservative), so this is a fixable faithfulness bar, not a vacuity/soundness collapse.
What is sound (no action needed)
- All 5
#[cfg(not(kani))]gates are legitimate. They sit on disabled#[kani::proof]harnesses for intrinsics Kani reports as unsupported —check_volatile_set_memory_no_ub(618.diff L833),check_volatile_copy_nonoverlapping_memory_no_ub(L851),check_volatile_copy_memory_no_ub(L873),check_unaligned_volatile_load_no_ub(L929),check_unaligned_volatile_store_no_ub(L947). None compiles out a verified std function body behind an assume-the-conclusion stub. This is the same idiom as the pre-existing removedwrite_bytesgate (L199). No fatal vacuity. - Contract-liveness is complete: 20
#[kani::proof_for_contract]targets, each paired to a contracted wrapper; the 42requires/ 31ensuresare multi-clause contracts on those 20 functions, so the raw "53 vs 20" is consistent, not decorative. typed_swap_fallback_wrapper(L77),copy_wrapper/copy_nonoverlapping_wrapper/write_bytes_wrapper(L112–134) use the correctcan_dereference/can_write/maybe_is_nonoverlapping/alignment preconditions — exactly the right contract shape for raw-pointer memory intrinsics.- Bounded fixtures (
[u8;8],[u32;4],COMPARE_BYTES_CAP=4) are acceptable; the challenge does not mandate unbounded, and the addedkani::covernon-vacuity witnesses are a nice touch.
Blocking: contracts that don't faithfully capture the documented safety condition (criterion 5)
-
vtable_size_wrapper/vtable_align_wrapper(L550–561).#[ensures(*result == size_of::<u32>())]/align_of::<u32>()is hard-coded to the fixture type; it is false for any non-u32vtable (e.g.dyn Debugoveru64/[u8;8]) that equally satisfiescan_dereference(ptr as *const [usize;3]). The precondition also doesn't establish "ptris actually a vtable." As written this is a monomorphic probe, not the intrinsic's contract. The author's own scoping note concedes this. Either encode the erased type's expected layout generically or don't count these two as verified for the challenge table. -
size_of_val_wrapper(L486).#[requires(can_dereference(ptr))]is stronger than documented:mem::size_of_val_rawis safe for anyT: Sizedincluding null/dangling data pointers, whichcan_dereferencerejects. So "meeting the documented condition is enough" (criterion 5) is not demonstrated — a stronger condition is. OnlyT = u32(Sized) is covered; the?Sizedmetadata cases are absent. -
compare_bytes_wrapper(L417).bytes <= COMPARE_BYTES_CAPis a tractability bound placed in#[requires], so the contract rejects valid calls over larger readable regions. Keep the cap as a harnessassumeonly; state the contract purely as "both regions readable forbytes." -
volatile_load_wrapper/volatile_store_wrapper(L503/L519).can_dereference/can_write+ an ordinary-deref postcondition cover only Rust-backed allocations and exclude the documented MMIO case (read/write_volatilepermit aligned non-trapping access outside any Rust allocation). Over-constrains valid callers; list the external-memory case as an unverified residual rather than presenting this as the full contract. -
arith_offset_wrapper(L258).#[requires(offset >= 0 && offset <= 8)]on the contract-form wrapper is not a documented precondition (arith_offsethas none). The author's mitigation is real and appreciated —check_arith_offset_unconditional_safety(L280) proves safety unbounded — so the safety property is genuinely covered. But the bounded wrapper should be presented as a behavioral/pointer-model probe, not "the intrinsic contract."
Non-blocking but worth addressing
check_copy_untypedoracle asymmetry (pre-existing helper,mod.rs:2954, now depended on bycopy_wrapper/copy_nonoverlapping_wrapperensures at diff L109/L119). It offsetsdstbyelembut leavessrcat element 0 (src_data.add(byte)vsdst.add(elem)...add(byte)), so it comparesdst[elem]'s init state againstsrc[0]'s. For sources with per-element init differences this oracle is checking the wrong pair. It's not introduced by this PR, but since the PR newly relies on it for thecopy/copy_nonoverlappingpostconditions, it should be fixed to offsetsrcbyelemtoo (or confirmed harmless for these fixtures).ptr_offset_from_wrapper/ptr_offset_from_unsigned_wrapper(L301/L338). The author honestly discloses that dropping the#[requires]"all the way totruestill verifies SUCCESSFUL" because the fixture only ever derives both pointers from one[u8;8]array. The contract text is doc-faithful, but the harness does not exercise the precondition (no cross-allocation / reversed-order pointers), so the proof is near-vacuous w.r.t. that precondition. Strengthen the fixture or note it as a known ablation gap in the PR body.typed_swap_fallback_wrapper: verifying a verbatim copy of the fallback body (not the shared implementation) satisfies criterion 2's letter but not Kani-entry intotyped_swap_nonoverlapping; the author documents this scope limit clearly. Consider extracting a shared helper.
Partial submission
Partial (15/20) is acceptable for this open challenge, and the 5 uncovered intrinsics (the volatile/unaligned-volatile family) are genuinely Kani-unsupported and honestly documented. The blocker is not the missing 5 — it's that several of the claimed 15 have contracts that over-constrain or hard-code fixture specifics and so don't yet satisfy criterion 5 ("meeting the documented conditions is enough to guarantee safe usage"). Tighten items 1–5 (or relabel the affected ones as bounded probes / residuals) and this becomes approvable.
…of 20 raw-pointer core::intrinsics Add doc-derived safety contracts and Kani proof harnesses for 15 of the 20 raw-pointer intrinsics in Challenge 2, each verified via #[kani::proof_for_contract]: typed_swap, vtable_size, vtable_align, copy, copy_nonoverlapping, write_bytes, size_of_val, arith_offset, volatile_load, volatile_store, ptr_offset_from, ptr_offset_from_unsigned, compare_bytes, read_via_copy, and write_via_move. Kani cannot attach a contract to a bodyless #[rustc_intrinsic] (kani#3325, kani#3345), so each contract sits on a thin wrapper that calls the intrinsic. This is the pattern already used in-tree for transmute_unchecked_wrapper. For vtable_size/vtable_align the wrapper takes *const T and performs the unsize coercion inside the wrapper, so the pointer handed to the intrinsic is a vtable for T by construction. The postcondition is the generic size_of::<T>() / align_of::<T>(), verified at 7 erased types with mutually independent size and align. Every proof checks the result against an independent oracle, never by re-calling the intrinsic under test. Every added harness carries satisfied kani::cover witnesses for non-vacuity. Tractability bounds live in the harnesses as assumes, never in the contracts, so each #[requires] states only the documented safety condition. arith_offset has no documented precondition: its unbounded safety is proven by a separate plain proof, and its bounded wrapper is a behavioral probe. The raw *const () vtable wrappers are kept as labelled probes and are not counted. The 5 volatile-family intrinsics are not counted. Kani reports them unsupported at the pinned commit (d4df833), so their harnesses are kept under #[cfg(not(kani))] with the exact attempted proof preserved. Whole-module run at the pinned toolchain (kani d4df833, CBMC 6.8.0): 0 failures across the challenge-2 verification module. Reproduce: kani verify-std -Z unstable-options ./library \ -Z function-contracts -Z mem-predicates -Z float-lib -Z c-ffi \ -Z loop-contracts -Z quantifiers -Z stubbing \ --no-assert-contracts --harness intrinsics::verify:: \ --cbmc-args --object-bits 12
2b9b99c to
9dc6083
Compare
Factor the intrinsic's fallback body into a private typed_swap_nonoverlapping_fallback helper, marked rustc_const_stable_indirect so it stays callable from the const-stable-indirect intrinsic. The intrinsic and the Kani verification wrapper in mod verify now both call this same helper instead of the wrapper carrying a separate copy of the body, so the proof covers the production fallback path and the two cannot drift apart. Updates the wrapper's comment to match.
|
Implemented the changes and scope relabelling at 9dc6083, and the shared-helper extraction you 1. vtable_size / vtable_align hard-coded to u32. Added verify-rust-std/library/core/src/intrinsics/mod.rs Lines 4636 to 4834 in e7b1bc1 The harnesses include your u64 counterexample and six further size/alignment cases, among them[u8; 8] (size 8, align 1), a ZST, and an over-aligned type. The raw *const () wrappers remain asu32/u64 probes, labelled as such and excluded from the verified count. One residual statedin-code: rustc's layout_of supplies both sides of the comparison.
2. size_of_val_wrapper precondition stronger than documented. verify-rust-std/library/core/src/intrinsics/mod.rs Lines 4393 to 4537 in e7b1bc1 Residuals: composite unsized tails are not instantiated, and extern type is excluded becauseKani's model panics on it. 3. compare_bytes_wrapper tractability cap in #[requires]. Done as asked. verify-rust-std/library/core/src/intrinsics/mod.rs Lines 4351 to 4391 in e7b1bc1 4. volatile_load / volatile_store exclude the MMIO case. No code change. Taking your second verify-rust-std/library/core/src/intrinsics/mod.rs Lines 4539 to 4571 in e7b1bc1 5. arith_offset_wrapper's undocumented 0..=8 bound. No contract change. Taking your framing: the verify-rust-std/library/core/src/intrinsics/mod.rs Lines 4228 to 4257 in e7b1bc1 Non-blocking: check_copy_untyped oracle asymmetry. Fixed at the helper — verify-rust-std/library/core/src/intrinsics/mod.rs Lines 2963 to 2981 in e7b1bc1 verify-rust-std/library/core/src/intrinsics/mod.rs Lines 3580 to 3610 in e7b1bc1 ptr_offset_from precondition never exercised. Strengthened the fixture rather than noting the verify-rust-std/library/core/src/intrinsics/mod.rs Lines 4259 to 4315 in e7b1bc1 typed_swap_fallback_wrapper verifies a copied body. Took your suggestion and extracted the verify-rust-std/library/core/src/intrinsics/mod.rs Lines 2558 to 2567 in e7b1bc1 verify-rust-std/library/core/src/intrinsics/mod.rs Lines 3511 to 3528 in e7b1bc1 The PR body is updated to match the current scope. Re-requesting review. |
|
if you are interested, you can check acceptance file for this PR based on acceptance format i've been developing. |
|
Not yet complete, found some issuea |
Challenge 2 (partial): safety contracts + verification for 15 of 20 raw-pointer
core::intrinsicsCloses a substantial part of Challenge 2, and states plainly what it does not close. Per criterion 4, the assumptions are listed rather than implied.
Criterion 1 requires all 20; this PR delivers 15 and documents precisely why the rest are not counted. The challenge sets no partial threshold — whether to land partial progress is the maintainers' call.
Headline: 15 of the 20 mandated intrinsics are verified — 14 in the mandated contract form (including
vtable_size/vtable_align, upgraded from monomorphic probes to coercion-based contracts (no#[requires]) per review), plusarith_offset(which has no documented safety precondition) via an unbounded no-UB proof (see ¹). The remaining 5 are not verifiable by official Kani at the pinned commit (d4df833) — Kani emits an explicit "not currently supported" diagnostic for each. That is a tool gap, not a proof failure: the upstream fix is merged (kani#4672, kani#4673), and all five verify under a build carrying those merges (see Residuals).What is verified
15 of the 20 mandated intrinsics — 14 with a doc-derived safety contract verified via
#[kani::proof_for_contract], plusarith_offset¹ (no documented precondition) closed by an unbounded no-UB proof:typed_swap(in-tree today astyped_swap_nonoverlapping) ·vtable_size² ·vtable_align² ·copy·copy_nonoverlapping·write_bytes·size_of_val³ ·arith_offset¹ ·volatile_load·volatile_store·ptr_offset_from·ptr_offset_from_unsigned·compare_bytes·read_via_copy·write_via_move¹
arith_offsethas no documented preconditions; its safety claim ischeck_arith_offset_unconditional_safety— a plain#[kani::proof]over a fully-symbolic (unbounded) offset, with the base pointer a single representative 8-byte allocation. It is not a#[kani::proof_for_contract]. The bounded contract-form wrapper alongside it is a behavioral/pointer-model probe, not the intrinsic's contract, and is not counted.²
vtable_size/vtable_alignare counted via wrappers that take*const Tand perform the unsize coercion inside the wrapper, so the pointer handed to the intrinsic is a vtable forTby construction — "ptr is a vtable" is established, not assumed — with the generic postconditionsize_of::<T>()/align_of::<T>(). Verified at 7 erased types with independent size/align (incl.[u8;8], a ZST, mixed-align and over-aligned structs). The earlier raw-*const ()monomorphic wrappers are kept in-tree, labelled probes, uncounted. Residuals: the monomorphization set is finite (as for every generic contract here), and the coercion fixes one trait (Debug); both stated in Assumptions.³
size_of_valis verified on three paths:T: Sized(requires nothing — matching the documentation — with null/dangling/nondet-address witnesses), trait objects (dyn-metadata wrapper, 4 erased types), and slices with symbolic length, whose single#[requires]is the documentedsize * len ≤ isize::MAXcondition (its ablation observed failing inside Kani's own size model). Theextern typemetadata case does not exist on stable and is a named residual.Whole-module run on the currently pinned toolchain:
Toolchain identity captured at run time rather than inferred, because the version string
0.67.0isidentical for the 0.67.0 release and the pinned dev commit:
d4df833c8f8f18e632e7b0a7945bb2161f708990(the committool_config/kani-version.tomlpins)kani-dependenciesdeclares--no-assert-contracts,--object-bits 12Non-vacuity is machine-checked, not asserted: every harness authored for this work carries
kani::coverwitnesses, all satisfied on the run above. None is unsatisfied — which matters, because Kani reports a harness whose cover is unsatisfiable asVERIFICATION: SUCCESSFUL, so an uninspected cover count can hide a vacuous proof. These covers witness that a scenario is reachable; they are not all adversarial, and which#[requires]clauses are actually load-bearing is tracked separately under Assumptions → precondition exercise status.Preconditions are exercised, not just stated, where the fixture permits it: the same-allocation
requiresonptr_offset_from(_unsigned)were ablation-tested — weakening them totruemakes the harnesses FAIL (two-allocation nondet fixtures) — and the slice-sizerequiresonsize_of_valfails the same ablation test inside Kani's size model. Load-bearing clauses, demonstrated.Every counted intrinsic except
arith_offsetcarries doc-derived#[requires]/#[ensures], verified with#[kani::proof_for_contract];arith_offset(no documented precondition) is closed by an unbounded no-UB proof instead (see ¹).Because kani#3325 blocks contracts directly on bodyless
#[rustc_intrinsic]declarations, contracts sit on a thinunsafe fn <name>_wrapper— the same pattern already used in-tree fortransmute_unchecked_wrapper(PR #185), and the workaround recorded in kani#3345. For the vtable pair the wrapper additionally constructs the vtable by unsize coercion rather than assuming its validity — the same by-construction technique already in-tree in theNonNull::from_raw_partsharnesses (PR #127), which build a genuinedyn-trait vtable via coercion and read its metadata.Each proof uses an independent reference check: the property is checked against a separately-computed expected value (for the vtable pair: the compiler's own
size_of/align_of), never by re-calling the intrinsic under test. Honest bound, stated so a reviewer meets it head-on: for the layout intrinsics that expected value and the intrinsic's result both ultimately descend from rustc'slayout_of, so the check is relative to the layout model — as is every size/align contract in this repo (e.g.Layout::new'ssize() == size_of::<T>(), PR #43). What it still falsifies end to end: the coercion wiring the right vtable, the model reading the right slot (the fixture set is chosen so size and align are mutually discriminating), and rustc's constant agreeing with CBMC's__CPROVER_OBJECT_SIZE— two independent sources of truth. See Criterion 3 below for the slot-level detail.Criterion-by-criterion
arith_offset, no documented precondition) via an unbounded no-UB proof — see ¹. The 5 residuals are not annotated, because pinned Kani cannot codegen them (support merged upstream, unreleased).min_align_of_valis a non-intrinsiccore::memwrapper, so criterion 2 does not range over it; it resolves to the bodylessalign_of_valintrinsic. Of the 20 intrinsics, 19 are declared with no body — nothing for the criterion to range over — and exactly one,typed_swap_nonoverlapping, carries a fallback body (unsafe { ptr::swap_nonoverlapping(x, y, 1) }), verified against the intrinsic's own contract via a wrapper that forces symbolic execution of the body rather than Kani's built-in model. The fallback body is factored into a shared private helper (typed_swap_nonoverlapping_fallback) that both the intrinsic and the verification wrapper call, so the proof covers the production fallback path itself and the two cannot drift apart. This shows the fallback satisfies the contract, not that Kani's model and the fallback are equivalent — a claim criterion 2 does not make.requires= the documented conditions, no more — tractability caps live in harnesses, not contracts;size_of_valrequires nothing forT: Sizedand only the documented size bound for slices). Two carve-outs, stated plainly:volatile_load/volatile_storeusecan_dereference/can_write, which are proven for Rust-allocation-backed memory only — the documented MMIO case (aligned, non-trapping access outside any Rust allocation) is a named unverified residual (see Assumptions); andarith_offset's documented condition set is empty, so its sufficiency is shown by the unbounded no-UB proof, not a contract (see ¹). Open for the 5 tool-blocked residuals.Criterion 3 — how the
vtable_size/vtable_alignmodel is matchedFor the value/pointer intrinsics the contract is the correspondence. The vtable pair is the one place the model deserves spelling out, because the intrinsic reads a compiler-emitted structure:
let dyn_ptr: *const dyn Debug = ptr;makes rustc emit the unique vtable for(T, dyn Debug)— under Kani's codegen the globalvtable_impl_for_T(codegen_cast_to_fat_pointer→codegen_vtable). No fixture choice can substitute a vtable for a different erased type: the metadata is a function ofTalone, sosize_of::<T>()is the correct oracle for everyT.vtable_infocasts a per-trait vtable struct to the type-erasedKani::CommonVTableand reads the size/align slots by field name — a real layout-agreement obligation between two distinct struct types, not a no-op.check_vtable_sizeasserts the rustc constant equals CBMC's__CPROVER_OBJECT_SIZE— two independently derived sizes agreeing.layout_ofdisagreeing with the vtable LLVM finally emits. That limit is relative to the model and is shared by every contract in this challenge.The counted form is the coerced
*const Twrapper: validity is established by construction for the(T, dyn Debug)pair, not asserted over arbitrary*const ()inputs. So this is a generic, by-construction contract on the vtable path — not a reusable specification over the intrinsic's raw erased-pointer domain, nor over all traits/DSTs (those bounds are named under Assumptions). The raw*const ()probes are kept in-tree, labelled, and excluded from the count; if a reviewer holds that a*const Twrapper is "a different function's contract," the probes stand as the monomorphic evidence they are, and the count is unaffected either way.These two intrinsics are, additionally, already reached by merged maintainer-authored work: PR #43 (
Layout::for_value/for_value_raw, tautschnig) verifies thedyn Debugcase, which exercisesvtable_size/vtable_alignthrough Kani's model — to date the only merged verification that touches the vtable pair at all. That is the public-caller precedent that these intrinsics are legitimately verified via a real caller's contract; the coerced wrapper here is the complementary form that additionally pins the returned size/align tosize_of::<T>()/align_of::<T>(), which the layout-onlyLayout::for_valuepostcondition does not state.Residuals — what is NOT covered, at the headline
5 intrinsics official Kani cannot verify at the pinned commit:
volatile_copy_memory,volatile_copy_nonoverlapping_memory,volatile_set_memory,unaligned_volatile_load,unaligned_volatile_store.These are not proof failures. Running each harness produces Kani's own diagnostic, e.g.:
The harnesses are written and kept in-tree under
#[cfg(not(kani))], so the exact attempted proof is preserved and can be un-gated the moment support lands.The upstream fix is merged — kani#4672 (
volatile_copy_memory,volatile_copy_nonoverlapping_memory,volatile_set_memory) and kani#4673 (unaligned_volatile_load,unaligned_volatile_store). What the investigation behind those PRs found, all checkable by reading Kani's tree: the placeholders were never compiled (unstable_codegen!never expands its tokens), so the gated bodies had bit-rotted; the sketched implementation had(dst, src)reversed relative to Kani'scodegen_copy; and adding the missingvolatile_set_memoryvariant ICEs the points-to analysis unless handled.Complete-picture check, run on a build of the pinned commit with both merges applied: contracts + harnesses for all five (same faithfulness treatment as the 15: MMIO residual named, no undocumented preconditions, ablation controls), and the full 20-intrinsic target set verifies whole-module with zero failures. This PR does not claim those five: the count stays 15/20 until Kani releases the merged support and this repo's
kani-version.tomlmoves — at which point the additional commits are ready to land. Reported so the residuals are visibly tool-gated rather than quietly failing proofs.One caveat preserved from that work, for whoever un-gates these later:
check_volatile_copy_memory_no_ubas originally written combined a symbolicshiftwithfor i in 0..(N - shift), putting a symbolic trip count in the formula (40 minutes, no convergence). Rewritten to the fixed-representative-SHIFTpatterncheck_copy_overlapping_shift_no_ubalready uses, it verifies in 0.13s. That is a real weakening, identical to the onecopy's own overlap harness carries, and it is flagged here rather than buried.Why these five are gated rather than modeled behind a stand-in. A tempting shortcut is to give each unsupported volatile intrinsic a wrapper whose body calls an ordinary supported operation —
copy,copy_nonoverlapping,write_bytes,read_unaligned/write_unaligned— and verify that. This PR deliberately does not do that. A#[kani::proof_for_contract]over a body that never calls the real intrinsic verifies the stand-in, not the intrinsic's own codegen; the volatile semantics (the flag that stops the optimizer reordering the access) are exactly what such a model drops. And the upstream investigation behind kani#4672/rust-lang#4673 found the sketchedvolatile_copy_memoryimplementation had(dst, src)reversed — precisely the class of defect a stand-in hides. So the five stay under#[cfg(not(kani))]with the real attempted proof preserved, and the honest count is 15/20 until the tool supports them — at which point the additional commits land as a genuine 20/20, each intrinsic verified against its own codegen rather than a substitute.Assumptions and bounds (criterion 4)
These are the honest limits of what the proofs establish.
N = 4) with symbolic contents. Tractability caps (e.g.compare_bytes' byte cap) live in the harness as assumes — the contracts state only documented conditions.Debug); the vtable layout Kani models is trait-independent. The arbitrary-pointer harnesses additionally excludeDangling/DeadObjectallocation states, which Kani's memory predicates cannot currently model.copyoverlap harness uses a fixed representativeSHIFTwith a symbolic index. This applies tocopy, one of the 15 claimed above.-Z stubbingflag in the reproduction command is required only because the module also contains pre-existing upstream transmute harnesses using#[kani::stub_verified]. No challenge-2 harness useskani::stuborstub_verified.--no-assert-contracts. Dependency contracts are assumed, not asserted. The contracts underproof_for_contractare themselves fully checked.--object-bits 12. Fails loudly if exceeded — a sound bound, not a silent cap.ptr_offset_from(_unsigned)'ssame_allocationclause andsize_of_val(slice)'s size-boundrequiresare ablation-tested load-bearing (observed FAILING when weakened). Because bothptr_offset_fromwrappers are instantiated atT = u8, their byte-distance-divisibility clause reduces to% 1 == 0and is not exercised (a named gap; au32instantiation with misaligned pointer pairs would exercise it).copy_nonoverlapping's non-overlap requires remains doc-correct but not falsifiable by its single-array fixture. For overlappingcopy, the initialization oraclecheck_copy_untypedcompares post-state source against post-state destination; akani::coverincheck_copyrecords whether the non-trivial mixed-initialization-overlap domain is reachable, and the post-state (rather than pre-state) comparison is a named limit on that domain.write_bytesvalue check. Thewrite_bytes_wrappercontract constrains only UB and initialization, not the written value. A separate plain proofcheck_write_bytes_sets_valuereads each written byte back and requires it to equalval, so the written value is independently checked.volatile_load/volatile_storecover Rust-allocation-backed memory only. The documented MMIO case (aligned, non-trapping access outside any Rust allocation) is an unverified residual;can_dereference/can_writedo not model it.kani::cover. The pre-existingtyped_swapharnesses predate this discipline and carry none.swap,copy_from_sliceand thealign_of_valsite each delegate to the named intrinsic;zeroeddelegates towrite_bytes, which is verified here. The fifth,parse_u64_into, is the naming finding below.Existing code this PR replaces (called out deliberately)
The diff touches one file,
library/core/src/intrinsics/mod.rs. Pieces removed or corrected rather than left alongside:check_copyandcheck_copy_nonoverlappingsketches — superseded by workingproof_for_contractharnesses on the wrapper pattern that resolves their own noted blocker.#[cfg(not(kani))]-disabledwrite_bytesharness and its kani#90FIXME. kani#90 remains open upstream; the specific configuration this FIXME guarded verifies here (all 296 checks pass), with akani::coverwitnessing the exact kani#90 trigger as reachable. If maintainers would rather keep theFIXMEuntil kani#90 is formally closed, say so and I'll restore it.check_copy_untypedhelper's element pairing (pre-existing) compareddst[elem]againstsrc[0]; per review it now pairssrc[elem]withdst[elem]. Both dependent harnesses stayed green after the fix, and a planted mutation in the corrected comparison was observed failing.typed_swap_nonoverlapping's fallback body is factored into a shared private helper,typed_swap_nonoverlapping_fallback(the same one-lineptr::swap_nonoverlapping(x, y, 1)call, semantically unchanged, markedrustc_const_stable_indirect), per review — so the verification wrapper executes the same code the intrinsic falls back to, rather than a copy of it.No other upstream code is modified, and nothing outside this file is touched.
One finding for the maintainers
The challenge's mandated table lists
parse_u64_into, whichgit grep parse_u64_intoacrosslibrary/at this branch's HEAD does not find (this subtree mirrors rust-lang/rust, so a rename would have happened there). It may be renamed or removed upstream. Flagging it rather than silently dropping it — happy to open a separate issue if useful.Reproducing
All harnesses live in
library/core/src/intrinsics/mod.rsundermod verify. To reproduce the complete-picture check: build Kani atd4df833with the merged kani#4672/rust-lang#4673 commits applied, and run the same command against this branch plus the five un-gated volatile harness commits (available on request).