Skip to content

Commit c1620b6

Browse files
author
Kasim Te
committed
Add Kani verification harnesses for iterator adapter unsafe functions and safe abstractions (Challenge 16)
1 parent f25c24e commit c1620b6

File tree

13 files changed

+1029
-0
lines changed

13 files changed

+1029
-0
lines changed

library/core/src/iter/adapters/array_chunks.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use crate::iter::adapters::SourceIter;
33
use crate::iter::{
44
ByRefSized, FusedIterator, InPlaceIterable, TrustedFused, TrustedRandomAccessNoCoerce,
55
};
6+
#[cfg(kani)]
7+
use crate::kani;
68
use crate::num::NonZero;
79
use crate::ops::{ControlFlow, NeverShortCircuit, Try};
810

@@ -274,3 +276,77 @@ unsafe impl<I: InPlaceIterable + Iterator, const N: usize> InPlaceIterable for A
274276
}
275277
};
276278
}
279+
280+
#[cfg(kani)]
281+
#[unstable(feature = "kani", issue = "none")]
282+
mod verify {
283+
use super::*;
284+
285+
// next_back_remainder (uses unwrap_err_unchecked internally)
286+
// Uses Range<u8> instead of slice::Iter to avoid pointer-heavy symbolic
287+
// state that causes CBMC to exhaust resources. Range<u8> satisfies
288+
// DoubleEndedIterator + ExactSizeIterator and exercises the same
289+
// unwrap_err_unchecked path.
290+
#[kani::proof]
291+
#[kani::unwind(5)]
292+
fn check_array_chunks_next_back_remainder_n2() {
293+
let len: u8 = kani::any();
294+
kani::assume(len <= 4);
295+
let mut chunks = ArrayChunks::<_, 2>::new(0..len);
296+
let _ = chunks.next_back();
297+
}
298+
299+
#[kani::proof]
300+
#[kani::unwind(5)]
301+
fn check_array_chunks_next_back_remainder_n3() {
302+
let len: u8 = kani::any();
303+
kani::assume(len <= 4);
304+
let mut chunks = ArrayChunks::<_, 3>::new(0..len);
305+
let _ = chunks.next_back();
306+
}
307+
308+
// fold (TRANC specialized — uses __iterator_get_unchecked in a loop)
309+
#[kani::proof]
310+
#[kani::unwind(9)]
311+
fn check_array_chunks_fold_n2_u8() {
312+
const MAX_LEN: usize = 8;
313+
let array: [u8; MAX_LEN] = kani::any();
314+
let slice = kani::slice::any_slice_of_array(&array);
315+
let chunks = ArrayChunks::<_, 2>::new(slice.iter());
316+
let count = Iterator::fold(chunks, 0usize, |acc, _| acc + 1);
317+
assert_eq!(count, slice.len() / 2);
318+
}
319+
320+
#[kani::proof]
321+
#[kani::unwind(9)]
322+
fn check_array_chunks_fold_n2_unit() {
323+
const MAX_LEN: usize = 8;
324+
let array: [(); MAX_LEN] = [(); MAX_LEN];
325+
let slice = kani::slice::any_slice_of_array(&array);
326+
let chunks = ArrayChunks::<_, 2>::new(slice.iter());
327+
let count = Iterator::fold(chunks, 0usize, |acc, _| acc + 1);
328+
assert_eq!(count, slice.len() / 2);
329+
}
330+
331+
#[kani::proof]
332+
#[kani::unwind(9)]
333+
fn check_array_chunks_fold_n3_u8() {
334+
const MAX_LEN: usize = 6;
335+
let array: [u8; MAX_LEN] = kani::any();
336+
let slice = kani::slice::any_slice_of_array(&array);
337+
let chunks = ArrayChunks::<_, 3>::new(slice.iter());
338+
let count = Iterator::fold(chunks, 0usize, |acc, _| acc + 1);
339+
assert_eq!(count, slice.len() / 3);
340+
}
341+
342+
#[kani::proof]
343+
#[kani::unwind(9)]
344+
fn check_array_chunks_fold_n2_char() {
345+
const MAX_LEN: usize = 8;
346+
let array: [char; MAX_LEN] = kani::any();
347+
let slice = kani::slice::any_slice_of_array(&array);
348+
let chunks = ArrayChunks::<_, 2>::new(slice.iter());
349+
let count = Iterator::fold(chunks, 0usize, |acc, _| acc + 1);
350+
assert_eq!(count, slice.len() / 2);
351+
}
352+
}

library/core/src/iter/adapters/cloned.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ where
152152
I: UncheckedIterator<Item = &'a T>,
153153
T: Clone,
154154
{
155+
#[requires(self.it.size_hint().0 > 0)]
155156
unsafe fn next_unchecked(&mut self) -> T {
156157
// SAFETY: `Cloned` is 1:1 with the inner iterator, so if the caller promised
157158
// that there's an element left, the inner iterator has one too.
@@ -193,3 +194,102 @@ unsafe impl<I: InPlaceIterable> InPlaceIterable for Cloned<I> {
193194
const EXPAND_BY: Option<NonZero<usize>> = I::EXPAND_BY;
194195
const MERGE_BY: Option<NonZero<usize>> = I::MERGE_BY;
195196
}
197+
198+
#[cfg(kani)]
199+
#[unstable(feature = "kani", issue = "none")]
200+
mod verify {
201+
use super::*;
202+
203+
#[kani::proof]
204+
#[kani::unwind(9)]
205+
fn check_cloned_get_unchecked_u8() {
206+
const MAX_LEN: usize = 8;
207+
let array: [u8; MAX_LEN] = kani::any();
208+
let slice = kani::slice::any_slice_of_array(&array);
209+
let mut iter = Cloned::new(slice.iter());
210+
let idx: usize = kani::any();
211+
kani::assume(idx < iter.size_hint().0);
212+
let result = unsafe { iter.__iterator_get_unchecked(idx) };
213+
assert_eq!(result, slice[idx]);
214+
}
215+
216+
#[kani::proof]
217+
#[kani::unwind(9)]
218+
fn check_cloned_get_unchecked_unit() {
219+
const MAX_LEN: usize = 8;
220+
let array: [(); MAX_LEN] = [(); MAX_LEN];
221+
let slice = kani::slice::any_slice_of_array(&array);
222+
let mut iter = Cloned::new(slice.iter());
223+
let idx: usize = kani::any();
224+
kani::assume(idx < iter.size_hint().0);
225+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
226+
}
227+
228+
#[kani::proof]
229+
#[kani::unwind(9)]
230+
fn check_cloned_next_unchecked_u8() {
231+
const MAX_LEN: usize = 8;
232+
let array: [u8; MAX_LEN] = kani::any();
233+
let slice = kani::slice::any_slice_of_array(&array);
234+
let mut iter = Cloned::new(slice.iter());
235+
kani::assume(iter.size_hint().0 > 0);
236+
let _ = unsafe { iter.next_unchecked() };
237+
}
238+
239+
#[kani::proof]
240+
#[kani::unwind(9)]
241+
fn check_cloned_next_unchecked_unit() {
242+
const MAX_LEN: usize = 8;
243+
let array: [(); MAX_LEN] = [(); MAX_LEN];
244+
let slice = kani::slice::any_slice_of_array(&array);
245+
let mut iter = Cloned::new(slice.iter());
246+
kani::assume(iter.size_hint().0 > 0);
247+
let _ = unsafe { iter.next_unchecked() };
248+
}
249+
250+
#[kani::proof]
251+
#[kani::unwind(9)]
252+
fn check_cloned_get_unchecked_char() {
253+
const MAX_LEN: usize = 8;
254+
let array: [char; MAX_LEN] = kani::any();
255+
let slice = kani::slice::any_slice_of_array(&array);
256+
let mut iter = Cloned::new(slice.iter());
257+
let idx: usize = kani::any();
258+
kani::assume(idx < iter.size_hint().0);
259+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
260+
}
261+
262+
#[kani::proof]
263+
#[kani::unwind(9)]
264+
fn check_cloned_get_unchecked_tup() {
265+
const MAX_LEN: usize = 8;
266+
let array: [(char, u8); MAX_LEN] = kani::any();
267+
let slice = kani::slice::any_slice_of_array(&array);
268+
let mut iter = Cloned::new(slice.iter());
269+
let idx: usize = kani::any();
270+
kani::assume(idx < iter.size_hint().0);
271+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
272+
}
273+
274+
#[kani::proof]
275+
#[kani::unwind(9)]
276+
fn check_cloned_next_unchecked_char() {
277+
const MAX_LEN: usize = 8;
278+
let array: [char; MAX_LEN] = kani::any();
279+
let slice = kani::slice::any_slice_of_array(&array);
280+
let mut iter = Cloned::new(slice.iter());
281+
kani::assume(iter.size_hint().0 > 0);
282+
let _ = unsafe { iter.next_unchecked() };
283+
}
284+
285+
#[kani::proof]
286+
#[kani::unwind(9)]
287+
fn check_cloned_next_unchecked_tup() {
288+
const MAX_LEN: usize = 8;
289+
let array: [(char, u8); MAX_LEN] = kani::any();
290+
let slice = kani::slice::any_slice_of_array(&array);
291+
let mut iter = Cloned::new(slice.iter());
292+
kani::assume(iter.size_hint().0 > 0);
293+
let _ = unsafe { iter.next_unchecked() };
294+
}
295+
}

library/core/src/iter/adapters/copied.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,101 @@ unsafe impl<I: InPlaceIterable> InPlaceIterable for Copied<I> {
278278
const EXPAND_BY: Option<NonZero<usize>> = I::EXPAND_BY;
279279
const MERGE_BY: Option<NonZero<usize>> = I::MERGE_BY;
280280
}
281+
282+
#[cfg(kani)]
283+
#[unstable(feature = "kani", issue = "none")]
284+
mod verify {
285+
use super::*;
286+
287+
// Phase 0 spike: proof_for_contract doesn't work on trait impl methods,
288+
// so we use #[kani::proof] with manual precondition via kani::assume.
289+
#[kani::proof]
290+
#[kani::unwind(9)]
291+
fn check_copied_get_unchecked_u8() {
292+
const MAX_LEN: usize = 8;
293+
let array: [u8; MAX_LEN] = kani::any();
294+
let slice = kani::slice::any_slice_of_array(&array);
295+
let mut iter = Copied::new(slice.iter());
296+
let idx: usize = kani::any();
297+
kani::assume(idx < iter.size_hint().0);
298+
let result = unsafe { iter.__iterator_get_unchecked(idx) };
299+
assert_eq!(result, slice[idx]);
300+
}
301+
302+
#[kani::proof]
303+
#[kani::unwind(9)]
304+
fn check_copied_get_unchecked_unit() {
305+
const MAX_LEN: usize = 8;
306+
let array: [(); MAX_LEN] = [(); MAX_LEN];
307+
let slice = kani::slice::any_slice_of_array(&array);
308+
let mut iter = Copied::new(slice.iter());
309+
let idx: usize = kani::any();
310+
kani::assume(idx < iter.size_hint().0);
311+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
312+
}
313+
314+
// spec_next_chunk (specialized for slice::Iter, uses ptr::copy_nonoverlapping)
315+
#[kani::proof]
316+
#[kani::unwind(9)]
317+
fn check_spec_next_chunk_n2_u8() {
318+
const MAX_LEN: usize = 8;
319+
let array: [u8; MAX_LEN] = kani::any();
320+
let slice = kani::slice::any_slice_of_array(&array);
321+
let mut iter = Copied::new(slice.iter());
322+
let _ = iter.next_chunk::<2>();
323+
}
324+
325+
#[kani::proof]
326+
#[kani::unwind(9)]
327+
fn check_spec_next_chunk_n3_u8() {
328+
const MAX_LEN: usize = 8;
329+
let array: [u8; MAX_LEN] = kani::any();
330+
let slice = kani::slice::any_slice_of_array(&array);
331+
let mut iter = Copied::new(slice.iter());
332+
let _ = iter.next_chunk::<3>();
333+
}
334+
335+
#[kani::proof]
336+
#[kani::unwind(9)]
337+
fn check_spec_next_chunk_n2_unit() {
338+
const MAX_LEN: usize = 8;
339+
let array: [(); MAX_LEN] = [(); MAX_LEN];
340+
let slice = kani::slice::any_slice_of_array(&array);
341+
let mut iter = Copied::new(slice.iter());
342+
let _ = iter.next_chunk::<2>();
343+
}
344+
345+
#[kani::proof]
346+
#[kani::unwind(9)]
347+
fn check_copied_get_unchecked_char() {
348+
const MAX_LEN: usize = 8;
349+
let array: [char; MAX_LEN] = kani::any();
350+
let slice = kani::slice::any_slice_of_array(&array);
351+
let mut iter = Copied::new(slice.iter());
352+
let idx: usize = kani::any();
353+
kani::assume(idx < iter.size_hint().0);
354+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
355+
}
356+
357+
#[kani::proof]
358+
#[kani::unwind(9)]
359+
fn check_copied_get_unchecked_tup() {
360+
const MAX_LEN: usize = 8;
361+
let array: [(char, u8); MAX_LEN] = kani::any();
362+
let slice = kani::slice::any_slice_of_array(&array);
363+
let mut iter = Copied::new(slice.iter());
364+
let idx: usize = kani::any();
365+
kani::assume(idx < iter.size_hint().0);
366+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
367+
}
368+
369+
#[kani::proof]
370+
#[kani::unwind(9)]
371+
fn check_spec_next_chunk_n2_char() {
372+
const MAX_LEN: usize = 8;
373+
let array: [char; MAX_LEN] = kani::any();
374+
let slice = kani::slice::any_slice_of_array(&array);
375+
let mut iter = Copied::new(slice.iter());
376+
let _ = iter.next_chunk::<2>();
377+
}
378+
}

library/core/src/iter/adapters/enumerate.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,59 @@ impl<I: Default> Default for Enumerate<I> {
321321
Enumerate::new(Default::default())
322322
}
323323
}
324+
325+
#[cfg(kani)]
326+
#[unstable(feature = "kani", issue = "none")]
327+
mod verify {
328+
use super::*;
329+
330+
#[kani::proof]
331+
#[kani::unwind(9)]
332+
fn check_enumerate_get_unchecked_u8() {
333+
const MAX_LEN: usize = 8;
334+
let array: [u8; MAX_LEN] = kani::any();
335+
let slice = kani::slice::any_slice_of_array(&array);
336+
let mut iter = Enumerate::new(slice.iter());
337+
let idx: usize = kani::any();
338+
kani::assume(idx < iter.size_hint().0);
339+
let (i, val) = unsafe { iter.__iterator_get_unchecked(idx) };
340+
assert_eq!(i, idx);
341+
assert_eq!(*val, slice[idx]);
342+
}
343+
344+
#[kani::proof]
345+
#[kani::unwind(9)]
346+
fn check_enumerate_get_unchecked_unit() {
347+
const MAX_LEN: usize = 8;
348+
let array: [(); MAX_LEN] = [(); MAX_LEN];
349+
let slice = kani::slice::any_slice_of_array(&array);
350+
let mut iter = Enumerate::new(slice.iter());
351+
let idx: usize = kani::any();
352+
kani::assume(idx < iter.size_hint().0);
353+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
354+
}
355+
356+
#[kani::proof]
357+
#[kani::unwind(9)]
358+
fn check_enumerate_get_unchecked_char() {
359+
const MAX_LEN: usize = 8;
360+
let array: [char; MAX_LEN] = kani::any();
361+
let slice = kani::slice::any_slice_of_array(&array);
362+
let mut iter = Enumerate::new(slice.iter());
363+
let idx: usize = kani::any();
364+
kani::assume(idx < iter.size_hint().0);
365+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
366+
}
367+
368+
#[kani::proof]
369+
#[kani::unwind(9)]
370+
fn check_enumerate_get_unchecked_tup() {
371+
const MAX_LEN: usize = 8;
372+
let array: [(char, u8); MAX_LEN] = kani::any();
373+
let slice = kani::slice::any_slice_of_array(&array);
374+
let mut iter = Enumerate::new(slice.iter());
375+
let idx: usize = kani::any();
376+
kani::assume(idx < iter.size_hint().0);
377+
let _ = unsafe { iter.__iterator_get_unchecked(idx) };
378+
}
379+
}

0 commit comments

Comments
 (0)