Skip to content
Open
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
73 changes: 40 additions & 33 deletions library/core/src/slice/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ where
R: ops::RangeBounds<usize>,
{
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
Expand All @@ -957,69 +957,74 @@ 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")]
#[inline]
pub(crate) const fn try_into_slice_range(
len: usize,
(start, end): (ops::Bound<usize>, ops::Bound<usize>),
) -> Option<ops::Range<usize>> {
) -> Result<ops::Range<usize>, 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<usize>, ops::Bound<usize>),
bounds: (ops::Bound<usize>, ops::Bound<usize>),
) -> ops::Range<usize> {
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")]
Expand All @@ -1028,13 +1033,13 @@ unsafe impl<T> SliceIndex<[T]> for (ops::Bound<usize>, ops::Bound<usize>) {

#[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]
Expand All @@ -1050,11 +1055,13 @@ unsafe impl<T> SliceIndex<[T]> for (ops::Bound<usize>, ops::Bound<usize>) {
}

#[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)
Expand Down
4 changes: 2 additions & 2 deletions library/core/src/str/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,12 @@ unsafe impl SliceIndex<str> for (ops::Bound<usize>, ops::Bound<usize>) {

#[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]
Expand Down
80 changes: 80 additions & 0 deletions tests/codegen-llvm/bounds-checking/bound-tuple.rs
Original file line number Diff line number Diff line change
@@ -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<usize>) -> &[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<usize>) -> &[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<usize>) -> &[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..)) }
}
Loading