Verify safety of iterator adapter functions (Challenge 16)#549
Verify safety of iterator adapter functions (Challenge 16)#549kasimte wants to merge 1 commit intomodel-checking:mainfrom
Conversation
68e2629 to
c1620b6
Compare
74 harnesses proving safety of all 10 unsafe functions and 17 safe abstractions listed in Challenge 16, across 13 iterator adapter files. Unbounded verification via large symbolic arrays, loop contracts, and inductive decomposition. 4 representative types (u8, (), char, (char,u8)) cover all behavioral axes of the generic code.
There was a problem hiding this comment.
Pull request overview
Adds Kani-based formal verification coverage for iterator adapter implementations in library/core/src/iter/adapters/, including harnesses and contract/invariant annotations to justify the safety of unsafe internals and safe abstractions.
Changes:
- Add
#[cfg(kani)]verification modules with Kani harnesses across multiple iterator adapters. - Add/extend safety contracts (
#[requires(...)]) and Kani loop-invariant annotations (#[cfg_attr(kani, ...)]) to enable (mostly) unbounded verification. - Document verification assumptions/limitations inline (e.g., bounded unwind in some harnesses).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| library/core/src/iter/adapters/zip.rs | Adds contracts/loop-invariant annotations and extensive Kani harnesses for Zip unsafe/specialized paths. |
| library/core/src/iter/adapters/take.rs | Adds loop-invariant annotations and Kani harnesses for Take specialized paths. |
| library/core/src/iter/adapters/step_by.rs | Adds Kani harnesses exercising original_step via multiple entry points. |
| library/core/src/iter/adapters/skip.rs | Adds Kani harnesses for Skip::__iterator_get_unchecked. |
| library/core/src/iter/adapters/map.rs | Adds Kani harnesses for Map unsafe methods. |
| library/core/src/iter/adapters/map_windows.rs | Adds Kani harnesses targeting buffer initialization/wrap, clone paths, and drop safety. |
| library/core/src/iter/adapters/fuse.rs | Adds Kani harnesses for Fuse::__iterator_get_unchecked. |
| library/core/src/iter/adapters/filter.rs | Adds bounded + inductive-step Kani harnesses for next_chunk-related unsafe operations. |
| library/core/src/iter/adapters/filter_map.rs | Adds bounded + inductive-step Kani harnesses for next_chunk-related unsafe operations. |
| library/core/src/iter/adapters/enumerate.rs | Adds Kani harnesses for Enumerate::__iterator_get_unchecked. |
| library/core/src/iter/adapters/copied.rs | Adds Kani harnesses for Copied::__iterator_get_unchecked and next_chunk specialization. |
| library/core/src/iter/adapters/cloned.rs | Adds a safety contract for next_unchecked and Kani harnesses for unsafe methods. |
| library/core/src/iter/adapters/array_chunks.rs | Adds comments about loop reasoning and Kani harnesses for next_back and bounded fold. |
| @@ -1,4 +1,4 @@ | |||
| use safety::requires; | |||
| use safety::{loop_invariant, requires}; | |||
There was a problem hiding this comment.
loop_invariant is imported from safety but not used anywhere in this module (all loop invariants are written as #[cfg_attr(kani, kani::loop_invariant(...))]). This will trigger an unused_imports warning; either remove the import or switch the attributes to use the imported macro/path consistently.
| use safety::{loop_invariant, requires}; | |
| use safety::requires; |
| } | ||
|
|
||
| #[inline] | ||
| #[requires(self.index + idx < self.a.size() && self.index + idx < self.b.size())] |
There was a problem hiding this comment.
The #[requires(self.index + idx < ...)] contract can overflow on self.index + idx. In debug builds that can panic, and in release it can wrap and make the precondition unsound. Consider rewriting the contract to avoid overflowing arithmetic (e.g., require self.index <= a.size() and idx < a.size() - self.index, similarly for b), or use checked_add in the condition.
| #[requires(self.index + idx < self.a.size() && self.index + idx < self.b.size())] | |
| #[requires( | |
| self.index <= self.a.size() | |
| && idx < self.a.size() - self.index | |
| && self.index <= self.b.size() | |
| && idx < self.b.size() - self.index | |
| )] |
| // Loop invariant: get_unchecked is a read-only operation for | ||
| // TrustedRandomAccessNoCoerce iterators, so sizes are preserved. | ||
| // Loop invariant enables loop contracts for unbounded verification. | ||
| // Safety of get_unchecked(i) is established by the pre-loop state: | ||
| // len = min(a.size(), b.size()) - index, and get_unchecked adds index | ||
| // back, so the actual index is always < both sizes. |
There was a problem hiding this comment.
These comments describe specific loop invariants about sizes and index bounds, but the attached Kani invariant is true (vacuous) and doesn’t actually encode those properties. To avoid misleading future readers, either encode the stated invariant(s) in the attribute expression, or adjust the comment to clarify that the invariant is intentionally vacuous and only used to enable loop-contract mode.
| // Loop invariant: get_unchecked is a read-only operation for | |
| // TrustedRandomAccessNoCoerce iterators, so sizes are preserved. | |
| // Loop invariant enables loop contracts for unbounded verification. | |
| // Safety of get_unchecked(i) is established by the pre-loop state: | |
| // len = min(a.size(), b.size()) - index, and get_unchecked adds index | |
| // back, so the actual index is always < both sizes. | |
| // For TrustedRandomAccessNoCoerce iterators, get_unchecked is a | |
| // read-only operation, so sizes are preserved. | |
| // Safety of get_unchecked(i) is established by the pre-loop state: | |
| // len = min(a.size(), b.size()) - index, and get_unchecked adds index | |
| // back, so the actual index is always < both sizes. | |
| // The Kani loop invariant below is intentionally vacuous (`true`) and | |
| // is used only to enable loop-contract mode, not to encode these | |
| // bounds or size properties directly. |
| use safety::loop_invariant; | ||
|
|
There was a problem hiding this comment.
loop_invariant is imported from safety but the file only uses #[cfg_attr(kani, kani::loop_invariant(...))] attributes. This causes an unused_imports warning; remove the import or use it consistently in the attributes.
| use safety::loop_invariant; |
| // Loop invariant: __iterator_get_unchecked is a read-only operation for | ||
| // TrustedRandomAccess iterators, so iter.size() is preserved across iterations. | ||
| #[cfg_attr(kani, kani::loop_invariant(true))] | ||
| for i in 0..end { |
There was a problem hiding this comment.
The comment claims a loop invariant about iter.size() being preserved, but the Kani invariant provided is true (vacuous) and doesn’t encode that property. Either express the intended invariant in the attribute (so the comment matches what’s checked) or reword the comment to state that the invariant is intentionally trivial and only enables loop-contract verification.
| // Loop invariant: __iterator_get_unchecked is read-only for | ||
| // TrustedRandomAccessNoCoerce iterators, so iter.size() is preserved. | ||
| // Loop invariant: i tracks the consumed element count and stays within |
There was a problem hiding this comment.
These lines are written as “Loop invariant: …”, but there’s no corresponding #[cfg_attr(kani, kani::loop_invariant(...))] annotation on the while loop. If this is meant to be a verified invariant, consider adding the attribute; otherwise, reword as a safety argument (to avoid implying the property is being checked).
| // Loop invariant: __iterator_get_unchecked is read-only for | |
| // TrustedRandomAccessNoCoerce iterators, so iter.size() is preserved. | |
| // Loop invariant: i tracks the consumed element count and stays within | |
| // Safety argument: __iterator_get_unchecked is read-only for | |
| // TrustedRandomAccessNoCoerce iterators, so iter.size() is preserved. | |
| // Safety argument: i tracks the consumed element count and stays within |
Resolves #280 (Challenge 16: Verify the safety of Iterator functions)
74 Kani verification harnesses proving the safety of all 10 unsafe functions and all 17 safe abstractions across 13 iterator adapter files in
library/core/src/iter/adapters/. 26 of 27 functions have unbounded verification coverage (array_chunks::foldis bounded due to CI time limits; see Known limitations).Requirements checklist
#[requires]contracts + 36 harnesses (4 types each)array_chunks::foldis bounded (see below)Tu8,(),char,(char,u8)) covering all behavioral axes (size_of,align_of, ZST, validity constraints, padding)#[requires(...)]on inherent impl methods;kani::assume()for trait impl methods (proc macro limitation)Known limitations
array_chunks::fold: Bounded harness (MAX_LEN=8, unwind 9). A source-code loop invariant (i <= inner_len) was implemented and verified locally, but theTrustedRandomAccessNoCoerce-specialized fold path with__iterator_get_uncheckedconsistently exceeded CI's per-harness timeout under loop contract mode (tested at MAX_LEN 1000, 500, 100, and 10). The unsafe operation —__iterator_get_unchecked(i + local)guarded byinner_len - i >= N— is the same indexed access pattern verified unboundedly via loop contracts intake.rsandzip.rs.next_back_remainder: UsesRange<u8>instead ofslice::Iterbecause CBMC exhausts resources on the pointer-heavy adapter chain. Exercises the sameunwrap_err_uncheckedcode path.Generic type
T: The challenge requires verification without monomorphization. Kani requires concrete types (CBMC operates on concrete GOTO programs), so truly generic proofs are not possible with this tool. The unsafe operations depend onsize_of::<T>()andalign_of::<T>(), not type identity — our 4 types exhaust these behavioral axes. We defer to the committee's judgment on whether this satisfies the requirement.Source code modifications
5 loop invariant annotations added via
#[cfg_attr(kani, kani::loop_invariant(...))]intake.rs(2) andzip.rs(3). Zero impact on non-Kani builds. 10 safety contracts added via#[requires(...)].Verification details (click to expand)
Quick verification
Expected:
Complete - 74 successfully verified harnesses, 0 failures, 74 total.Unbounded verification techniques
kani::slice::any_slice_of_arraywith up to 5000 elements (u8),isize::MAX(ZST), 50 (char/tuple). Slice length is fully symbolic within [0, MAX_LEN].#[cfg_attr(kani, kani::loop_invariant(...))]on 5 loops intake.rsandzip.rs.zip::spec_fold,filter::next_chunk_dropless,filter_map::next_chunk.Safety contracts
__iterator_get_unchecked#[requires(idx < self.it.size_hint().0)]next_unchecked#[requires(self.it.size_hint().0 > 0)]__iterator_get_unchecked#[requires(idx < self.it.size_hint().0)]__iterator_get_unchecked#[requires(idx < self.iter.size_hint().0)]__iterator_get_unchecked#[requires(self.iter.is_some() && idx < self.iter.as_ref().unwrap().size_hint().0)]__iterator_get_unchecked#[requires(idx < self.iter.size_hint().0)]next_unchecked#[requires(self.iter.size_hint().0 > 0)]__iterator_get_unchecked#[requires(idx + self.n < self.iter.size_hint().0)]__iterator_get_unchecked#[requires(idx < self.size_hint().0)]get_unchecked#[requires(self.index + idx < self.a.size() && self.index + idx < self.b.size())]Unsafe functions (10/10)
__iterator_get_uncheckednext_unchecked__iterator_get_unchecked__iterator_get_unchecked__iterator_get_unchecked__iterator_get_uncheckednext_unchecked__iterator_get_unchecked__iterator_get_uncheckedget_uncheckedSafe abstractions (17/17)
next_back_remainderfoldspec_next_chunknext_chunk_droplessnext_chunkas_array_refas_uninit_array_mutpushdroporiginal_stepspec_foldspec_for_eachfoldnextnthnext_backspec_foldTest plan
Resolves #280
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.