Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions library/core/src/str/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +1663 to +1665
Copy link

Copilot AI Mar 31, 2026

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., restrict remainder/string length to keep unwinding finite) or update the PR description/challenge checklist to reflect that advance_by is not verified here.

Copilot uses AI. Check for mistakes.

// --- 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
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify_split_internal_get_end doesn’t ensure SplitInternal::get_end is actually exercised for all inputs: if the symbolic string contains enough delimiters, two next() calls may not reach the end-of-iteration path where get_end runs. To truly verify get_end, drive the iterator to completion (e.g., call next() MAX_LEN + 2 times, or add assumptions that force next_match() to return None).

Suggested change
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 uses AI. Check for mistakes.
}

#[kani::proof]
#[kani::unwind(10)]
fn verify_split_internal_next() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut split = s.split(',');
let _ = split.next();
}

#[kani::proof]
#[kani::unwind(10)]
fn verify_split_internal_next_inclusive() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut split = s.split_inclusive(',');
let _ = split.next();
}

#[kani::proof]
#[kani::unwind(10)]
fn verify_split_internal_next_match_back() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut rsplit = s.rsplit(',');
let _ = rsplit.next();
}

#[kani::proof]
#[kani::unwind(10)]
fn verify_split_internal_next_back_inclusive() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut split = s.split_inclusive(',');
let _ = split.next_back();
}

#[kani::proof]
#[kani::unwind(10)]
fn verify_split_internal_remainder() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut split = s.split(',');
let _ = split.next();
let _ = split.remainder();
}

// --- MatchIndicesInternal: symbolic string ---

#[kani::proof]
#[kani::unwind(10)]
fn verify_match_indices_next() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut mi = s.match_indices('a');
let _ = mi.next();
}

#[kani::proof]
#[kani::unwind(10)]
fn verify_match_indices_next_back() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut mi = s.match_indices('a');
let _ = mi.next_back();
}

// --- MatchesInternal: symbolic string ---

#[kani::proof]
#[kani::unwind(10)]
fn verify_matches_next() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut m = s.matches('a');
let _ = m.next();
}

#[kani::proof]
#[kani::unwind(10)]
fn verify_matches_next_back() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut m = s.matches('a');
let _ = m.next_back();
}

// --- SplitAsciiWhitespace: symbolic string ---

#[kani::proof]
#[kani::unwind(10)]
fn verify_split_ascii_whitespace_remainder() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
let mut split = s.split_ascii_whitespace();
let _ = split.next();
let _ = split.remainder();
}

// --- __iterator_get_unchecked: symbolic ---

#[kani::proof]
#[kani::unwind(10)]
fn verify_iterator_get_unchecked() {
let bytes: [u8; MAX_LEN] = kani::any();
let s = any_str(&bytes);
kani::assume(s.len() > 0);
let idx: usize = kani::any();
kani::assume(idx < s.len());
let mut b = s.bytes();
Comment on lines +1786 to +1788
Copy link

Copilot AI Mar 31, 2026

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.

Suggested change
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());

Copilot uses AI. Check for mistakes.
let val = unsafe { b.__iterator_get_unchecked(idx) };
let _ = val;
}
}
Loading