-
Notifications
You must be signed in to change notification settings - Fork 65
Challenge 22: Verify safety of str iter functions #572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1613,3 +1613,180 @@ macro_rules! escape_types_impls { | |||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| escape_types_impls!(EscapeDebug, EscapeDefault, EscapeUnicode); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[cfg(kani)] | ||||||||||||||||||||||||||||||||
| #[unstable(feature = "kani", issue = "none")] | ||||||||||||||||||||||||||||||||
| mod verify { | ||||||||||||||||||||||||||||||||
| use super::*; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const MAX_LEN: usize = 4; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Create a symbolic valid UTF-8 string of symbolic length. | ||||||||||||||||||||||||||||||||
| /// Kani explores ALL valid UTF-8 byte patterns exhaustively. | ||||||||||||||||||||||||||||||||
| fn any_str(bytes: &[u8; MAX_LEN]) -> &str { | ||||||||||||||||||||||||||||||||
| let len: usize = kani::any(); | ||||||||||||||||||||||||||||||||
| kani::assume(len <= MAX_LEN); | ||||||||||||||||||||||||||||||||
| kani::assume(crate::str::from_utf8(&bytes[..len]).is_ok()); | ||||||||||||||||||||||||||||||||
| unsafe { crate::str::from_utf8_unchecked(&bytes[..len]) } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // --- Chars (loop-free: next, next_back, as_str are wrappers) --- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||||||||||||
| #[kani::unwind(10)] | ||||||||||||||||||||||||||||||||
| fn verify_chars_next() { | ||||||||||||||||||||||||||||||||
| let bytes: [u8; MAX_LEN] = kani::any(); | ||||||||||||||||||||||||||||||||
| let s = any_str(&bytes); | ||||||||||||||||||||||||||||||||
| let mut chars = s.chars(); | ||||||||||||||||||||||||||||||||
| let _ = chars.next(); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||||||||||||
| #[kani::unwind(10)] | ||||||||||||||||||||||||||||||||
| fn verify_chars_next_back() { | ||||||||||||||||||||||||||||||||
| let bytes: [u8; MAX_LEN] = kani::any(); | ||||||||||||||||||||||||||||||||
| let s = any_str(&bytes); | ||||||||||||||||||||||||||||||||
| let mut chars = s.chars(); | ||||||||||||||||||||||||||||||||
| let _ = chars.next_back(); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||||||||||||
| #[kani::unwind(10)] | ||||||||||||||||||||||||||||||||
| fn verify_chars_as_str() { | ||||||||||||||||||||||||||||||||
| let bytes: [u8; MAX_LEN] = kani::any(); | ||||||||||||||||||||||||||||||||
| let s = any_str(&bytes); | ||||||||||||||||||||||||||||||||
| let mut chars = s.chars(); | ||||||||||||||||||||||||||||||||
| let _ = chars.next(); | ||||||||||||||||||||||||||||||||
| let _ = chars.as_str(); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Note: Chars::advance_by has 3 internal loops that exceed bounded | ||||||||||||||||||||||||||||||||
| // unwind. Truly unbounded verification requires loop invariants on | ||||||||||||||||||||||||||||||||
| // the advance_by implementation — a Kani enhancement opportunity. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // --- SplitInternal: symbolic string, concrete pattern --- | ||||||||||||||||||||||||||||||||
| // Pattern correctness assumed per spec assumption #2 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[kani::proof] | ||||||||||||||||||||||||||||||||
| #[kani::unwind(10)] | ||||||||||||||||||||||||||||||||
| fn verify_split_internal_get_end() { | ||||||||||||||||||||||||||||||||
| let bytes: [u8; MAX_LEN] = kani::any(); | ||||||||||||||||||||||||||||||||
| let s = any_str(&bytes); | ||||||||||||||||||||||||||||||||
| let mut split = s.split(','); | ||||||||||||||||||||||||||||||||
| let _ = split.next(); | ||||||||||||||||||||||||||||||||
| let _ = split.next(); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+1676
to
+1677
|
||||||||||||||||||||||||||||||||
| let _ = split.next(); | |
| let _ = split.next(); | |
| // Drive the iterator to completion to ensure the end-of-iteration | |
| // path (and thus SplitInternal::get_end) is exercised for all inputs. | |
| for _ in 0..(MAX_LEN + 2) { | |
| let _ = split.next(); | |
| } |
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verify_iterator_get_unchecked only calls __iterator_get_unchecked on a fresh Bytes iterator, so it doesn’t cover safety when the iterator has been advanced (where internal pointer/len have changed). Consider nondeterministically advancing b by some n (bounded by s.len()) and then choosing idx < b.len() before calling __iterator_get_unchecked, to exercise more iterator states.
| let idx: usize = kani::any(); | |
| kani::assume(idx < s.len()); | |
| let mut b = s.bytes(); | |
| let mut b = s.bytes(); | |
| // Nondeterministically advance the iterator by some n bounded by s.len() | |
| let n: usize = kani::any(); | |
| kani::assume(n <= s.len()); | |
| for _ in 0..n { | |
| let _ = b.next(); | |
| } | |
| // Now choose an index within the remaining length of the iterator | |
| let idx: usize = kani::any(); | |
| kani::assume(idx < b.len()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR description lists a proof harness for
Chars::advance_by, but this module explicitly skips it (no#[kani::proof]harness is present). Either add a bounded harness (e.g., restrictremainder/string length to keep unwinding finite) or update the PR description/challenge checklist to reflect thatadvance_byis not verified here.