Conversation
|
You should use macros to reduce code duplication. It'll also make review easier. |
… harnesses with rust macro
- fix verify_4533 slice harness generation - rename duplicate UniqueRcUninit drop macro - add unstable(kani) annotations to verify modules - keep production from_iter_exact loop under non-Kani builds - make nondet Vec helper initialize elements soundly
058866b to
436a1b4
Compare
|
Thanks for the thoughtful review. We have addressed all 5 comments and pushed 3 follow-up commits with the requested changes. The PR should now be ready for another round of CI and review. Could you please re-run the CI checks when possible? |
|
Update on the CI resource issue: The recent changes add a macOS-only bound to the nondeterministic slice/vector length used by the shared The bound is guarded by The intent is to keep the macOS CI jobs within their time/memory budget, not to change normal std behavior or the Linux verification setup. |
feliperodri
left a comment
There was a problem hiding this comment.
Approving — leading solution for Challenge 26
Thanks @v3risec. After reviewing both open Challenge 26 (Rc/Weak) solutions with our vacuity tooling and local Kani (pinned 0.67.0 / CBMC 6.8.0), this is complete and sound. Prioritizing it.
Coverage against Ch26 criteria:
- A: 12/12 unsafe pub fns with real
#[requires]/#[ensures]/kani::modifiescontracts backed by matching#[kani::proof_for_contract]harnesses, monomorphized across primitive T (i8..i128, u8..u128, bool, unit, [u8;4]) and unsized[T]as challenge-allowed. - B: ~54/54 safe abstractions with unsafe (~100%, ≥ 75%). Complete coverage across new(_uninit/_zeroed/in), try*, pin/pin_in, into_array, get_mut/make_mut (with 3-state coverage), downcast (Ok+Err), from_box_in, RcFromSlice / ToRcSlice, Drop/Clone/Default, all From/TryFrom variants, Weak::{as_ptr,into_raw_with_allocator,upgrade,inner} (multi-path), inc_strong/inc_weak (+ should_panic overflow), UniqueRc + UniqueRcUninit.
Soundness (all clean):
- T1: no cfg body swaps — grep for
cfg(not(kani))in the diff returns 0 hits; harnesses run the real bodies. - T2: no trivial invariants, no
loop_invariant(true). - T7: all 12 unsafe fns have explicit
proof_for_contract;.github/workflows/kani.ymlautoharness allowlist unchanged — no silent autoharness dependency. - Not assume-the-conclusion — the only
kani::assume(can_dereference(...))isVec::set_len's own precondition inside a helper, not the fn under proof. - Inputs symbolic (
kani::any::<T>()); slice length bounded ≤100 (documented CI-tractability budget); iterator loopsunwind(6)for 4-element input (documented Kani loop-contract limitation). - No runtime std logic changed.
Local Kani sample (CBMC 6.8.0): 4/4 VERIFICATION SUCCESSFUL — harness_rc_assume_init_i8 (proof_for_contract), harness_rc_downcast_unchecked_i8 (proof_for_contract), harness_inc_strong_overflow_should_panic, harness_rc_default_str.
Minor weaknesses to note for the record (non-blocking):
verifier_nondet_vecusesptr::write_bytes(..., kani::any::<u8>(), size_of::<T>() * sz)— one symbolic byte pattern replicated across all elements, so a run overRc<[u32]>only covers[0x00000000; n],[0x01010101; n], … Symbolic but coverage-limited for multi-byte T.Rc::from_raw,Rc::increment_strong_count, andWeak::from_raw(non-_invariants) read(*ptr).get()insideunsafewithout an explicitkani::mem::can_dereferencein the requires, unlike the_invariants which do. Harmless for the roundtrip harnesses (pointers are always valid), but the exported contract is weaker than the_invariant for arbitrary callers — consider addingcan_dereferenceto match.Rc::get_mut_uncheckedcontract only requirescan_write(value); the documented safety property (no otherRc/Weakmay reference the inner value) is not encoded — the harness proves UB-freedom of the body but under-specifies the caller obligation.- Roundtrip-narrowed inputs (from_raw/increment/decrement use ptr obtained via into_raw) narrow the input universe vs a fully symbolic pointer meeting the precondition.
These are contract-strength refinements, not soundness bugs; they can be addressed in a followup. Challenge 26 explicitly permits bounded + primitive-mono.
|
@lucasccordeiro @rajath-mk @patricklam @HuStmpHrrr could you review this propose solution for challenge 26? |
|
checking now |
|
@feliperodri @HuStmpHrrr Thanks for the detailed review. I went through the comments and updated the contracts and harnesses accordingly. The main changes are:
I also noticed that the Kani CI jobs are currently failing while setting up Kani/CBMC, before reaching the verification itself. The failure comes from Homebrew rejecting the Is this a known CI/infrastructure issue at the moment, or is there anything I should change on my side? I'm happy to make any further changes or refinements if needed. Thanks again for the review! |
Criteria 1 and 2 are met. Local Kani (pinned Criterion 1 — all 12 listed
|
| Evidence | Result |
|---|---|
Contract attributes in library/alloc/src/rc.rs |
12 #[requires]-annotated fns at lines 1291, 1436, 1515, 1576, 1622, 1777, 1890, 1945, 2065, 2293, 3399, 3581 — a 1:1 match with the table (checked by reading each signature, not by name) |
kani list (local) |
Reports exactly those 12 as the contracted functions in alloc::rc, backed by 207 #[kani::proof_for_contract] harnesses |
| Local verification, one harness per required fn | 12/12 SUCCESSFUL, and each reports 1 of 1 cover properties satisfied → not vacuous |
Liveness mutation: increment_strong_count's ensures checked_add(1)→(2) |
Harness FAILED → the postcondition is genuinely evaluated |
Criterion 2 — ≥75% of the 54 safe fns ✅ (well clear of threshold)
| Evidence | Result |
|---|---|
| Per-function grep across the harness block | All 54 are actually called; 1127 plain #[kani::proof] rc harnesses |
| Local run of 105 harnesses = one per harness family, covering every one of the 12 + 54 functions | 105/105 SUCCESSFUL |
| Effective count after discounting limitations (below) | 46/54 = 85%, still ≥ 75% |
Two limitations discount 8 functions: RcFromSlice<T: Clone>::from_slice and ToRcSlice::to_rc_slice are bounded to 4 elements via #[kani::unwind(6)] (disclosed in the PR); the six try_new* functions verify only the success path — their Err cover comes back UNREACHABLE because Kani cannot fail an allocation.
Criterion 3 — general rules ✅ / List of UBs ⚠️
General rules ✅
| Evidence | Result |
|---|---|
Diff vs merge-base 82ab358 |
rc.rs is +5518 / −0 (pure addition); alloc/src/lib.rs +1 cfg_attr(kani, …) gate; Cargo.lock trailing newline. No std runtime logic touched |
| Vacuity scan | Zero #[cfg(not(kani))], zero kani::stub → no body-swap or stub vacuity. Only 7 kani::assume calls, all benign (layout validity, sz < 100, can_dereference) |
| PR CI | All 4 Verify std library partitions, autoharness, goto-transcoder, upstream_test pass. The two red checks (Kani List, Kani Metrics) are exit 143 "runner has received a shutdown signal" — infra, not verification |
List of UBs — audited per bullet
My earlier version of this comment asserted "pointer/alignment/overflow/invalid value enabled and passing" as a property of Kani's defaults. That overclaimed on one bullet, so here is the actual per-check tally from the 105-harness run (39,147 SUCCESS / 344 UNREACHABLE / 270 SATISFIED covers / 2 FAILURE — both FAILUREs are the intended intrinsic::abort inside the two #[kani::should_panic] overflow harnesses, so those harnesses pass):
| UB bullet | Checks covering it | Verdict |
|---|---|---|
| Accessing a dangling or misaligned place | 11,423 pointer_dereference; 799 "dereference failure: pointer invalid"; 799 "misaligned pointer to reference cast" |
✅ Directly evidenced |
| UB via compiler intrinsics | 332 arithmetic_overflow; "Offset in bytes overflows isize"; "Offset result and original pointer must point to the same allocation"; "`dst` must be properly aligned"; "failed to compute `size_of_val`/`align_of_val`" |
✅ Directly evidenced |
| Producing an invalid value | Only where raw bytes become typed values — the misaligned pointer to reference cast class and can_dereference on nondet input (the PR's verifier_nondet_vec assumes it explicitly). No blanket validity check |
|
| Mutating immutable bytes | None — Kani emits no check of this kind | ❌ Not checked |
Boundary of what these proofs establish: the run also contains 360 unsupported_construct checks — Kani's marker for a construct it cannot model, which fails if reached. All are SUCCESS (i.e. unreachable), but 68 read "Kani does not support reasoning about pointer to unallocated memory".
Verdict pending
Nothing above is a defect in this PR. But "mutating immutable bytes" is not checkable by Kani at all, which means no solution to any challenge in this repo has ever satisfied that bullet — it is an unstated repo-wide assumption rather than something #582 specifically misses.
I would rather resolve that at the policy level than silently accept it here, so I am holding the final verdict until the committee discusses whether to relax, reword, or formally carve out the "mutating immutable bytes" criterion (and, relatedly, how much of "producing an invalid value" we require). @v3risec — this is on us, not on your submission; please carry on with the four review items in the following comment.
|
Thank you, @v3risec, for this outstanding contribution — the rigor here is exactly the standard we want for these proofs. Placing a Before we merge, four small items — none of them affect the verification result:
One caveat we are tracking separately rather than asking you to fix: |
- Remove unreachable allocation-failure covers from the try_new* harnesses and document that Kani does not currently model allocation failure. - Restore the trailing newline in library/Cargo.lock and remove the unrelated lockfile diff. - Strengthen rc_raw_valid with same-allocation and implicit weak-count validation. - Mark Challenge 26 as resolved in the README and challenge book, linking PR model-checking#582, the Kani proof, and contributor information.
Head branch was pushed to by a user without write access
|
@feliperodri Thank you very much for the careful review and for the encouraging feedback on the verification work. I really appreciate both the detailed suggestions and the recognition. I’ve addressed all four merge-preparation items in
The |




Summary
This PR adds Kani-based verification artifacts for
Rc/Weaksafety inlibrary/alloc/src/rc.rsfor Challenge 26.The change introduces:
#[cfg(kani)]for all 12 required unsafe functions and all 54 listed safe functions;kani::coverproperties to demonstrate that the checked paths are reachable;Rc<[T]>/Weak<[T]>paths can be exercised in a reusable way.No non-verification runtime behavior is changed in normal builds.
Verification Coverage Report
Unsafe functions (required by Challenge 26)
Coverage: 12 / 12 (100%)
Verified set includes:
Rc<mem::MaybeUninit<T>,A>::assume_initRc<[mem::MaybeUninit<T>],A>::assume_initRc<T:?Sized>::from_rawRc<T:?Sized>::increment_strong_countRc<T:?Sized>::decrement_strong_countRc<T:?Sized,A:Allocator>::from_raw_inRc<T:?Sized,A:Allocator>::increment_strong_count_inRc<T:?Sized,A:Allocator>::decrement_strong_count_inRc<T:?Sized,A:Allocator>::get_mut_uncheckedRc<dyn Any,A:Allocator>::downcast_uncheckedWeak<T:?Sized>::from_rawWeak<T:?Sized,A:Allocator>::from_raw_inSafe functions (Challenge 26 list)
Coverage: 54 / 54 (100%)
This exceeds the challenge threshold (>= 75%).
Covered safe functions (54/54), grouped by API category:
Allocation
Rc<T>::newRc<T>::new_uninitRc<T>::new_zeroedRc<T>::try_newRc<T>::try_new_uninitRc<T>::try_new_zeroedRc<T>::pinRc<T,A:Allocator>::new_uninit_inRc<T,A:Allocator>::new_zeroed_inRc<T,A:Allocator>::new_cyclic_inRc<T,A:Allocator>::try_new_inRc<T,A:Allocator>::try_new_uninit_inRc<T,A:Allocator>::try_new_zeroed_inRc<T,A:Allocator>::pin_inSlice
Rc<[T]>::new_uninit_sliceRc<[T]>::new_zeroed_sliceRc<[T]>::into_arrayRc<[T],A:Allocator>::new_uninit_slice_inRc<[T],A:Allocator>::new_zeroed_slice_inRcFromSlice<T: Copy>::from_sliceRcFromSlice<T: Clone>::from_sliceToRcSlice<T, I>::to_rc_sliceConversion and pointer
Rc<T:?Sized, A:Allocator>::innerRc<T:?Sized, A:Allocator>::into_inner_with_allocatorRc<T,A:Allocator>::try_unwrapRc<T:?Sized,A:Allocator>::into_raw_with_allocatorRc<T:?Sized,A:Allocator>::as_ptrRc<T:?Sized,A:Allocator>::get_mutRc<T:?Sized+CloneToUninit, A:Allocator+Clone>::make_mutRc<T:?Sized,A:Allocator>::from_box_inRc<dyn Any,A:Allocator>::downcastTrait implementations (Rc)
Clone<T: ?Sized, A:Allocator>::clone for RcDrop<T: ?Sized, A:Allocator>::drop for RcDefault<T:Default>::defaultDefault<str>::defaultFrom<&str>::fromFrom<Vec<T,A:Allocator>>::fromFrom<Rc<str>>::fromTryFrom<Rc<[T],A:Allocator>>::try_fromWeak and traits
Weak<T:?Sized,A:Allocator>::as_ptrWeak<T:?Sized,A:Allocator>::into_raw_with_allocatorWeak<T:?Sized,A:Allocator>::upgradeWeak<T:?Sized,A:Allocator>::innerDrop<T:?Sized, A:Allocator>::drop for WeakUniqueRc and traits
UniqueRc<T:?Sized,A:Allocator>::into_rcUniqueRc<T:?Sized,A:Allocator+Clone>::downgradeDeref<T:?Sized,A:Allocator>::derefDerefMut<T:?Sized,A:Allocator>::deref_mutDrop<T:?Sized, A:Allocator>::drop for UniqueRcUniqueRcUninit<T:?Sized, A:Allocator>::newUniqueRcUninit<T:?Sized, A:Allocator>::data_ptrDrop<T:?Sized, A:Allocator>::drop for UniqueRcUninitRefcount internals
RcInnerPtr::inc_strongRcInnerPtr::inc_weakNote
RcFromSlice<T: Clone>::from_sliceimplementation with a manually implemented non-trivialClonetype, ensuring that the harness does not dispatch to theTrivialClonespecialization.ToRcSlice<T, I>::to_rc_slicethrough theFromIteratorand exact-sizeTrustedLenpath.Rc::from_iter_exactimplementation and its original element-writing loop. The target implementation is not replaced with acfg(kani)-specific loop.Three Criteria Met (Challenge 26)
Tis instantiated with allowed representative concrete types, and allocator-focused proofs are limited to standard-library allocator scope (Global).Approach
The verification strategy combines contracts for unsafe entry points with executable proof harnesses:
requirespreconditions for pointer validity, alignment soundness, same-allocation checks, and refcount well-formedness.ensures) and mutation footprints (kani::modifies) for refcount-changing operations.#[kani::proof_for_contract(...)]harnesses for all required unsafe functions, and regular#[kani::proof]harnesses for the covered safe functions.kani::coverproperties after the corresponding assertions to show that the verified paths are not vacuous.?Sizedslice-based functions.Rc<[T]>/Weak<[T]>constructions without duplicating per-harness setup logic.cfg(kani)so normal std behavior is unchanged.Scope assumptions (per challenge allowance)
i8..i128,u8..u128),bool,(), arrays, vectors, slices,str, and trait objects (dyn Any).Global(both explicitRc<_, Global>/Weak<_, Global>and defaultRc/Weakaliases).Verification
All harnesses in this PR pass locally with Kani.
Platform-specific CI tractability note
The shared nondeterministic vector helper now bounds the symbolic length
to <= 100for CI resource stability. This is only a verification-time tractability bound for shared CI runners; it is not a safety condition or a function-behavior assumption.Resolves #382
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.