1
0

rust: replace CStr with core::ffi::CStr

`kernel::ffi::CStr` was introduced in commit d126d23801 ("rust: str:
add `CStr` type") in November 2022 as an upstreaming of earlier work
that was done in May 2021[0]. That earlier work, having predated the
inclusion of `CStr` in `core`, largely duplicated the implementation of
`std::ffi::CStr`.

`std::ffi::CStr` was moved to `core::ffi::CStr` in Rust 1.64 in
September 2022. Hence replace `kernel::str::CStr` with `core::ffi::CStr`
to reduce our custom code footprint, and retain needed custom
functionality through an extension trait.

Add `CStr` to `ffi` and the kernel prelude.

Link: faa3cbcca0 [0]
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Acked-by: Danilo Krummrich <dakr@kernel.org>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
Link: https://patch.msgid.link/20251018-cstr-core-v18-16-9378a54385f8@gmail.com
[ Removed assert that would now depend on the Rust version. - Miguel ]
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
Tamir Duberstein
2025-10-18 15:16:37 -04:00
committed by Miguel Ojeda
parent c5cf01ba8d
commit 3b83f5d5e7
13 changed files with 127 additions and 330 deletions

View File

@@ -61,7 +61,7 @@ impl BinderStats {
mod strings { mod strings {
use core::str::from_utf8_unchecked; use core::str::from_utf8_unchecked;
use kernel::str::CStr; use kernel::str::{CStr, CStrExt as _};
extern "C" { extern "C" {
static binder_command_strings: [*const u8; super::BC_COUNT]; static binder_command_strings: [*const u8; super::BC_COUNT];

View File

@@ -46,3 +46,5 @@ alias! {
} }
pub use core::ffi::c_void; pub use core::ffi::c_void;
pub use core::ffi::CStr;

View File

@@ -3,7 +3,7 @@
use crate::debugfs::file_ops::FileOps; use crate::debugfs::file_ops::FileOps;
use crate::ffi::c_void; use crate::ffi::c_void;
use crate::str::CStr; use crate::str::{CStr, CStrExt as _};
use crate::sync::Arc; use crate::sync::Arc;
use core::marker::PhantomData; use core::marker::PhantomData;

View File

@@ -13,6 +13,7 @@ use core::{marker::PhantomData, ptr};
#[cfg(CONFIG_PRINTK)] #[cfg(CONFIG_PRINTK)]
use crate::c_str; use crate::c_str;
use crate::str::CStrExt as _;
pub mod property; pub mod property;

View File

@@ -156,7 +156,9 @@ macro_rules! declare_drm_ioctls {
Some($cmd) Some($cmd)
}, },
flags: $flags, flags: $flags,
name: $crate::c_str!(::core::stringify!($cmd)).as_char_ptr(), name: $crate::str::as_char_ptr_in_const_context(
$crate::c_str!(::core::stringify!($cmd)),
),
} }
),*]; ),*];
ioctls ioctls

View File

@@ -182,6 +182,8 @@ impl Error {
if ptr.is_null() { if ptr.is_null() {
None None
} else { } else {
use crate::str::CStrExt as _;
// SAFETY: The string returned by `errname` is static and `NUL`-terminated. // SAFETY: The string returned by `errname` is static and `NUL`-terminated.
Some(unsafe { CStr::from_char_ptr(ptr) }) Some(unsafe { CStr::from_char_ptr(ptr) })
} }

View File

@@ -4,7 +4,14 @@
//! //!
//! C header: [`include/linux/firmware.h`](srctree/include/linux/firmware.h) //! C header: [`include/linux/firmware.h`](srctree/include/linux/firmware.h)
use crate::{bindings, device::Device, error::Error, error::Result, ffi, str::CStr}; use crate::{
bindings,
device::Device,
error::Error,
error::Result,
ffi,
str::{CStr, CStrExt as _},
};
use core::ptr::NonNull; use core::ptr::NonNull;
/// # Invariants /// # Invariants

View File

@@ -19,7 +19,7 @@ pub use core::{
pub use ::ffi::{ pub use ::ffi::{
c_char, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, c_char, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong,
c_ushort, c_void, c_ushort, c_void, CStr,
}; };
pub use crate::alloc::{flags::*, Box, KBox, KVBox, KVVec, KVec, VBox, VVec, Vec}; pub use crate::alloc::{flags::*, Box, KBox, KVBox, KVVec, KVec, VBox, VVec, Vec};
@@ -43,7 +43,7 @@ pub use super::static_assert;
pub use super::error::{code::*, Error, Result}; pub use super::error::{code::*, Error, Result};
pub use super::{str::CStr, ThisModule}; pub use super::{str::CStrExt as _, ThisModule};
pub use super::init::InPlaceInit; pub use super::init::InPlaceInit;

View File

@@ -4,7 +4,7 @@
//! //!
//! C header: [`include/linux/seq_file.h`](srctree/include/linux/seq_file.h) //! C header: [`include/linux/seq_file.h`](srctree/include/linux/seq_file.h)
use crate::{bindings, c_str, fmt, types::NotThreadSafe, types::Opaque}; use crate::{bindings, c_str, fmt, str::CStrExt as _, types::NotThreadSafe, types::Opaque};
/// A utility for generating the contents of a seq file. /// A utility for generating the contents of a seq file.
#[repr(transparent)] #[repr(transparent)]

View File

@@ -10,9 +10,11 @@ use crate::{
}; };
use core::{ use core::{
marker::PhantomData, marker::PhantomData,
ops::{self, Deref, DerefMut, Index}, ops::{Deref, DerefMut, Index},
}; };
pub use crate::prelude::CStr;
/// Byte string without UTF-8 validity guarantee. /// Byte string without UTF-8 validity guarantee.
#[repr(transparent)] #[repr(transparent)]
pub struct BStr([u8]); pub struct BStr([u8]);
@@ -186,58 +188,17 @@ macro_rules! b_str {
// - error[E0379]: functions in trait impls cannot be declared const // - error[E0379]: functions in trait impls cannot be declared const
#[inline] #[inline]
pub const fn as_char_ptr_in_const_context(c_str: &CStr) -> *const c_char { pub const fn as_char_ptr_in_const_context(c_str: &CStr) -> *const c_char {
c_str.0.as_ptr() c_str.as_ptr().cast()
} }
/// Possible errors when using conversion functions in [`CStr`]. mod private {
#[derive(Debug, Clone, Copy)] pub trait Sealed {}
pub enum CStrConvertError {
/// Supplied bytes contain an interior `NUL`.
InteriorNul,
/// Supplied bytes are not terminated by `NUL`. impl Sealed for super::CStr {}
NotNulTerminated,
}
impl From<CStrConvertError> for Error {
#[inline]
fn from(_: CStrConvertError) -> Error {
EINVAL
}
}
/// A string that is guaranteed to have exactly one `NUL` byte, which is at the
/// end.
///
/// Used for interoperability with kernel APIs that take C strings.
#[repr(transparent)]
pub struct CStr([u8]);
impl CStr {
/// Returns the length of this string excluding `NUL`.
#[inline]
pub const fn len(&self) -> usize {
self.len_with_nul() - 1
}
/// Returns the length of this string with `NUL`.
#[inline]
pub const fn len_with_nul(&self) -> usize {
if self.0.is_empty() {
// SAFETY: This is one of the invariant of `CStr`.
// We add a `unreachable_unchecked` here to hint the optimizer that
// the value returned from this function is non-zero.
unsafe { core::hint::unreachable_unchecked() };
}
self.0.len()
}
/// Returns `true` if the string only includes `NUL`.
#[inline]
pub const fn is_empty(&self) -> bool {
self.len() == 0
} }
/// Extensions to [`CStr`].
pub trait CStrExt: private::Sealed {
/// Wraps a raw C string pointer. /// Wraps a raw C string pointer.
/// ///
/// # Safety /// # Safety
@@ -245,54 +206,9 @@ impl CStr {
/// `ptr` must be a valid pointer to a `NUL`-terminated C string, and it must /// `ptr` must be a valid pointer to a `NUL`-terminated C string, and it must
/// last at least `'a`. When `CStr` is alive, the memory pointed by `ptr` /// last at least `'a`. When `CStr` is alive, the memory pointed by `ptr`
/// must not be mutated. /// must not be mutated.
#[inline] // This function exists to paper over the fact that `CStr::from_ptr` takes a `*const
pub unsafe fn from_char_ptr<'a>(ptr: *const c_char) -> &'a Self { // core::ffi::c_char` rather than a `*const crate::ffi::c_char`.
// SAFETY: The safety precondition guarantees `ptr` is a valid pointer unsafe fn from_char_ptr<'a>(ptr: *const c_char) -> &'a Self;
// to a `NUL`-terminated C string.
let len = unsafe { bindings::strlen(ptr) } + 1;
// SAFETY: Lifetime guaranteed by the safety precondition.
let bytes = unsafe { core::slice::from_raw_parts(ptr.cast(), len) };
// SAFETY: As `len` is returned by `strlen`, `bytes` does not contain interior `NUL`.
// As we have added 1 to `len`, the last byte is known to be `NUL`.
unsafe { Self::from_bytes_with_nul_unchecked(bytes) }
}
/// Creates a [`CStr`] from a `[u8]`.
///
/// The provided slice must be `NUL`-terminated, does not contain any
/// interior `NUL` bytes.
pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, CStrConvertError> {
if bytes.is_empty() {
return Err(CStrConvertError::NotNulTerminated);
}
if bytes[bytes.len() - 1] != 0 {
return Err(CStrConvertError::NotNulTerminated);
}
let mut i = 0;
// `i + 1 < bytes.len()` allows LLVM to optimize away bounds checking,
// while it couldn't optimize away bounds checks for `i < bytes.len() - 1`.
while i + 1 < bytes.len() {
if bytes[i] == 0 {
return Err(CStrConvertError::InteriorNul);
}
i += 1;
}
// SAFETY: We just checked that all properties hold.
Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) })
}
/// Creates a [`CStr`] from a `[u8]` without performing any additional
/// checks.
///
/// # Safety
///
/// `bytes` *must* end with a `NUL` byte, and should only have a single
/// `NUL` byte (or the string will be truncated).
#[inline]
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
// SAFETY: Properties of `bytes` guaranteed by the safety precondition.
unsafe { core::mem::transmute(bytes) }
}
/// Creates a mutable [`CStr`] from a `[u8]` without performing any /// Creates a mutable [`CStr`] from a `[u8]` without performing any
/// additional checks. /// additional checks.
@@ -301,99 +217,16 @@ impl CStr {
/// ///
/// `bytes` *must* end with a `NUL` byte, and should only have a single /// `bytes` *must* end with a `NUL` byte, and should only have a single
/// `NUL` byte (or the string will be truncated). /// `NUL` byte (or the string will be truncated).
#[inline] unsafe fn from_bytes_with_nul_unchecked_mut(bytes: &mut [u8]) -> &mut Self;
pub unsafe fn from_bytes_with_nul_unchecked_mut(bytes: &mut [u8]) -> &mut CStr {
// SAFETY: Properties of `bytes` guaranteed by the safety precondition.
unsafe { &mut *(core::ptr::from_mut(bytes) as *mut CStr) }
}
/// Returns a C pointer to the string. /// Returns a C pointer to the string.
/// // This function exists to paper over the fact that `CStr::as_ptr` returns a `*const
/// Using this function in a const context is deprecated in favor of // core::ffi::c_char` rather than a `*const crate::ffi::c_char`.
/// [`as_char_ptr_in_const_context`] in preparation for replacing `CStr` with `core::ffi::CStr` fn as_char_ptr(&self) -> *const c_char;
/// which does not have this method.
#[inline]
pub const fn as_char_ptr(&self) -> *const c_char {
as_char_ptr_in_const_context(self)
}
/// Convert the string to a byte slice without the trailing `NUL` byte.
#[inline]
pub fn to_bytes(&self) -> &[u8] {
&self.0[..self.len()]
}
/// Convert the string to a byte slice without the trailing `NUL` byte.
///
/// This function is deprecated in favor of [`Self::to_bytes`] in preparation for replacing
/// `CStr` with `core::ffi::CStr` which does not have this method.
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.to_bytes()
}
/// Convert the string to a byte slice containing the trailing `NUL` byte.
#[inline]
pub const fn to_bytes_with_nul(&self) -> &[u8] {
&self.0
}
/// Convert the string to a byte slice containing the trailing `NUL` byte.
///
/// This function is deprecated in favor of [`Self::to_bytes_with_nul`] in preparation for
/// replacing `CStr` with `core::ffi::CStr` which does not have this method.
#[inline]
pub const fn as_bytes_with_nul(&self) -> &[u8] {
self.to_bytes_with_nul()
}
/// Yields a [`&str`] slice if the [`CStr`] contains valid UTF-8.
///
/// If the contents of the [`CStr`] are valid UTF-8 data, this
/// function will return the corresponding [`&str`] slice. Otherwise,
/// it will return an error with details of where UTF-8 validation failed.
///
/// # Examples
///
/// ```
/// # use kernel::str::CStr;
/// let cstr = CStr::from_bytes_with_nul(b"foo\0")?;
/// assert_eq!(cstr.to_str(), Ok("foo"));
/// # Ok::<(), kernel::error::Error>(())
/// ```
#[inline]
pub fn to_str(&self) -> Result<&str, core::str::Utf8Error> {
core::str::from_utf8(self.as_bytes())
}
/// Unsafely convert this [`CStr`] into a [`&str`], without checking for
/// valid UTF-8.
///
/// # Safety
///
/// The contents must be valid UTF-8.
///
/// # Examples
///
/// ```
/// # use kernel::c_str;
/// # use kernel::str::CStr;
/// let bar = c_str!("ツ");
/// // SAFETY: String literals are guaranteed to be valid UTF-8
/// // by the Rust compiler.
/// assert_eq!(unsafe { bar.as_str_unchecked() }, "ツ");
/// ```
#[inline]
pub unsafe fn as_str_unchecked(&self) -> &str {
// SAFETY: TODO.
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
/// Convert this [`CStr`] into a [`CString`] by allocating memory and /// Convert this [`CStr`] into a [`CString`] by allocating memory and
/// copying over the string data. /// copying over the string data.
pub fn to_cstring(&self) -> Result<CString, AllocError> { fn to_cstring(&self) -> Result<CString, AllocError>;
CString::try_from(self)
}
/// Converts this [`CStr`] to its ASCII lower case equivalent in-place. /// Converts this [`CStr`] to its ASCII lower case equivalent in-place.
/// ///
@@ -404,11 +237,7 @@ impl CStr {
/// [`to_ascii_lowercase()`]. /// [`to_ascii_lowercase()`].
/// ///
/// [`to_ascii_lowercase()`]: #method.to_ascii_lowercase /// [`to_ascii_lowercase()`]: #method.to_ascii_lowercase
pub fn make_ascii_lowercase(&mut self) { fn make_ascii_lowercase(&mut self);
// INVARIANT: This doesn't introduce or remove NUL bytes in the C
// string.
self.0.make_ascii_lowercase();
}
/// Converts this [`CStr`] to its ASCII upper case equivalent in-place. /// Converts this [`CStr`] to its ASCII upper case equivalent in-place.
/// ///
@@ -419,11 +248,7 @@ impl CStr {
/// [`to_ascii_uppercase()`]. /// [`to_ascii_uppercase()`].
/// ///
/// [`to_ascii_uppercase()`]: #method.to_ascii_uppercase /// [`to_ascii_uppercase()`]: #method.to_ascii_uppercase
pub fn make_ascii_uppercase(&mut self) { fn make_ascii_uppercase(&mut self);
// INVARIANT: This doesn't introduce or remove NUL bytes in the C
// string.
self.0.make_ascii_uppercase();
}
/// Returns a copy of this [`CString`] where each character is mapped to its /// Returns a copy of this [`CString`] where each character is mapped to its
/// ASCII lower case equivalent. /// ASCII lower case equivalent.
@@ -434,13 +259,7 @@ impl CStr {
/// To lowercase the value in-place, use [`make_ascii_lowercase`]. /// To lowercase the value in-place, use [`make_ascii_lowercase`].
/// ///
/// [`make_ascii_lowercase`]: str::make_ascii_lowercase /// [`make_ascii_lowercase`]: str::make_ascii_lowercase
pub fn to_ascii_lowercase(&self) -> Result<CString, AllocError> { fn to_ascii_lowercase(&self) -> Result<CString, AllocError>;
let mut s = self.to_cstring()?;
s.make_ascii_lowercase();
Ok(s)
}
/// Returns a copy of this [`CString`] where each character is mapped to its /// Returns a copy of this [`CString`] where each character is mapped to its
/// ASCII upper case equivalent. /// ASCII upper case equivalent.
@@ -451,13 +270,7 @@ impl CStr {
/// To uppercase the value in-place, use [`make_ascii_uppercase`]. /// To uppercase the value in-place, use [`make_ascii_uppercase`].
/// ///
/// [`make_ascii_uppercase`]: str::make_ascii_uppercase /// [`make_ascii_uppercase`]: str::make_ascii_uppercase
pub fn to_ascii_uppercase(&self) -> Result<CString, AllocError> { fn to_ascii_uppercase(&self) -> Result<CString, AllocError>;
let mut s = self.to_cstring()?;
s.make_ascii_uppercase();
Ok(s)
}
} }
impl fmt::Display for CStr { impl fmt::Display for CStr {
@@ -490,98 +303,75 @@ impl fmt::Display for CStr {
} }
} }
impl fmt::Debug for CStr { /// Converts a mutable C string to a mutable byte slice.
/// Formats printable ASCII characters with a double quote on either end, escaping the rest.
/// ///
/// ``` /// # Safety
/// # use kernel::c_str;
/// # use kernel::prelude::fmt;
/// # use kernel::str::CStr;
/// # use kernel::str::CString;
/// let penguin = c_str!("🐧");
/// let s = CString::try_from_fmt(fmt!("{penguin:?}"))?;
/// assert_eq!(s.as_bytes_with_nul(), "\"\\xf0\\x9f\\x90\\xa7\"\0".as_bytes());
/// ///
/// // Embedded double quotes are escaped. /// The caller must ensure that the slice ends in a NUL byte and contains no other NUL bytes before
/// let ascii = c_str!("so \"cool\""); /// the borrow ends and the underlying [`CStr`] is used.
/// let s = CString::try_from_fmt(fmt!("{ascii:?}"))?; unsafe fn to_bytes_mut(s: &mut CStr) -> &mut [u8] {
/// assert_eq!(s.as_bytes_with_nul(), "\"so \\\"cool\\\"\"\0".as_bytes()); // SAFETY: the cast from `&CStr` to `&[u8]` is safe since `CStr` has the same layout as `&[u8]`
/// # Ok::<(), kernel::error::Error>(()) // (this is technically not guaranteed, but we rely on it here). The pointer dereference is
/// ``` // safe since it comes from a mutable reference which is guaranteed to be valid for writes.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { unsafe { &mut *(core::ptr::from_mut(s) as *mut [u8]) }
f.write_str("\"")?;
for &c in self.as_bytes() {
match c {
// Printable characters.
b'\"' => f.write_str("\\\"")?,
0x20..=0x7e => f.write_char(c as char)?,
_ => write!(f, "\\x{c:02x}")?,
} }
impl CStrExt for CStr {
#[inline]
unsafe fn from_char_ptr<'a>(ptr: *const c_char) -> &'a Self {
// SAFETY: The safety preconditions are the same as for `CStr::from_ptr`.
unsafe { CStr::from_ptr(ptr.cast()) }
} }
f.write_str("\"")
#[inline]
unsafe fn from_bytes_with_nul_unchecked_mut(bytes: &mut [u8]) -> &mut Self {
// SAFETY: the cast from `&[u8]` to `&CStr` is safe since the properties of `bytes` are
// guaranteed by the safety precondition and `CStr` has the same layout as `&[u8]` (this is
// technically not guaranteed, but we rely on it here). The pointer dereference is safe
// since it comes from a mutable reference which is guaranteed to be valid for writes.
unsafe { &mut *(core::ptr::from_mut(bytes) as *mut CStr) }
}
#[inline]
fn as_char_ptr(&self) -> *const c_char {
self.as_ptr().cast()
}
fn to_cstring(&self) -> Result<CString, AllocError> {
CString::try_from(self)
}
fn make_ascii_lowercase(&mut self) {
// SAFETY: This doesn't introduce or remove NUL bytes in the C string.
unsafe { to_bytes_mut(self) }.make_ascii_lowercase();
}
fn make_ascii_uppercase(&mut self) {
// SAFETY: This doesn't introduce or remove NUL bytes in the C string.
unsafe { to_bytes_mut(self) }.make_ascii_uppercase();
}
fn to_ascii_lowercase(&self) -> Result<CString, AllocError> {
let mut s = self.to_cstring()?;
s.make_ascii_lowercase();
Ok(s)
}
fn to_ascii_uppercase(&self) -> Result<CString, AllocError> {
let mut s = self.to_cstring()?;
s.make_ascii_uppercase();
Ok(s)
} }
} }
impl AsRef<BStr> for CStr { impl AsRef<BStr> for CStr {
#[inline] #[inline]
fn as_ref(&self) -> &BStr { fn as_ref(&self) -> &BStr {
BStr::from_bytes(self.as_bytes()) BStr::from_bytes(self.to_bytes())
}
}
impl Deref for CStr {
type Target = BStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl Index<ops::RangeFrom<usize>> for CStr {
type Output = CStr;
#[inline]
fn index(&self, index: ops::RangeFrom<usize>) -> &Self::Output {
// Delegate bounds checking to slice.
// Assign to _ to mute clippy's unnecessary operation warning.
let _ = &self.as_bytes()[index.start..];
// SAFETY: We just checked the bounds.
unsafe { Self::from_bytes_with_nul_unchecked(&self.0[index.start..]) }
}
}
impl Index<ops::RangeFull> for CStr {
type Output = CStr;
#[inline]
fn index(&self, _index: ops::RangeFull) -> &Self::Output {
self
}
}
mod private {
use core::ops;
// Marker trait for index types that can be forward to `BStr`.
pub trait CStrIndex {}
impl CStrIndex for usize {}
impl CStrIndex for ops::Range<usize> {}
impl CStrIndex for ops::RangeInclusive<usize> {}
impl CStrIndex for ops::RangeToInclusive<usize> {}
}
impl<Idx> Index<Idx> for CStr
where
Idx: private::CStrIndex,
BStr: Index<Idx>,
{
type Output = <BStr as Index<Idx>>::Output;
#[inline]
fn index(&self, index: Idx) -> &Self::Output {
&self.as_ref()[index]
} }
} }
@@ -612,6 +402,13 @@ macro_rules! c_str {
mod tests { mod tests {
use super::*; use super::*;
impl From<core::ffi::FromBytesWithNulError> for Error {
#[inline]
fn from(_: core::ffi::FromBytesWithNulError) -> Error {
EINVAL
}
}
macro_rules! format { macro_rules! format {
($($f:tt)*) => ({ ($($f:tt)*) => ({
CString::try_from_fmt(fmt!($($f)*))?.to_str()? CString::try_from_fmt(fmt!($($f)*))?.to_str()?
@@ -634,40 +431,28 @@ mod tests {
#[test] #[test]
fn test_cstr_to_str() -> Result { fn test_cstr_to_str() -> Result {
let good_bytes = b"\xf0\x9f\xa6\x80\0"; let cstr = c"\xf0\x9f\xa6\x80";
let checked_cstr = CStr::from_bytes_with_nul(good_bytes)?; let checked_str = cstr.to_str()?;
let checked_str = checked_cstr.to_str()?;
assert_eq!(checked_str, "🦀"); assert_eq!(checked_str, "🦀");
Ok(()) Ok(())
} }
#[test] #[test]
fn test_cstr_to_str_invalid_utf8() -> Result { fn test_cstr_to_str_invalid_utf8() -> Result {
let bad_bytes = b"\xc3\x28\0"; let cstr = c"\xc3\x28";
let checked_cstr = CStr::from_bytes_with_nul(bad_bytes)?; assert!(cstr.to_str().is_err());
assert!(checked_cstr.to_str().is_err());
Ok(())
}
#[test]
fn test_cstr_as_str_unchecked() -> Result {
let good_bytes = b"\xf0\x9f\x90\xA7\0";
let checked_cstr = CStr::from_bytes_with_nul(good_bytes)?;
// SAFETY: The contents come from a string literal which contains valid UTF-8.
let unchecked_str = unsafe { checked_cstr.as_str_unchecked() };
assert_eq!(unchecked_str, "🐧");
Ok(()) Ok(())
} }
#[test] #[test]
fn test_cstr_display() -> Result { fn test_cstr_display() -> Result {
let hello_world = CStr::from_bytes_with_nul(b"hello, world!\0")?; let hello_world = c"hello, world!";
assert_eq!(format!("{hello_world}"), "hello, world!"); assert_eq!(format!("{hello_world}"), "hello, world!");
let non_printables = CStr::from_bytes_with_nul(b"\x01\x09\x0a\0")?; let non_printables = c"\x01\x09\x0a";
assert_eq!(format!("{non_printables}"), "\\x01\\x09\\x0a"); assert_eq!(format!("{non_printables}"), "\\x01\\x09\\x0a");
let non_ascii = CStr::from_bytes_with_nul(b"d\xe9j\xe0 vu\0")?; let non_ascii = c"d\xe9j\xe0 vu";
assert_eq!(format!("{non_ascii}"), "d\\xe9j\\xe0 vu"); assert_eq!(format!("{non_ascii}"), "d\\xe9j\\xe0 vu");
let good_bytes = CStr::from_bytes_with_nul(b"\xf0\x9f\xa6\x80\0")?; let good_bytes = c"\xf0\x9f\xa6\x80";
assert_eq!(format!("{good_bytes}"), "\\xf0\\x9f\\xa6\\x80"); assert_eq!(format!("{good_bytes}"), "\\xf0\\x9f\\xa6\\x80");
Ok(()) Ok(())
} }
@@ -686,14 +471,12 @@ mod tests {
#[test] #[test]
fn test_cstr_debug() -> Result { fn test_cstr_debug() -> Result {
let hello_world = CStr::from_bytes_with_nul(b"hello, world!\0")?; let hello_world = c"hello, world!";
assert_eq!(format!("{hello_world:?}"), "\"hello, world!\""); assert_eq!(format!("{hello_world:?}"), "\"hello, world!\"");
let non_printables = CStr::from_bytes_with_nul(b"\x01\x09\x0a\0")?; let non_printables = c"\x01\x09\x0a";
assert_eq!(format!("{non_printables:?}"), "\"\\x01\\x09\\x0a\""); assert_eq!(format!("{non_printables:?}"), "\"\\x01\\t\\n\"");
let non_ascii = CStr::from_bytes_with_nul(b"d\xe9j\xe0 vu\0")?; let non_ascii = c"d\xe9j\xe0 vu";
assert_eq!(format!("{non_ascii:?}"), "\"d\\xe9j\\xe0 vu\""); assert_eq!(format!("{non_ascii:?}"), "\"d\\xe9j\\xe0 vu\"");
let good_bytes = CStr::from_bytes_with_nul(b"\xf0\x9f\xa6\x80\0")?;
assert_eq!(format!("{good_bytes:?}"), "\"\\xf0\\x9f\\xa6\\x80\"");
Ok(()) Ok(())
} }

View File

@@ -8,7 +8,7 @@
use super::{lock::Backend, lock::Guard, LockClassKey}; use super::{lock::Backend, lock::Guard, LockClassKey};
use crate::{ use crate::{
ffi::{c_int, c_long}, ffi::{c_int, c_long},
str::CStr, str::{CStr, CStrExt as _},
task::{ task::{
MAX_SCHEDULE_TIMEOUT, TASK_FREEZABLE, TASK_INTERRUPTIBLE, TASK_NORMAL, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT, TASK_FREEZABLE, TASK_INTERRUPTIBLE, TASK_NORMAL, TASK_UNINTERRUPTIBLE,
}, },

View File

@@ -7,7 +7,7 @@
use super::LockClassKey; use super::LockClassKey;
use crate::{ use crate::{
str::CStr, str::{CStr, CStrExt as _},
types::{NotThreadSafe, Opaque, ScopeGuard}, types::{NotThreadSafe, Opaque, ScopeGuard},
}; };
use core::{cell::UnsafeCell, marker::PhantomPinned, pin::Pin}; use core::{cell::UnsafeCell, marker::PhantomPinned, pin::Pin};

View File

@@ -5,7 +5,7 @@
//! Support for defining statics containing locks. //! Support for defining statics containing locks.
use crate::{ use crate::{
str::CStr, str::{CStr, CStrExt as _},
sync::lock::{Backend, Guard, Lock}, sync::lock::{Backend, Guard, Lock},
sync::{LockClassKey, LockedBy}, sync::{LockClassKey, LockedBy},
types::Opaque, types::Opaque,