Skip to content

Commit 2605c8d

Browse files
committed
Use niche length type for strlen to guarantee isize::MAX bound
1 parent 76af58f commit 2605c8d

3 files changed

Lines changed: 31 additions & 7 deletions

File tree

library/core/src/ffi/c_str.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::ffi::c_char;
66
use crate::intrinsics::const_eval_select;
77
use crate::iter::FusedIterator;
88
use crate::marker::PhantomData;
9+
use crate::num::niche_types::UsizeNoHighBitMinusOne;
910
use crate::ptr::NonNull;
1011
use crate::slice::memchr;
1112
use crate::{fmt, ops, range, slice, str};
@@ -262,7 +263,12 @@ impl CStr {
262263
// means the call to `from_bytes_with_nul_unchecked` is correct.
263264
//
264265
// The cast from c_char to u8 is ok because a c_char is always one byte.
265-
unsafe { Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr.cast(), len + 1)) }
266+
unsafe {
267+
Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(
268+
ptr.cast(),
269+
len.as_inner() + 1,
270+
))
271+
}
266272
}
267273

268274
/// Creates a C string wrapper from a byte slice with any number of nuls.
@@ -750,9 +756,9 @@ const impl AsRef<CStr> for CStr {
750756
#[inline]
751757
#[unstable(feature = "cstr_internals", issue = "none")]
752758
#[rustc_allow_const_fn_unstable(const_eval_select)]
753-
const unsafe fn strlen(ptr: *const c_char) -> usize {
759+
const unsafe fn strlen(ptr: *const c_char) -> UsizeNoHighBitMinusOne {
754760
const_eval_select!(
755-
@capture { s: *const c_char = ptr } -> usize:
761+
@capture { s: *const c_char = ptr } -> UsizeNoHighBitMinusOne:
756762
if const {
757763
let mut len = 0;
758764

@@ -761,15 +767,16 @@ const unsafe fn strlen(ptr: *const c_char) -> usize {
761767
len += 1;
762768
}
763769

764-
len
770+
UsizeNoHighBitMinusOne::new(len).unwrap()
765771
} else {
766772
unsafe extern "C" {
767773
/// Provided by libc or compiler_builtins.
768774
fn strlen(s: *const c_char) -> usize;
769775
}
770776

771-
// SAFETY: Outer caller has provided a pointer to a valid C string.
772-
unsafe { strlen(s) }
777+
// SAFETY: Outer caller has provided a pointer to a valid C string,
778+
// and its length is within bounds.
779+
unsafe { UsizeNoHighBitMinusOne::new_unchecked(strlen(s)) }
773780
}
774781
)
775782
}
@@ -841,7 +848,7 @@ impl Iterator for Bytes<'_> {
841848
#[inline]
842849
fn count(self) -> usize {
843850
// SAFETY: We always hold a valid pointer to a C string
844-
unsafe { strlen(self.ptr.as_ptr().cast()) }
851+
unsafe { strlen(self.ptr.as_ptr().cast()) }.as_inner()
845852
}
846853
}
847854

library/core/src/num/niche_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ const impl Default for Nanoseconds {
111111
}
112112

113113
const HALF_USIZE: usize = usize::MAX >> 1;
114+
const HALF_USIZE_MINUS_ONE: usize = HALF_USIZE - 1;
114115

115116
define_valid_range_type! {
116117
pub struct NonZeroU8Inner(u8 is 1..);
@@ -126,6 +127,7 @@ define_valid_range_type! {
126127
pub struct NonZeroI128Inner(i128 is ..0 | 1..);
127128

128129
pub struct UsizeNoHighBit(usize is 0..=HALF_USIZE);
130+
pub struct UsizeNoHighBitMinusOne(usize is 0..=HALF_USIZE_MINUS_ONE);
129131
pub struct NonZeroUsizeInner(usize is 1..);
130132
pub struct NonZeroIsizeInner(isize is ..0 | 1..);
131133

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ compile-flags: -Copt-level=3 -Cpanic=abort
2+
3+
#![crate_type = "lib"]
4+
#![feature(cstr_bytes)]
5+
6+
use std::ffi::CStr;
7+
8+
// A `CStr`'s length always fits in an isize after the NUL bit is accounted for
9+
10+
// CHECK-LABEL: @cstr_len_plus_one
11+
#[no_mangle]
12+
pub fn cstr_len_plus_one(s: &CStr) -> bool {
13+
// CHECK: ret i1 true
14+
s.bytes().count() + 1 <= isize::MAX as usize
15+
}

0 commit comments

Comments
 (0)