Challenge 26: Verify the safety of Rc and Weak in alloc::rc - #661
Open
kasimte wants to merge 1 commit into
Open
Conversation
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.
Towards #382. Solves Challenge 26: Verify reference-counted Cell implementation: safety contracts on all 12 required
pub unsafefunctions and Kani harnesses covering all 54 listed safe functions inalloc::rc. The suite lives in onemod verify: 101 harness macros expand to 1,293 harness functions, 205 of themproof_for_contract, plus 3 standalone harnesses. Every harness carries akani::covernon-vacuity witness. Element types are primitives, slices, and small arrays (the challenge permits limiting genericTto primitives). Allocator coverage isGlobal, explicit and default.Systemlives instdand is not nameable fromalloc's harnesses. All 1,293 harnesses pass viascripts/run-kani.sh.Coverage: required unsafe functions (12/12)
Rc<MaybeUninit<T>, A>::assume_initproof_for_contract(rc_check_assume_init, 13 types)Rc<[MaybeUninit<T>], A>::assume_initproof_for_contract(rc_check_assume_init_slice, 12 types)Rc<T>::from_rawproof_for_contract(rc_check_from_raw_{sized,unsized}, 18 instantiations)Rc<T>::increment_strong_countproof_for_contract(sized + unsized, 18)Rc<T>::decrement_strong_countproof_for_contract(sized + unsized, 18)Rc<T, A>::from_raw_inproof_for_contract(sized + unsized, 18)Rc<T, A>::increment_strong_count_inproof_for_contract(sized + unsized, 18)Rc<T, A>::decrement_strong_count_inproof_for_contract(sized + unsized, 18)Rc<T, A>::get_mut_uncheckedproof_for_contract(sized + unsized, 18)Rc<dyn Any, A>::downcast_uncheckedproof_for_contract(13 types + 5Vecpayloads)Weak<T>::from_rawproof_for_contract(sized + unsized, 18)Weak<T, A>::from_raw_inproof_for_contract(sized + unsized, 18)Coverage: safe functions (54/54)
All 54 functions from the challenge's table, grouped by API area; each entry names its harness family.
new,new_uninit,new_zeroed,try_new,try_new_uninit,try_new_zeroed,new_uninit_in,new_zeroed_in,new_cyclic_in,try_new_in,try_new_uninit_in,try_new_zeroed_in,pin,pin_in(onerc_check_*family each;pin/pin_ininclude a!Unpinsentinel type).new_uninit_slice,new_zeroed_slice,new_uninit_slice_in,new_zeroed_slice_in,into_array(one family each, symbolic lengths under layout-validity assumptions).inner,into_inner_with_allocator,into_raw_with_allocator,as_ptr,get_mut(three-state),make_mut(three-state),downcast(success + failure),from_box_in,try_unwrap(three-state).From/FromIterator/TryFrom:From<&str>,From<Vec<T>>,From<Rc<str>>,ToRcSlice::to_rc_slice(both specializations),TryFrom<Rc<[T]>> for Rc<[T; N]>(both arms, one cover per arm).RcFromSlice: theT: Copy-era specialization (today'sTrivialCloneimpl;rc_check_from_slice_copy, 13 types) and theT: Clonedefault (rc_check_from_slice_clone, via a manual-Clonewrapper — every primitive isTrivialClone, so no primitive can reach the default path).Drop for Rc(three-state),Clone for Rc,Default(13 types +Vecpayloads),Default for Rc<str>.Weak:as_ptr(live + dangling),into_raw_with_allocator(live + dangling),upgrade(three-path),inner(both branches),Drop for Weak(three-path).RcInnerPtr:inc_strong,inc_weak(non-overflow harnesses + the twoshould_panicoverflow harnesses).UniqueRc/UniqueRcUninit:into_rc,downgrade,Deref,DerefMut,Drop for UniqueRc(both branches),UniqueRcUninit::new,data_ptr,Drop for UniqueRcUninit.Contracts
safety::{requires, ensures}. The fourmodifiesclauses usecfg_attr(kani, kani::modifies(...))— thesafetycrate exposes no modifies wrapper.ptr::addr_eq, check size and alignment viakani::mem::checked_size_of_raw/checked_align_of_raw, and requirestrong >= 1. No contract constructs or consumes anRcinternally.assume_initharness comments state exactly what is constrained (the harness-generated bytes, beforeset_len) and why the postcondition assertion is not circular.Proof soundness
kani::coverwitness per harness body (141 source lines), placed after the last assumption and before the operation under verification (inshould_panicharnesses, before the panicking operation); every harness reportsN of N cover properties satisfied.Some/None,Ok/Err, strong/weak count relations, pointer identity, post-dropupgrade()behavior,make_mut's clone-on-write and weak-disassociation effects); constructor harnesses assert value preservation.kani::assumein the suite has a reachability witness downstream of it;TryFromadditionally covers both match arms.unique/shared/weak_present,success/failure,live/strong_zero/dangling) construct each behavior-relevant state explicitly.should_panicpairing: both reachable abort guards in the file (inc_strong/inc_weakrefcount overflow) have#[kani::should_panic]harnesses. A sweep of the remainingassert!/panic!/unwrapsites found no other guard reachable under the documented preconditions: theLayout::array().unwrap()sites are unreachable under the harnesses' layout-validity assumptions, and theTrustedLencapacity overflowpanic requires an iterator longer thanusize::MAX.Loop verification
from_iter_exact's element-writing loop is verified against the unmodified upstreamfor-loop by bounded unrolling (#[kani::unwind(5)], length-3 inputs, justified in-code). Unrolling is deliberate: a loop-contract formulation cannot carry the harnesses' element-value postconditions, because under invariant havoc no invariant relates an opaqueimpl Iterator's yielded prefix to the memory the harness asserts on. The file contains nocfg(kani)code substitutions: the code Kani verifies is the code that runs.Bounds
The shared nondeterministic-vector helper bounds the symbolic length (
sz <= 100). This is a CI tractability measure, not a safety precondition; sampled harnesses re-verified locally with the bound removed:clone_rc_vec_u822.4s,from_raw_vec_u840.5s,get_mut_vec_u8_shared_none35.2s (all successful, covers satisfied). Theassume_init_sliceharnesses carry the samelen <= 100bound for CI-runner memory on wide element types: the unbounded form verifies on the Linux CI runners, but the widest variants exceed macOS runner memory. In-code comments carry the same statements. Fixed harness lengths (len 3,N = 100ininto_array) carry in-code justifications.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.