diff --git a/library/core/src/str/pattern.rs b/library/core/src/str/pattern.rs index 25202ffd67313..d2a65813bc8b4 100644 --- a/library/core/src/str/pattern.rs +++ b/library/core/src/str/pattern.rs @@ -1494,7 +1494,15 @@ impl TwoWaySearcher { let start = if long_period { self.crit_pos } else { cmp::max(self.crit_pos, self.memory) }; for i in start..needle.len() { - if needle[i] != haystack[self.position + i] { + // SAFETY: on every iteration of `'search`, the `haystack.get(self.position + needle_last)` + // check returned `Some`, so `self.position + needle_last < haystack.len()`. + // Since `i < needle.len()` implies `i <= needle_last`, we have + // `self.position + i < haystack.len()`. + // Every path that mutates `self.position` below either returns or re-enters `'search`, + // which re-runs the check before reaching the loop again. + // `i < needle.len()` also guarantees `needle.get_unchecked(i)` is safe. + if unsafe { *needle.get_unchecked(i) != *haystack.get_unchecked(self.position + i) } + { self.position += i - self.crit_pos + 1; if !long_period { self.memory = 0; @@ -1506,7 +1514,15 @@ impl TwoWaySearcher { // See if the left part of the needle matches let start = if long_period { 0 } else { self.memory }; for i in (start..self.crit_pos).rev() { - if needle[i] != haystack[self.position + i] { + // SAFETY: `crit_pos < needle.len()` by construction, so `i < needle.len()` and + // `needle.get_unchecked(i)` is safe. + // The same `self.position + i < haystack.len()` argument as the right-part + // loop applies: `haystack.get(self.position + needle_last)` at the + // top of `'search` established the bound for this iteration, and every mutation + // of `self.position` is followed by `continue 'search` (which re-runs the check) + // or a `return` on match. + if unsafe { *needle.get_unchecked(i) != *haystack.get_unchecked(self.position + i) } + { self.position += self.period; if !long_period { self.memory = needle.len() - self.period; @@ -1581,7 +1597,18 @@ impl TwoWaySearcher { cmp::min(self.crit_pos_back, self.memory_back) }; for i in (0..crit).rev() { - if needle[i] != haystack[self.end - needle.len() + i] { + // SAFETY: + // - `i < crit <= crit_pos_back <= needle.len()`, so `needle.get_unchecked(i)` is safe. + // - On every iteration of `'search`, `haystack.get(self.end.wrapping_sub(needle.len()))` + // returned `Some`, so `self.end >= needle.len()` and `self.end - needle.len() < haystack.len()`. + // Since `self.end <= haystack.len()` and `i < needle.len()`, we have + // `self.end - needle.len() + i < self.end <= haystack.len()`, so + // `haystack.get_unchecked(self.end - needle.len() + i)` is safe. + // - The path that mutates `self.end` either re-enters `'search`, which re-runs the checks + // before reaching this loop again, or returns on match,so the invariant holds. + if unsafe { + *needle.get_unchecked(i) != *haystack.get_unchecked(self.end - needle.len() + i) + } { self.end -= self.crit_pos_back - i; if !long_period { self.memory_back = needle.len(); @@ -1593,7 +1620,16 @@ impl TwoWaySearcher { // See if the right part of the needle matches let needle_end = if long_period { needle.len() } else { self.memory_back }; for i in self.crit_pos_back..needle_end { - if needle[i] != haystack[self.end - needle.len() + i] { + // SAFETY: `needle_end <= needle.len()`, so `i < needle.len()` and + // `needle.get_unchecked(i)` is safe. + // The same `self.end - needle.len() + i < haystack.len()` argument as the + // left-part loop applies: the `haystack.get(self.end.wrapping_sub(needle.len()))` + // check at the top of `'search` established the bound for this iteration, and + // every mutation of `self.end` is followed by `continue 'search` (which re-runs + // the check) or a `return` (which exits before any further unsafe access). + if unsafe { + *needle.get_unchecked(i) != *haystack.get_unchecked(self.end - needle.len() + i) + } { self.end -= self.period; if !long_period { self.memory_back = self.period;