Skip to content

Commit 792bc5a

Browse files
committed
Auto merge of #99977 - BlackHoleFox:cfte-cstr, r=thomcc
Add validation to const fn CStr creation Improves upon the existing validation that only worked when building the stdlib from source. `CStr::from_bytes_with_nul_unchecked` now utilizes `const_eval_select` to validate the safety requirements of the function when used as `const FOO: &CStr = CStr::from_bytes_with_nul_unchecked(b"Foobar\0");`. This can help catch erroneous code written by accident and, assuming a new enough `rustc` in use, remove the need for boilerplate safety comments for this function in `const` contexts. ~~I think this might need a UI test, but I don't know where to put it. If this is a worth change, a perf run would be nice to make sure the `O(n)` validation isn't too bad. I didn't notice a difference building the stdlib tests locally.~~
2 parents ca37a45 + 0e54d71 commit 792bc5a

File tree

1 file changed

+34
-13
lines changed

1 file changed

+34
-13
lines changed

library/core/src/ffi/c_str.rs

+34-13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::ascii;
22
use crate::cmp::Ordering;
33
use crate::ffi::c_char;
44
use crate::fmt::{self, Write};
5+
use crate::intrinsics;
56
use crate::ops;
67
use crate::slice;
78
use crate::slice::memchr;
@@ -384,21 +385,41 @@ impl CStr {
384385
#[must_use]
385386
#[stable(feature = "cstr_from_bytes", since = "1.10.0")]
386387
#[rustc_const_stable(feature = "const_cstr_unchecked", since = "1.59.0")]
388+
#[rustc_allow_const_fn_unstable(const_eval_select)]
387389
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
388-
// We're in a const fn, so this is the best we can do
389-
debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);
390-
// SAFETY: Calling an inner function with the same prerequisites.
391-
unsafe { Self::_from_bytes_with_nul_unchecked(bytes) }
392-
}
390+
fn rt_impl(bytes: &[u8]) -> &CStr {
391+
// Chance at catching some UB at runtime with debug builds.
392+
debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);
393393

394-
#[inline]
395-
const unsafe fn _from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
396-
// SAFETY: Casting to CStr is safe because its internal representation
397-
// is a [u8] too (safe only inside std).
398-
// Dereferencing the obtained pointer is safe because it comes from a
399-
// reference. Making a reference is then safe because its lifetime
400-
// is bound by the lifetime of the given `bytes`.
401-
unsafe { &*(bytes as *const [u8] as *const CStr) }
394+
// SAFETY: Casting to CStr is safe because its internal representation
395+
// is a [u8] too (safe only inside std).
396+
// Dereferencing the obtained pointer is safe because it comes from a
397+
// reference. Making a reference is then safe because its lifetime
398+
// is bound by the lifetime of the given `bytes`.
399+
unsafe { &*(bytes as *const [u8] as *const CStr) }
400+
}
401+
402+
const fn const_impl(bytes: &[u8]) -> &CStr {
403+
// Saturating so that an empty slice panics in the assert with a good
404+
// message, not here due to underflow.
405+
let mut i = bytes.len().saturating_sub(1);
406+
assert!(!bytes.is_empty() && bytes[i] == 0, "input was not nul-terminated");
407+
408+
// Ending null byte exists, skip to the rest.
409+
while i != 0 {
410+
i -= 1;
411+
let byte = bytes[i];
412+
assert!(byte != 0, "input contained interior nul");
413+
}
414+
415+
// SAFETY: See `rt_impl` cast.
416+
unsafe { &*(bytes as *const [u8] as *const CStr) }
417+
}
418+
419+
// SAFETY: The const and runtime versions have identical behavior
420+
// unless the safety contract of `from_bytes_with_nul_unchecked` is
421+
// violated, which is UB.
422+
unsafe { intrinsics::const_eval_select((bytes,), const_impl, rt_impl) }
402423
}
403424

404425
/// Returns the inner pointer to this C string.

0 commit comments

Comments
 (0)