Challenge 27: Verify safety of Arc functions#575
Challenge 27: Verify safety of Arc functions#575Samuelsills wants to merge 3 commits intomodel-checking:mainfrom
Conversation
Add Kani proof harnesses for Arc functions specified in Challenge model-checking#27: 12 unsafe functions (assume_init, from_raw, from_raw_in, increment/decrement_strong_count, get_mut_unchecked, downcast_unchecked, Weak::from_raw, Weak::from_raw_in) and 35 safe functions covering allocation, atomic reference counting, conversion, Weak pointer operations, and trait implementations. Exceeds 75% safe threshold (35/42 = 83%). Resolves model-checking#383 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verification Coverage ReportUnsafe Functions (12/12 — 100% ✅)
Safe Functions with Unsafe Code (35/42 — 83%, exceeds 75% threshold ✅)Allocation, conversion, cloning, downcasting, Weak pointer operations, trait implementations. Total: 47 harnesses (12 unsafe + 35 safe) UBs Checked
Note on Data RacesIn single-threaded Kani verification, data races cannot occur (no concurrent threads). Pointer validity, alignment, and initialization are verified for all operations. Verification Approach
|
There was a problem hiding this comment.
Pull request overview
Adds Kani verification harnesses to alloc::sync (Arc/Weak) as part of Challenge #27 / tracking issue #383, aiming to model-check the safety contracts of selected APIs (including all required unsafe ones).
Changes:
- Introduces a
#[cfg(kani)]verification module inlibrary/alloc/src/sync.rs. - Adds Kani proof harnesses covering 12 required unsafe Arc/Weak functions plus a broad set of safe Arc/Weak operations.
| let ptr = Arc::as_ptr(&a); | ||
| unsafe { | ||
| Arc::increment_strong_count(ptr); | ||
| } | ||
| let a2 = unsafe { Arc::from_raw(ptr) }; | ||
| assert!(*a2 == 42); | ||
| } | ||
|
|
||
| #[kani::proof] | ||
| fn verify_increment_strong_count_in() { | ||
| let a = Arc::new_in(42i32, Global); | ||
| let ptr = Arc::as_ptr(&a); | ||
| unsafe { | ||
| Arc::increment_strong_count_in(ptr, Global); | ||
| } | ||
| let a2 = unsafe { Arc::from_raw_in(ptr, Global) }; |
There was a problem hiding this comment.
Arc::increment_strong_count’s safety contract requires that the pointer comes from Arc::into_raw. This harness uses Arc::as_ptr(&a) instead, which means the proof is not exercising the API under its documented preconditions (and could be unsound under strict provenance assumptions). Consider obtaining ptr via Arc::into_raw(...) (and then balancing the extra strong count via Arc::from_raw/Arc::decrement_strong_count as in the docs).
| let ptr = Arc::as_ptr(&a); | |
| unsafe { | |
| Arc::increment_strong_count(ptr); | |
| } | |
| let a2 = unsafe { Arc::from_raw(ptr) }; | |
| assert!(*a2 == 42); | |
| } | |
| #[kani::proof] | |
| fn verify_increment_strong_count_in() { | |
| let a = Arc::new_in(42i32, Global); | |
| let ptr = Arc::as_ptr(&a); | |
| unsafe { | |
| Arc::increment_strong_count_in(ptr, Global); | |
| } | |
| let a2 = unsafe { Arc::from_raw_in(ptr, Global) }; | |
| let ptr = Arc::into_raw(a); | |
| unsafe { | |
| Arc::increment_strong_count(ptr); | |
| } | |
| let a1 = unsafe { Arc::from_raw(ptr) }; | |
| let a2 = unsafe { Arc::from_raw(ptr) }; | |
| assert!(*a1 == 42); | |
| assert!(*a2 == 42); | |
| } | |
| #[kani::proof] | |
| fn verify_increment_strong_count_in() { | |
| let a = Arc::new_in(42i32, Global); | |
| let (ptr, alloc) = Arc::into_raw_with_allocator(a); | |
| unsafe { | |
| Arc::increment_strong_count_in(ptr, alloc); | |
| } | |
| let a1 = unsafe { Arc::from_raw_in(ptr, alloc) }; | |
| let a2 = unsafe { Arc::from_raw_in(ptr, alloc) }; | |
| assert!(*a1 == 42); |
| let ptr = Arc::as_ptr(&a); | ||
| unsafe { | ||
| Arc::increment_strong_count(ptr); | ||
| } | ||
| let a2 = unsafe { Arc::from_raw(ptr) }; | ||
| assert!(*a2 == 42); | ||
| } | ||
|
|
||
| #[kani::proof] | ||
| fn verify_increment_strong_count_in() { | ||
| let a = Arc::new_in(42i32, Global); | ||
| let ptr = Arc::as_ptr(&a); | ||
| unsafe { | ||
| Arc::increment_strong_count_in(ptr, Global); | ||
| } | ||
| let a2 = unsafe { Arc::from_raw_in(ptr, Global) }; | ||
| assert!(*a2 == 42); |
There was a problem hiding this comment.
Arc::increment_strong_count_in requires the pointer be obtained from Arc::into_raw/Arc::into_raw_with_allocator for the same A. Using Arc::as_ptr(&a) here means the harness is not respecting the documented safety preconditions, weakening/invalidating the proof. Please switch to Arc::into_raw_with_allocator (or Arc::into_raw for Global) and ensure the strong-count balance is handled as intended by the API.
| let ptr = Arc::as_ptr(&a); | |
| unsafe { | |
| Arc::increment_strong_count(ptr); | |
| } | |
| let a2 = unsafe { Arc::from_raw(ptr) }; | |
| assert!(*a2 == 42); | |
| } | |
| #[kani::proof] | |
| fn verify_increment_strong_count_in() { | |
| let a = Arc::new_in(42i32, Global); | |
| let ptr = Arc::as_ptr(&a); | |
| unsafe { | |
| Arc::increment_strong_count_in(ptr, Global); | |
| } | |
| let a2 = unsafe { Arc::from_raw_in(ptr, Global) }; | |
| assert!(*a2 == 42); | |
| let ptr = Arc::into_raw(a); | |
| unsafe { | |
| Arc::increment_strong_count(ptr); | |
| } | |
| let a1 = unsafe { Arc::from_raw(ptr) }; | |
| let a2 = unsafe { Arc::from_raw(ptr) }; | |
| assert!(*a1 == 42 && *a2 == 42); | |
| } | |
| #[kani::proof] | |
| fn verify_increment_strong_count_in() { | |
| let a = Arc::new_in(42i32, Global); | |
| let (ptr, alloc) = Arc::into_raw_with_allocator(a); | |
| unsafe { | |
| Arc::increment_strong_count_in(ptr, alloc); | |
| } | |
| let a1 = unsafe { Arc::from_raw_in(ptr, alloc) }; | |
| let a2 = unsafe { Arc::from_raw_in(ptr, alloc) }; | |
| assert!(*a1 == 42 && *a2 == 42); |
| let a = Arc::new(42i32); | ||
| let a2 = a.clone(); | ||
| let ptr = Arc::as_ptr(&a2); | ||
| core::mem::forget(a2); | ||
| unsafe { | ||
| Arc::decrement_strong_count(ptr); | ||
| } |
There was a problem hiding this comment.
Arc::decrement_strong_count’s safety contract requires that ptr was obtained through Arc::into_raw. This harness derives ptr via Arc::as_ptr(&a2) and then forgets the Arc, which doesn’t match the API’s specified preconditions and may undermine the proof. Prefer let ptr = Arc::into_raw(a2); (no forget needed) and then call decrement_strong_count(ptr).
| let a = Arc::new_in(42i32, Global); | ||
| let a2 = a.clone(); | ||
| let ptr = Arc::as_ptr(&a2); | ||
| core::mem::forget(a2); | ||
| unsafe { | ||
| Arc::decrement_strong_count_in(ptr, Global); | ||
| } |
There was a problem hiding this comment.
Arc::decrement_strong_count_in requires ptr to originate from Arc::into_raw_with_allocator for the same allocator passed in. This harness uses Arc::as_ptr + forget, which doesn’t satisfy the documented preconditions and can make the proof misleading. Please obtain ptr with Arc::into_raw_with_allocator(a2) (or Arc::into_raw_in-equivalent pattern) and then call decrement_strong_count_in with the returned allocator.
verify_into_array previously called a.try_into(), which goes through the TryFrom impl, not Arc::into_array. The TryFrom path is already covered by verify_try_from. Rewrite verify_into_array to call a.into_array() directly. Add verify_into_inner_with_allocator (none existed before): call Arc::into_inner_with_allocator(a) and reconstruct via from_inner_in (matching how the TryFrom impl uses the helper). Both functions are listed in the Challenge 27 (Arc) success criteria.
Summary
Add Kani proof harnesses for Arc functions specified in Challenge #27:
Unsafe (12/12 — all required):
assume_init(single + slice),from_raw,from_raw_in,increment_strong_count,increment_strong_count_in,decrement_strong_count,decrement_strong_count_in,get_mut_unchecked,downcast_unchecked,Weak::from_raw,Weak::from_raw_inSafe (35/42 — 83%, exceeds 75% threshold):
All harnesses verified locally with Kani.
Resolves #383