Skip to content
Closed
Show file tree
Hide file tree
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
14 changes: 12 additions & 2 deletions library/core/src/str/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1494,7 +1494,12 @@ impl str {
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
pub fn find<P: Pattern>(&self, pat: P) -> Option<usize> {
pat.into_searcher(self).next_match().map(|(i, _)| i)
let result = pat.into_searcher(self).next_match().map(|(i, _)| i);
if let Some(index) = result {
// SAFETY: `Searcher` implementations must return ranges within the haystack.
unsafe { assert_unchecked(index <= self.len()) };
}
result
}

/// Returns the byte index for the first character of the last match of the pattern in
Expand Down Expand Up @@ -1543,7 +1548,12 @@ impl str {
where
for<'a> P::Searcher<'a>: ReverseSearcher<'a>,
{
pat.into_searcher(self).next_match_back().map(|(i, _)| i)
let result = pat.into_searcher(self).next_match_back().map(|(i, _)| i);
if let Some(index) = result {
// SAFETY: `Searcher` implementations must return ranges within the haystack.
unsafe { assert_unchecked(index <= self.len()) };
}
result
}

/// Returns an iterator over substrings of this string slice, separated by
Expand Down
16 changes: 14 additions & 2 deletions library/core/src/str/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ unsafe impl<'a> Searcher<'a> for CharSearcher<'a> {
let found_char = self.finger - self.utf8_size();
if let Some(slice) = self.haystack.as_bytes().get(found_char..self.finger) {
if slice == &self.utf8_encoded[0..self.utf8_size()] {
// SAFETY: `slice` is a nonempty UTF-8 encoding found in the haystack.
unsafe {
crate::hint::assert_unchecked(found_char < self.haystack.len())
};
return Some((found_char, self.finger));
}
}
Expand Down Expand Up @@ -521,6 +525,8 @@ unsafe impl<'a> ReverseSearcher<'a> for CharSearcher<'a> {
let found_char = index - shift;
if let Some(slice) = haystack.get(found_char..(found_char + self.utf8_size())) {
if slice == &self.utf8_encoded[0..self.utf8_size()] {
// SAFETY: `slice` is a nonempty UTF-8 encoding found in the haystack.
unsafe { crate::hint::assert_unchecked(found_char < haystack.len()) };
// move finger to before the character found (i.e., at its start index)
self.finger_back = found_char;
return Some((self.finger_back, self.finger_back + self.utf8_size()));
Expand Down Expand Up @@ -784,7 +790,10 @@ macro_rules! searcher_methods {
}
#[inline]
fn next_match(&mut self) -> Option<(usize, usize)> {
self.0.next_match()
let (start, end) = self.0.next_match()?;
// SAFETY: these searchers only match nonempty chars in the haystack.
unsafe { crate::hint::assert_unchecked(start < self.0.haystack.len()) };
Some((start, end))
}
#[inline]
fn next_reject(&mut self) -> Option<(usize, usize)> {
Expand All @@ -798,7 +807,10 @@ macro_rules! searcher_methods {
}
#[inline]
fn next_match_back(&mut self) -> Option<(usize, usize)> {
self.0.next_match_back()
let (start, end) = self.0.next_match_back()?;
// SAFETY: these searchers only match nonempty chars in the haystack.
unsafe { crate::hint::assert_unchecked(start < self.0.haystack.len()) };
Some((start, end))
}
#[inline]
fn next_reject_back(&mut self) -> Option<(usize, usize)> {
Expand Down
36 changes: 36 additions & 0 deletions tests/codegen-llvm/lib-optimizations/str-find-result-bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//@ compile-flags: -Copt-level=3

#![crate_type = "lib"]

// Make sure no bounds checks are emitted when slicing with an index returned
// by `str::find` or `str::rfind`.

// CHECK-LABEL: @find_str_prefix_no_bounds_check
#[no_mangle]
pub fn find_str_prefix_no_bounds_check<'a>(haystack: &'a str, needle: &str) -> &'a [u8] {
// CHECK-NOT: slice_index_fail
match haystack.find(needle) {
Some(index) => &haystack.as_bytes()[..index],
None => haystack.as_bytes(),
}
}

// CHECK-LABEL: @rfind_char_suffix_no_bounds_check
#[no_mangle]
pub fn rfind_char_suffix_no_bounds_check(haystack: &str, needle: char) -> &[u8] {
// CHECK-NOT: slice_index_fail
match haystack.rfind(needle) {
Some(index) => &haystack.as_bytes()[index..],
None => haystack.as_bytes(),
}
}

// CHECK-LABEL: @rfind_str_suffix_no_bounds_check
#[no_mangle]
pub fn rfind_str_suffix_no_bounds_check<'a>(haystack: &'a str, needle: &str) -> &'a [u8] {
// CHECK-NOT: slice_index_fail
match haystack.rfind(needle) {
Some(index) => &haystack.as_bytes()[index..],
None => haystack.as_bytes(),
}
}
43 changes: 43 additions & 0 deletions tests/codegen-llvm/str-find-index-no-bound-check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//@ compile-flags: -Copt-level=3 -C panic=abort
#![crate_type = "lib"]
#![no_std]

// A successful search for a `char` always returns the start of a nonempty
// match, so its byte index is valid for the original string.

// Verify that the check would be visible in the generated IR.

// CHECK-LABEL: @bounds_check_is_visible
#[no_mangle]
pub fn bounds_check_is_visible(s: &str, index: usize) -> u8 {
// CHECK: call{{.*}}panic_bounds_check
s.as_bytes()[index]
}

// CHECK-LABEL: @find_char_index_no_bounds_check
#[no_mangle]
pub fn find_char_index_no_bounds_check(s: &str, needle: char) -> u8 {
// CHECK-NOT: call{{.*}}panic_bounds_check
if let Some(index) = s.find(needle) { s.as_bytes()[index] } else { 0 }
}

// CHECK-LABEL: @find_predicate_index_no_bounds_check
#[no_mangle]
pub fn find_predicate_index_no_bounds_check(s: &str, needle: char) -> u8 {
// CHECK-NOT: call{{.*}}panic_bounds_check
if let Some(index) = s.find(|c| c == needle) { s.as_bytes()[index] } else { 0 }
}

// CHECK-LABEL: @rfind_char_index_no_bounds_check
#[no_mangle]
pub fn rfind_char_index_no_bounds_check(s: &str, needle: char) -> u8 {
// CHECK-NOT: call{{.*}}panic_bounds_check
if let Some(index) = s.rfind(needle) { s.as_bytes()[index] } else { 0 }
}

// CHECK-LABEL: @rfind_predicate_index_no_bounds_check
#[no_mangle]
pub fn rfind_predicate_index_no_bounds_check(s: &str, needle: char) -> u8 {
// CHECK-NOT: call{{.*}}panic_bounds_check
if let Some(index) = s.rfind(|c| c == needle) { s.as_bytes()[index] } else { 0 }
}
Loading