Skip to content

Commit e2b92ca

Browse files
committed
Verify safety of str iter functions (Challenge 22)
Add 17 Kani verification harnesses for all unsafe operations in library/core/src/str/iter.rs: Chars: next, next_back, advance_by (small + CHUNK_SIZE branch), as_str SplitInternal: get_end, next, next_inclusive, next_back, next_back (terminator path), next_back_inclusive, remainder MatchIndicesInternal: next, next_back MatchesInternal: next, next_back SplitAsciiWhitespace: remainder Bytes: __iterator_get_unchecked (safety contract proof) Techniques: - Symbolic char via kani::any::<char>() with encode_utf8 for full Unicode scalar value coverage (Chars harnesses) - Symbolic ASCII char patterns with 2-byte haystack for Split/Match harnesses covering match and no-match paths - Concrete 33-byte string for advance_by CHUNK_SIZE=32 branch
1 parent 9bbdb30 commit e2b92ca

File tree

1 file changed

+264
-2
lines changed

1 file changed

+264
-2
lines changed

library/core/src/str/iter.rs

Lines changed: 264 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,9 @@ where
10581058
P: Pattern<Searcher<'a>: fmt::Debug>,
10591059
{
10601060
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1061-
f.debug_tuple("MatchIndicesInternal").field(&self.0).finish()
1061+
f.debug_tuple("MatchIndicesInternal")
1062+
.field(&self.0)
1063+
.finish()
10621064
}
10631065
}
10641066

@@ -1435,7 +1437,9 @@ impl<'a, P: Pattern> Iterator for SplitInclusive<'a, P> {
14351437
#[stable(feature = "split_inclusive", since = "1.51.0")]
14361438
impl<'a, P: Pattern<Searcher<'a>: fmt::Debug>> fmt::Debug for SplitInclusive<'a, P> {
14371439
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1438-
f.debug_struct("SplitInclusive").field("0", &self.0).finish()
1440+
f.debug_struct("SplitInclusive")
1441+
.field("0", &self.0)
1442+
.finish()
14391443
}
14401444
}
14411445

@@ -1613,3 +1617,261 @@ macro_rules! escape_types_impls {
16131617
}
16141618

16151619
escape_types_impls!(EscapeDebug, EscapeDefault, EscapeUnicode);
1620+
1621+
#[cfg(kani)]
1622+
#[unstable(feature = "kani", issue = "none")]
1623+
pub mod verify {
1624+
use super::*;
1625+
1626+
// ========== Chars ==========
1627+
1628+
/// Verify safety of Chars::next.
1629+
/// Unsafe ops: next_code_point + char::from_u32_unchecked.
1630+
#[kani::proof]
1631+
#[kani::unwind(5)]
1632+
fn check_chars_next() {
1633+
let c: char = kani::any();
1634+
let mut buf = [0u8; 4];
1635+
let s = c.encode_utf8(&mut buf);
1636+
let mut chars = s.chars();
1637+
let result = chars.next();
1638+
assert_eq!(result, Some(c));
1639+
assert!(chars.next().is_none());
1640+
}
1641+
1642+
/// Verify safety of Chars::next_back.
1643+
/// Unsafe ops: next_code_point_reverse + char::from_u32_unchecked.
1644+
#[kani::proof]
1645+
#[kani::unwind(5)]
1646+
fn check_chars_next_back() {
1647+
let c: char = kani::any();
1648+
let mut buf = [0u8; 4];
1649+
let s = c.encode_utf8(&mut buf);
1650+
let mut chars = s.chars();
1651+
let result = chars.next_back();
1652+
assert_eq!(result, Some(c));
1653+
assert!(chars.next_back().is_none());
1654+
}
1655+
1656+
/// Verify safety of Chars::advance_by (small input, final loop path).
1657+
/// Unsafe ops: self.iter.advance_by(slurp).unwrap_unchecked().
1658+
#[kani::proof]
1659+
#[kani::unwind(5)]
1660+
fn check_chars_advance_by() {
1661+
let c: char = kani::any();
1662+
let mut buf = [0u8; 4];
1663+
let s = c.encode_utf8(&mut buf);
1664+
let mut chars = s.chars();
1665+
let n: usize = kani::any();
1666+
kani::assume(n <= 4);
1667+
let _ = chars.advance_by(n);
1668+
}
1669+
1670+
/// Verify safety of Chars::advance_by (large input, CHUNK_SIZE=32 branch).
1671+
/// Unsafe ops: self.iter.advance_by(bytes_skipped).unwrap_unchecked(),
1672+
/// self.iter.advance_by(1).unwrap_unchecked().
1673+
#[kani::proof]
1674+
#[kani::unwind(34)]
1675+
fn check_chars_advance_by_large() {
1676+
// 33 ASCII bytes exercises the CHUNK_SIZE=32 branch
1677+
let s = "abcdefghijklmnopqrstuvwxyz0123456";
1678+
let mut chars = s.chars();
1679+
let _ = chars.advance_by(33);
1680+
}
1681+
1682+
/// Verify safety of Chars::as_str.
1683+
/// Unsafe ops: from_utf8_unchecked(self.iter.as_slice()).
1684+
#[kani::proof]
1685+
#[kani::unwind(5)]
1686+
fn check_chars_as_str() {
1687+
let c: char = kani::any();
1688+
let mut buf = [0u8; 4];
1689+
let s = c.encode_utf8(&mut buf);
1690+
let len = s.len();
1691+
let mut chars = s.chars();
1692+
let s1 = chars.as_str();
1693+
assert_eq!(s1.len(), len);
1694+
let _ = chars.next();
1695+
let s2 = chars.as_str();
1696+
assert_eq!(s2.len(), 0);
1697+
}
1698+
1699+
// ========== SplitInternal ==========
1700+
1701+
/// Verify safety of SplitInternal::get_end.
1702+
/// Unsafe ops: get_unchecked(self.start..self.end).
1703+
/// Exercised when pattern has no matches in the haystack.
1704+
#[kani::proof]
1705+
#[kani::unwind(5)]
1706+
fn check_split_internal_get_end() {
1707+
let mut split = "ab".split('x');
1708+
let result = split.next(); // no match -> calls get_end
1709+
assert!(result.is_some());
1710+
assert!(split.next().is_none());
1711+
}
1712+
1713+
/// Verify safety of SplitInternal::next.
1714+
/// Unsafe ops: get_unchecked(self.start..a) and get_end fallback.
1715+
#[kani::proof]
1716+
#[kani::unwind(5)]
1717+
fn check_split_internal_next() {
1718+
let c: char = kani::any();
1719+
kani::assume(c.is_ascii());
1720+
let mut split = "ab".split(c);
1721+
let _ = split.next();
1722+
let _ = split.next();
1723+
let _ = split.next();
1724+
}
1725+
1726+
/// Verify safety of SplitInternal::next_inclusive.
1727+
/// Unsafe ops: get_unchecked(self.start..b) and get_end fallback.
1728+
#[kani::proof]
1729+
#[kani::unwind(5)]
1730+
fn check_split_internal_next_inclusive() {
1731+
let c: char = kani::any();
1732+
kani::assume(c.is_ascii());
1733+
let mut split = "ab".split_inclusive(c);
1734+
let _ = split.next();
1735+
let _ = split.next();
1736+
let _ = split.next();
1737+
}
1738+
1739+
/// Verify safety of SplitInternal::next_back.
1740+
/// Unsafe ops: get_unchecked(b..self.end) and get_unchecked(self.start..self.end).
1741+
#[kani::proof]
1742+
#[kani::unwind(5)]
1743+
fn check_split_internal_next_back() {
1744+
let c: char = kani::any();
1745+
kani::assume(c.is_ascii());
1746+
let mut split = "ab".split(c);
1747+
let _ = split.next_back();
1748+
let _ = split.next_back();
1749+
let _ = split.next_back();
1750+
}
1751+
1752+
/// Verify safety of SplitInternal::next_back (allow_trailing_empty=false path).
1753+
/// Exercises the recursive next_back call via split_terminator.
1754+
#[kani::proof]
1755+
#[kani::unwind(5)]
1756+
fn check_split_internal_next_back_terminator() {
1757+
let c: char = kani::any();
1758+
kani::assume(c.is_ascii());
1759+
let mut split = "ab".split_terminator(c);
1760+
let _ = split.next_back();
1761+
let _ = split.next_back();
1762+
let _ = split.next_back();
1763+
}
1764+
1765+
/// Verify safety of SplitInternal::next_back_inclusive.
1766+
/// Unsafe ops: get_unchecked(b..self.end) and get_unchecked(self.start..self.end).
1767+
/// split_inclusive sets allow_trailing_empty=false, exercising the recursive path.
1768+
#[kani::proof]
1769+
#[kani::unwind(5)]
1770+
fn check_split_internal_next_back_inclusive() {
1771+
let c: char = kani::any();
1772+
kani::assume(c.is_ascii());
1773+
let mut split = "ab".split_inclusive(c);
1774+
let _ = split.next_back();
1775+
let _ = split.next_back();
1776+
let _ = split.next_back();
1777+
}
1778+
1779+
/// Verify safety of SplitInternal::remainder.
1780+
/// Unsafe ops: get_unchecked(self.start..self.end).
1781+
#[kani::proof]
1782+
#[kani::unwind(5)]
1783+
fn check_split_internal_remainder() {
1784+
let c: char = kani::any();
1785+
kani::assume(c.is_ascii());
1786+
let mut split = "ab".split(c);
1787+
let _ = split.remainder();
1788+
let _ = split.next();
1789+
let _ = split.remainder();
1790+
}
1791+
1792+
// ========== MatchIndicesInternal ==========
1793+
1794+
/// Verify safety of MatchIndicesInternal::next.
1795+
/// Unsafe ops: get_unchecked(start..end).
1796+
#[kani::proof]
1797+
#[kani::unwind(5)]
1798+
fn check_match_indices_internal_next() {
1799+
let c: char = kani::any();
1800+
kani::assume(c.is_ascii());
1801+
let mut mi = "ab".match_indices(c);
1802+
let _ = mi.next();
1803+
let _ = mi.next();
1804+
let _ = mi.next();
1805+
}
1806+
1807+
/// Verify safety of MatchIndicesInternal::next_back.
1808+
/// Unsafe ops: get_unchecked(start..end).
1809+
#[kani::proof]
1810+
#[kani::unwind(5)]
1811+
fn check_match_indices_internal_next_back() {
1812+
let c: char = kani::any();
1813+
kani::assume(c.is_ascii());
1814+
let mut mi = "ab".match_indices(c);
1815+
let _ = mi.next_back();
1816+
let _ = mi.next_back();
1817+
let _ = mi.next_back();
1818+
}
1819+
1820+
// ========== MatchesInternal ==========
1821+
1822+
/// Verify safety of MatchesInternal::next.
1823+
/// Unsafe ops: get_unchecked(a..b).
1824+
#[kani::proof]
1825+
#[kani::unwind(5)]
1826+
fn check_matches_internal_next() {
1827+
let c: char = kani::any();
1828+
kani::assume(c.is_ascii());
1829+
let mut m = "ab".matches(c);
1830+
let _ = m.next();
1831+
let _ = m.next();
1832+
let _ = m.next();
1833+
}
1834+
1835+
/// Verify safety of MatchesInternal::next_back.
1836+
/// Unsafe ops: get_unchecked(a..b).
1837+
#[kani::proof]
1838+
#[kani::unwind(5)]
1839+
fn check_matches_internal_next_back() {
1840+
let c: char = kani::any();
1841+
kani::assume(c.is_ascii());
1842+
let mut m = "ab".matches(c);
1843+
let _ = m.next_back();
1844+
let _ = m.next_back();
1845+
let _ = m.next_back();
1846+
}
1847+
1848+
// ========== SplitAsciiWhitespace ==========
1849+
1850+
/// Verify safety of SplitAsciiWhitespace::remainder.
1851+
/// Unsafe ops: from_utf8_unchecked(&self.inner.iter.iter.v).
1852+
#[kani::proof]
1853+
#[kani::unwind(5)]
1854+
fn check_split_ascii_whitespace_remainder() {
1855+
let mut split = "a b".split_ascii_whitespace();
1856+
let _ = split.remainder();
1857+
let _ = split.next();
1858+
let _ = split.remainder();
1859+
let _ = split.next();
1860+
let _ = split.remainder();
1861+
}
1862+
1863+
// ========== Bytes ==========
1864+
1865+
/// Verify safety contract of Bytes::__iterator_get_unchecked.
1866+
/// Contract: #[requires(idx < self.0.len())].
1867+
#[kani::proof]
1868+
fn check_bytes_iterator_get_unchecked() {
1869+
let s = "abcd";
1870+
let mut bytes = s.bytes();
1871+
let idx: usize = kani::any();
1872+
kani::assume(idx < bytes.len());
1873+
unsafe {
1874+
let _ = bytes.__iterator_get_unchecked(idx);
1875+
}
1876+
}
1877+
}

0 commit comments

Comments
 (0)