Challenge 27: Verify safety of Arc functions#575
Challenge 27: Verify safety of Arc functions#575Samuelsills wants to merge 2 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.
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