Challenge 18: Verify safety of Slice iterator functions#568
Challenge 18: Verify safety of Slice iterator functions#568Samuelsills wants to merge 1 commit intomodel-checking:mainfrom
Conversation
Add Kani proof harnesses for slice iterator functions specified in Challenge model-checking#18, covering safe abstractions and unsafe __iterator_get_unchecked implementations for Chunks, ChunksMut, ChunksExact, ChunksExactMut, Windows, ArrayWindows, RChunks, RChunksMut, RChunksExact, RChunksExactMut, Split, Iter, and IterMut. Resolves model-checking#282 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verification Coverage ReportPart 1: Macro Functions (16/16 ✅)Pre-existing harnesses verify Part 2: Unsafe
|
There was a problem hiding this comment.
Pull request overview
Adds additional Kani proof harnesses under #[cfg(kani)] to exercise (and locally verify) safety-related behavior for various slice iterator adapters and their hidden __iterator_get_unchecked implementations as part of Challenge #18 / issue #282.
Changes:
- Added new Kani proofs for
Iter/IterMut-related iterator operations (last,fold,for_each,position,rposition,into_slice,as_mut_slice). - Added new Kani proofs for
Split, chunk-based iterators,array_windows, and reverse chunk iterators (next,nth,next_back,nth_back,last, and constructors/remainders in a few cases). - Added Kani proofs calling unsafe
__iterator_get_uncheckedfor multiple slice iterator adapter types with bounded indices.
| let arr: [i32; 5] = kani::any(); | ||
| let s: &[i32] = &arr; | ||
| let iter = s.iter(); | ||
| let last = iter.last(); | ||
| assert_eq!(*last.unwrap(), arr[4]); | ||
| } | ||
|
|
||
| #[kani::proof] | ||
| #[kani::unwind(6)] | ||
| fn verify_iter_fold() { | ||
| let arr: [i32; 3] = kani::any(); | ||
| let s: &[i32] = &arr; | ||
| let sum = s.iter().fold(0i32, |acc, &x| acc.wrapping_add(x)); | ||
| assert_eq!(sum, arr[0].wrapping_add(arr[1]).wrapping_add(arr[2])); |
There was a problem hiding this comment.
These Kani proofs use fixed-size arrays (e.g., [i32; 5]) and then assert specific functional results. That only verifies the behavior for that exact length and doesn't exercise the any_slice/any_iter pattern used elsewhere in this verify module to cover arbitrary (including empty) slices and pointer/len edge cases relevant to safety. Consider rewriting these harnesses to build the iterator from any_slice/any_iter (and avoid unwrap() unless you kani::assume non-empty) so the proofs actually target the safety/invariant surface across lengths rather than a single concrete case.
| let arr: [i32; 5] = kani::any(); | |
| let s: &[i32] = &arr; | |
| let iter = s.iter(); | |
| let last = iter.last(); | |
| assert_eq!(*last.unwrap(), arr[4]); | |
| } | |
| #[kani::proof] | |
| #[kani::unwind(6)] | |
| fn verify_iter_fold() { | |
| let arr: [i32; 3] = kani::any(); | |
| let s: &[i32] = &arr; | |
| let sum = s.iter().fold(0i32, |acc, &x| acc.wrapping_add(x)); | |
| assert_eq!(sum, arr[0].wrapping_add(arr[1]).wrapping_add(arr[2])); | |
| let s: &[i32] = kani::any_slice(); | |
| let iter = s.iter(); | |
| let last = iter.last(); | |
| match last { | |
| Some(&value) => { | |
| let len = s.len(); | |
| // If `last` returns Some, the slice must be non-empty, | |
| // and the value must equal the last element. | |
| assert!(len > 0); | |
| assert_eq!(value, s[len - 1]); | |
| } | |
| None => { | |
| // If `last` returns None, the slice must be empty. | |
| assert!(s.is_empty()); | |
| } | |
| } | |
| } | |
| #[kani::proof] | |
| #[kani::unwind(6)] | |
| fn verify_iter_fold() { | |
| let s: &[i32] = kani::any_slice(); | |
| let sum = s.iter().fold(0i32, |acc, &x| acc.wrapping_add(x)); | |
| // Compute the expected result with an explicit loop to | |
| // cross-check `fold` for arbitrary slice lengths. | |
| let mut expected = 0i32; | |
| for &x in s { | |
| expected = expected.wrapping_add(x); | |
| } | |
| assert_eq!(sum, expected); |
| #[kani::proof] | ||
| fn verify_iter_mut_new() { | ||
| let mut arr: [i32; 5] = kani::any(); | ||
| let s: &mut [i32] = &mut arr; | ||
| let iter = s.iter_mut(); | ||
| assert_eq!(iter.len(), 5); | ||
| } |
There was a problem hiding this comment.
verify_iter_mut_new is described as covering the safe abstraction IterMut::new, but it only calls slice.iter_mut() and checks len(). If the goal is to verify IterMut::new specifically (and the iterator safety invariant), consider constructing with IterMut::new(&mut ...) here and asserting iter.is_safe() (as done for Iter in check_new_iter).
| #[kani::proof] | ||
| #[kani::unwind(6)] | ||
| fn verify_iter_position() { | ||
| let arr: [i32; 5] = kani::any(); | ||
| let s: &[i32] = &arr; | ||
| let target = arr[2]; | ||
| let pos = s.iter().position(|&x| x == target); | ||
| assert!(pos.is_some()); | ||
| assert!(pos.unwrap() <= 2); | ||
| } |
There was a problem hiding this comment.
Within this #[cfg(kani)] mod verify block, the existing harness generators use kani::assert(...) (e.g., line 3306) rather than assert!/assert_eq!. Using assert! will panic on failure and provides less Kani-specific context; for consistency with the rest of this module, consider switching these new harness assertions to kani::assert / kani::assert_eq-style checks (or add messages).
| #[kani::proof] | ||
| #[kani::unwind(6)] | ||
| fn verify_chunks_next_back() { | ||
| let arr: [i32; 5] = kani::any(); | ||
| let s: &[i32] = &arr; | ||
| let mut chunks = s.chunks(2); | ||
| let last = chunks.next_back(); | ||
| assert!(last.is_some()); | ||
| assert_eq!(last.unwrap().len(), 1); | ||
| } | ||
|
|
||
| #[kani::proof] | ||
| #[kani::unwind(6)] | ||
| fn verify_chunks_mut_next() { | ||
| let mut arr: [i32; 5] = kani::any(); | ||
| let s: &mut [i32] = &mut arr; | ||
| let mut chunks = s.chunks_mut(2); | ||
| let first = chunks.next(); | ||
| assert!(first.is_some()); | ||
| assert_eq!(first.unwrap().len(), 2); | ||
| } | ||
|
|
There was a problem hiding this comment.
This section adds many highly repetitive harnesses for chunk iterators that differ only by iterator type/method and a few expected lengths. Since this verify module already uses macros to generate harnesses (e.g., check_iter_with_ty! / check_safe_abstraction!), consider introducing a small macro/helper for these chunk/split/window harnesses as well to reduce duplication and make it easier to audit completeness against the Challenge #18 checklist.
| #[kani::proof] | |
| #[kani::unwind(6)] | |
| fn verify_chunks_next_back() { | |
| let arr: [i32; 5] = kani::any(); | |
| let s: &[i32] = &arr; | |
| let mut chunks = s.chunks(2); | |
| let last = chunks.next_back(); | |
| assert!(last.is_some()); | |
| assert_eq!(last.unwrap().len(), 1); | |
| } | |
| #[kani::proof] | |
| #[kani::unwind(6)] | |
| fn verify_chunks_mut_next() { | |
| let mut arr: [i32; 5] = kani::any(); | |
| let s: &mut [i32] = &mut arr; | |
| let mut chunks = s.chunks_mut(2); | |
| let first = chunks.next(); | |
| assert!(first.is_some()); | |
| assert_eq!(first.unwrap().len(), 2); | |
| } | |
| #[cfg(kani)] | |
| macro_rules! kani_chunks_harness_immut { | |
| ($name:ident, $iter_method:ident, $next_method:ident, $expected_len:expr) => { | |
| #[kani::proof] | |
| #[kani::unwind(6)] | |
| fn $name() { | |
| let arr: [i32; 5] = kani::any(); | |
| let s: &[i32] = &arr; | |
| let mut iter = s.$iter_method(2); | |
| let item = iter.$next_method(); | |
| assert!(item.is_some()); | |
| assert_eq!(item.unwrap().len(), $expected_len); | |
| } | |
| }; | |
| } | |
| #[cfg(kani)] | |
| macro_rules! kani_chunks_harness_mut { | |
| ($name:ident, $iter_method:ident, $next_method:ident, $expected_len:expr) => { | |
| #[kani::proof] | |
| #[kani::unwind(6)] | |
| fn $name() { | |
| let mut arr: [i32; 5] = kani::any(); | |
| let s: &mut [i32] = &mut arr; | |
| let mut iter = s.$iter_method(2); | |
| let item = iter.$next_method(); | |
| assert!(item.is_some()); | |
| assert_eq!(item.unwrap().len(), $expected_len); | |
| } | |
| }; | |
| } | |
| kani_chunks_harness_immut!(verify_chunks_next_back, chunks, next_back, 1); | |
| kani_chunks_harness_mut!(verify_chunks_mut_next, chunks_mut, next, 2); |
Summary
Add Kani proof harnesses verifying slice iterator functions specified in Challenge #18:
new,into_slice,as_mut_slice,last,fold,for_each,position,rpositionnext,next_backnext,nth,next_back,nth_back,new,lastacross Chunks, ChunksMut, ChunksExact, ChunksExactMut, ArrayWindows, RChunks, RChunksMut, RChunksExact, RChunksExactMut__iterator_get_unchecked: Windows, Chunks, ChunksMut, ChunksExact, ChunksExactMut, RChunks, RChunksMut, RChunksExact, RChunksExactMutBuilds on pre-existing Iter harnesses (Part 1 macro functions) which cover
make_slice,len,is_empty,next,size_hint,count,nth,advance_by,next_back,nth_back,advance_back_by.All harnesses verified locally with Kani.
Resolves #282