diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index 51ab9b5fd1eac..bc0f757a65516 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -934,7 +934,7 @@ where R: ops::RangeBounds, { let len = bounds.end; - try_into_slice_range(len, (range.start_bound().copied(), range.end_bound().copied())) + try_into_slice_range(len, (range.start_bound().copied(), range.end_bound().copied())).ok() } /// Converts a pair of `ops::Bound`s into `ops::Range` without performing any @@ -957,6 +957,24 @@ pub(crate) const fn into_range_unchecked( start..end } +#[derive(Clone, Copy)] +pub(crate) enum RangeError { + EndPastLen { end: usize }, + StartPastEnd { start: usize, end: usize }, +} + +impl RangeError { + #[cfg_attr(not(panic = "immediate-abort"), inline(never), cold)] + #[cfg_attr(panic = "immediate-abort", inline)] + #[track_caller] + const fn report(self, len: usize) -> ! { + match self { + Self::EndPastLen { end } => slice_index_fail(0, end, len), + Self::StartPastEnd { start, end } => slice_index_fail(start, end, len), + } + } +} + /// Converts pair of `ops::Bound`s into `ops::Range`. /// Returns `None` on overflowing indices. #[rustc_const_unstable(feature = "const_range", issue = "none")] @@ -964,62 +982,49 @@ pub(crate) const fn into_range_unchecked( pub(crate) const fn try_into_slice_range( len: usize, (start, end): (ops::Bound, ops::Bound), -) -> Option> { +) -> Result, RangeError> { let end = match end { - ops::Bound::Included(end) if end >= len => return None, + ops::Bound::Included(end) if end >= len => return Err(RangeError::EndPastLen { end }), // Cannot overflow because `end < len` implies `end < usize::MAX`. ops::Bound::Included(end) => end + 1, - ops::Bound::Excluded(end) if end > len => return None, + ops::Bound::Excluded(end) if end > len => return Err(RangeError::EndPastLen { end }), ops::Bound::Excluded(end) => end, ops::Bound::Unbounded => len, }; let start = match start { - ops::Bound::Excluded(start) if start >= end => return None, + ops::Bound::Excluded(start) if start >= end => { + return Err(RangeError::StartPastEnd { start, end }); + } // Cannot overflow because `start < end` implies `start < usize::MAX`. ops::Bound::Excluded(start) => start + 1, - ops::Bound::Included(start) if start > end => return None, + ops::Bound::Included(start) if start > end => { + return Err(RangeError::StartPastEnd { start, end }); + } ops::Bound::Included(start) => start, ops::Bound::Unbounded => 0, }; - Some(start..end) + Ok(start..end) } /// Converts pair of `ops::Bound`s into `ops::Range`. /// Panics on overflowing indices. #[inline] +#[track_caller] +#[rustc_const_unstable(feature = "const_range", issue = "none")] pub(crate) const fn into_slice_range( len: usize, - (start, end): (ops::Bound, ops::Bound), + bounds: (ops::Bound, ops::Bound), ) -> ops::Range { - let end = match end { - ops::Bound::Included(end) if end >= len => slice_index_fail(0, end, len), - // Cannot overflow because `end < len` implies `end < usize::MAX`. - ops::Bound::Included(end) => end + 1, - - ops::Bound::Excluded(end) if end > len => slice_index_fail(0, end, len), - ops::Bound::Excluded(end) => end, - - ops::Bound::Unbounded => len, - }; - - let start = match start { - ops::Bound::Excluded(start) if start >= end => slice_index_fail(start, end, len), - // Cannot overflow because `start < end` implies `start < usize::MAX`. - ops::Bound::Excluded(start) => start + 1, - - ops::Bound::Included(start) if start > end => slice_index_fail(start, end, len), - ops::Bound::Included(start) => start, - - ops::Bound::Unbounded => 0, - }; - - start..end + match try_into_slice_range(len, bounds) { + Ok(range) => range, + Err(e) => e.report(len), + } } #[stable(feature = "slice_index_with_ops_bound_pair", since = "1.53.0")] @@ -1028,13 +1033,13 @@ unsafe impl SliceIndex<[T]> for (ops::Bound, ops::Bound) { #[inline] fn get(self, slice: &[T]) -> Option<&Self::Output> { - try_into_slice_range(slice.len(), self)?.get(slice) + try_into_slice_range(slice.len(), self).ok()?.get(slice) } #[inline] #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut Self::Output> { - try_into_slice_range(slice.len(), self)?.get_mut(slice) + try_into_slice_range(slice.len(), self).ok()?.get_mut(slice) } #[inline] @@ -1050,11 +1055,13 @@ unsafe impl SliceIndex<[T]> for (ops::Bound, ops::Bound) { } #[inline] + #[track_caller] fn index(self, slice: &[T]) -> &Self::Output { into_slice_range(slice.len(), self).index(slice) } #[inline] + #[track_caller] #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut Self::Output { into_slice_range(slice.len(), self).index_mut(slice) diff --git a/library/core/src/str/traits.rs b/library/core/src/str/traits.rs index e4bf752ad2ae6..77fa6fcfbbb3c 100644 --- a/library/core/src/str/traits.rs +++ b/library/core/src/str/traits.rs @@ -367,12 +367,12 @@ unsafe impl SliceIndex for (ops::Bound, ops::Bound) { #[inline] fn get(self, slice: &str) -> Option<&str> { - crate::slice::index::try_into_slice_range(slice.len(), self)?.get(slice) + crate::slice::index::try_into_slice_range(slice.len(), self).ok()?.get(slice) } #[inline] fn get_mut(self, slice: &mut str) -> Option<&mut str> { - crate::slice::index::try_into_slice_range(slice.len(), self)?.get_mut(slice) + crate::slice::index::try_into_slice_range(slice.len(), self).ok()?.get_mut(slice) } #[inline] diff --git a/tests/codegen-llvm/bounds-checking/bound-tuple.rs b/tests/codegen-llvm/bounds-checking/bound-tuple.rs new file mode 100644 index 0000000000000..06decaf3960bf --- /dev/null +++ b/tests/codegen-llvm/bounds-checking/bound-tuple.rs @@ -0,0 +1,80 @@ +//@ compile-flags: -O -Zmerge-functions=disabled +#![crate_type = "lib"] + +use std::collections::Bound; +use std::ops::RangeBounds; + +#[no_mangle] +pub fn raw_index(buf: &[u8]) -> Option<&[u8]> { + // CHECK-LABEL: @raw_index( + // CHECK-NOT: slice_index_fail + // CHECK-NOT: br {{.*}} + if buf.len() < 4 { None } else { Some(&buf[4..]) } +} + +#[no_mangle] +pub fn bounds_indexer(buf: &[u8]) -> Option<&[u8]> { + // CHECK-LABEL: @bounds_indexer( + // CHECK-NOT: slice_index_fail + // CHECK-NOT: br {{.*}} + // CHECK: ret + if buf.len() < 4 { None } else { Some(&buf[(Bound::Included(4), Bound::Unbounded)]) } +} + +#[no_mangle] +pub fn bounds_indexer_with_range_bounds(buf: &[u8]) -> Option<&[u8]> { + // CHECK-LABEL: @bounds_indexer_with_range_bounds( + // CHECK-NOT: slice_index_fail + // CHECK-NOT: br {{.*}} + // CHECK: ret + fn index(buf: &[u8], range: impl RangeBounds) -> &[u8] { + &buf[(range.start_bound().map(|x| *x), range.end_bound().map(|x| *x))] + } + + if buf.len() < 4 { None } else { Some(index(buf, 4..)) } +} + +#[no_mangle] +pub fn bounds_indexer_with_range_bounds_manual_map(buf: &[u8]) -> Option<&[u8]> { + // CHECK-LABEL: @bounds_indexer_with_range_bounds_manual_map( + // CHECK-NOT: slice_index_fail + // CHECK-NOT: br {{.*}} + // CHECK: ret + fn index(buf: &[u8], range: impl RangeBounds) -> &[u8] { + &buf[( + match range.start_bound() { + Bound::Included(&i) => Bound::Included(i), + Bound::Excluded(&i) => Bound::Excluded(i), + Bound::Unbounded => Bound::Unbounded, + }, + match range.end_bound() { + Bound::Included(&i) => Bound::Included(i), + Bound::Excluded(&i) => Bound::Excluded(i), + Bound::Unbounded => Bound::Unbounded, + }, + )] + } + + if buf.len() < 4 { None } else { Some(index(buf, 4..)) } +} + +#[no_mangle] +pub fn bounds_indexer_with_range_bounds_manually_mapped(buf: &[u8]) -> Option<&[u8]> { + // CHECK-LABEL: @bounds_indexer_with_range_bounds_manually_mapped( + // CHECK-NOT: slice_index_fail + // CHECK-NOT: br {{.*}} + // CHECK: ret + fn index(buf: &[u8], range: impl RangeBounds) -> &[u8] { + &buf[match range.start_bound() { + Bound::Included(&i) => i, + Bound::Excluded(i) => i.checked_add(1).expect("overflow"), + Bound::Unbounded => 0, + }..match range.end_bound() { + Bound::Included(&i) => i, + Bound::Excluded(i) => i.checked_sub(1).expect("overflow"), + Bound::Unbounded => buf.len(), + }] + } + + if buf.len() < 4 { None } else { Some(index(buf, 4..)) } +} diff --git a/tests/codegen-llvm/bounds-check-elision-slice-min.rs b/tests/codegen-llvm/bounds-checking/slice-min.rs similarity index 100% rename from tests/codegen-llvm/bounds-check-elision-slice-min.rs rename to tests/codegen-llvm/bounds-checking/slice-min.rs