Skip to main content

relibc/
c_str.rs

1//! Nul-terminated byte strings.
2
3use core::{marker::PhantomData, ptr::NonNull, str::Utf8Error};
4
5use alloc::{borrow::Cow, string::String};
6
7use crate::platform::types::{c_char, wchar_t};
8
9mod private {
10    pub trait Sealed {}
11}
12#[derive(Clone, Copy, Debug)]
13pub enum Thin {}
14
15#[derive(Clone, Copy, Debug)]
16pub enum Wide {}
17
18impl private::Sealed for Thin {}
19impl private::Sealed for Wide {}
20
21pub trait Kind: private::Sealed + Copy + 'static {
22    /// c_char or wchar_t
23    type C: Copy + 'static;
24    // u8 or u32
25    type Char: Copy + From<u8> + Into<u32> + PartialEq + 'static;
26
27    const NUL: Self::Char;
28
29    const IS_THIN_NOT_WIDE: bool;
30
31    fn r2c(c: Self::Char) -> Self::C;
32    fn c2r(c: Self::C) -> Self::Char;
33
34    fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]>;
35    fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]>;
36
37    unsafe fn strlen(s: *const Self::C) -> usize;
38    unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C;
39    unsafe fn strchrnul(s: *const Self::C, c: Self::C) -> *const Self::C;
40}
41impl Kind for Thin {
42    type C = c_char;
43    type Char = u8;
44
45    const NUL: Self::Char = 0;
46    const IS_THIN_NOT_WIDE: bool = true;
47
48    unsafe fn strlen(s: *const c_char) -> usize {
49        unsafe { crate::header::string::strlen(s) }
50    }
51    unsafe fn strchr(s: *const c_char, c: c_char) -> *const c_char {
52        unsafe { crate::header::string::strchr(s, c.into()) }
53    }
54    unsafe fn strchrnul(s: *const c_char, c: c_char) -> *const c_char {
55        unsafe { crate::header::string::strchrnul(s, c.into()) }
56    }
57    fn r2c(c: u8) -> c_char {
58        c as _
59    }
60    fn c2r(c: c_char) -> u8 {
61        c as _
62    }
63    fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> {
64        Some(b)
65    }
66    fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> {
67        Some(c)
68    }
69}
70impl Kind for Wide {
71    type C = wchar_t;
72    type Char = u32;
73
74    const NUL: Self::Char = 0;
75    const IS_THIN_NOT_WIDE: bool = false;
76
77    unsafe fn strlen(s: *const Self::C) -> usize {
78        unsafe { crate::header::wchar::wcslen(s) }
79    }
80    unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C {
81        unsafe { crate::header::wchar::wcschr(s, c) }
82    }
83    unsafe fn strchrnul(mut s: *const Self::C, c: Self::C) -> *const Self::C {
84        // TODO: optimized function
85        while unsafe { s.read() } != c && unsafe { s.read() } != 0 {
86            s = unsafe { s.add(1) };
87        }
88        s
89    }
90    fn r2c(c: Self::Char) -> Self::C {
91        c as _
92    }
93    fn c2r(c: Self::C) -> Self::Char {
94        c as _
95    }
96    fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> {
97        None
98    }
99    fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> {
100        None
101    }
102}
103
104/// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`.
105#[derive(Clone, Copy)]
106#[repr(transparent)]
107pub struct NulStr<'a, T: Kind> {
108    ptr: NonNull<T::C>,
109    _marker: PhantomData<&'a [u8]>,
110}
111pub type CStr<'a> = NulStr<'a, Thin>;
112pub type WStr<'a> = NulStr<'a, Wide>;
113
114impl<'a, T: Kind> NulStr<'a, T> {
115    /// Safety
116    ///
117    /// The ptr must be valid up to and including the first NUL byte from the base ptr.
118    pub const unsafe fn from_ptr(ptr: *const T::C) -> Self {
119        Self {
120            ptr: unsafe { NonNull::new_unchecked(ptr.cast_mut()) },
121            _marker: PhantomData,
122        }
123    }
124    pub unsafe fn from_nullable_ptr(ptr: *const T::C) -> Option<Self> {
125        if ptr.is_null() {
126            None
127        } else {
128            Some(unsafe { Self::from_ptr(ptr) })
129        }
130    }
131    /// Look for the closest occurence of `c`, and if found, split the string into a slice up to
132    /// that byte and a `CStr` starting at that byte.
133    #[inline]
134    #[doc(alias = "strchrnul")]
135    pub fn find_get_subslice_or_all(
136        self,
137        c: impl Into<T::Char>,
138    ) -> Result<(&'a [T::Char], Self), (&'a [T::Char], Self)> {
139        let c = c.into();
140
141        // SAFETY: strchrnul expects self.as_ptr() to be valid up to and including its last NUL
142        // byte
143        let found = unsafe { T::strchrnul(self.as_ptr(), T::r2c(c)) };
144
145        // SAFETY: the pointer returned from strchrnul is always a substring of this string, and
146        // hence always valid as a CStr.
147        let found = unsafe { Self::from_ptr(found) };
148        let until = unsafe { self.slice_until_substr(found) };
149
150        if found.first() == T::NUL {
151            // The character was not found, and we got the end of the string instead.
152            Err((until, found))
153        } else {
154            Ok((until, found))
155        }
156    }
157    /// # Safety
158    ///
159    /// `substr` must be contained within `self`
160    #[inline]
161    pub unsafe fn slice_until_substr(self, substr: NulStr<'_, T>) -> &'a [T::Char] {
162        let index = unsafe {
163            // SAFETY: the sub-pointer as returned by strchr must be derived from the same
164            // allocation
165            substr.as_ptr().offset_from(self.as_ptr()) as usize
166        };
167        unsafe { core::slice::from_raw_parts(self.as_ptr().cast::<T::Char>(), index) }
168    }
169    /// Look for the closest occurence of `c`, and if found, split the string into a slice up to
170    /// that byte and a `CStr` starting at that byte.
171    #[inline]
172    pub fn find_get_subslice(self, c: T::Char) -> Option<(&'a [T::Char], Self)> {
173        let rest = self.find(c)?;
174
175        // SAFETY: the output of strchr is obviously a substring if it doesn't return NULL
176        Some((unsafe { self.slice_until_substr(rest) }, rest))
177    }
178    /// Look for the closest occurence of `c`, and return a new string starting at that byte if
179    /// found.
180    #[doc(alias = "strchr")]
181    #[doc(alias = "wcschr")]
182    #[inline]
183    pub fn find(self, c: T::Char) -> Option<Self> {
184        unsafe {
185            // SAFETY: the only requirement is for self.as_ptr() to be valid up to and including
186            // the nearest NUL byte, which this type requires
187            let ret = T::strchr(self.as_ptr(), T::r2c(c));
188            // SAFETY: strchr must either return NULL (not found) or a substring of self, which can
189            // never exceed the nearest NUL byte of self
190            Self::from_nullable_ptr(ret)
191        }
192    }
193    // TODO: strrchr, strchrnul wrappers
194
195    #[inline]
196    pub fn contains(self, c: T::Char) -> bool {
197        self.find(c).is_some()
198    }
199    #[inline]
200    pub fn first(self) -> T::Char {
201        unsafe {
202            // SAFETY: Self must be valid up to and including its nearest NUL byte, which certainly
203            // implies its readable length is nonzero (string is empty if this first byte is 0).
204            T::c2r(self.ptr.read())
205        }
206    }
207    #[inline]
208    pub fn first_char(self) -> Option<char> {
209        char::from_u32(self.first().into())
210    }
211    /// Same as `split_first` except also requires that the first char be convertible into `char`
212    #[inline]
213    pub fn split_first_char(self) -> Option<(char, Self)> {
214        self.split_first()
215            .and_then(|(c, r)| Some((char::from_u32(c.into())?, r)))
216    }
217    /// Split this string into `Some((first_byte, string_after_that))` or `None` if empty.
218    #[inline]
219    pub fn split_first(self) -> Option<(T::Char, Self)> {
220        if self.first() == T::NUL {
221            return None;
222        }
223        Some((self.first(), unsafe {
224            Self::from_ptr(self.as_ptr().add(1))
225        }))
226    }
227    pub fn to_chars_with_nul(self) -> &'a [T::Char] {
228        unsafe {
229            // SAFETY: The string must be valid at least until (and including) the NUL byte.
230            let len = T::strlen(self.ptr.as_ptr());
231            core::slice::from_raw_parts(self.ptr.as_ptr().cast(), len + 1)
232        }
233    }
234    pub fn to_chars(self) -> &'a [T::Char] {
235        let s = self.to_chars_with_nul();
236        &s[..s.len() - 1]
237    }
238    pub const fn as_ptr(self) -> *const T::C {
239        self.ptr.as_ptr()
240    }
241    pub const unsafe fn from_chars_with_nul_unchecked(chars: &'a [T::Char]) -> Self {
242        unsafe { Self::from_ptr(chars.as_ptr().cast()) }
243    }
244    pub fn from_chars_with_nul(chars: &'a [T::Char]) -> Result<Self, FromCharsWithNulError> {
245        if chars.last() != Some(&T::NUL) || chars[..chars.len() - 1].contains(&T::NUL) {
246            return Err(FromCharsWithNulError);
247        }
248
249        Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) })
250    }
251    pub fn from_chars_until_nul(chars: &'a [T::Char]) -> Result<Self, FromCharsUntilNulError> {
252        if !chars.contains(&T::NUL) {
253            return Err(FromCharsUntilNulError);
254        }
255
256        Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) })
257    }
258    /// Scan the string to get its length.
259    #[doc(alias = "strlen")]
260    #[doc(alias = "wcslen")]
261    pub fn len(self) -> usize {
262        self.to_chars().len()
263    }
264    #[inline]
265    pub fn is_empty(&self) -> bool {
266        self.first() == T::NUL
267    }
268}
269impl<'a> CStr<'a> {
270    pub fn to_owned_cstring(self) -> CString {
271        CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) })
272    }
273    pub fn borrow(string: &'a CString) -> Self {
274        unsafe { Self::from_ptr(string.as_ptr()) }
275    }
276    #[inline]
277    pub fn to_bytes(self) -> &'a [u8] {
278        self.to_chars()
279    }
280    #[inline]
281    pub fn to_bytes_with_nul(self) -> &'a [u8] {
282        self.to_chars_with_nul()
283    }
284    pub fn to_str(self) -> Result<&'a str, Utf8Error> {
285        core::str::from_utf8(self.to_bytes())
286    }
287    pub fn to_string_lossy(self) -> Cow<'a, str> {
288        String::from_utf8_lossy(self.to_bytes())
289    }
290    #[inline]
291    pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self {
292        unsafe { Self::from_chars_with_nul_unchecked(bytes) }
293    }
294    #[inline]
295    pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result<Self, FromCharsWithNulError> {
296        Self::from_chars_with_nul(bytes)
297    }
298    #[inline]
299    pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result<Self, FromCharsUntilNulError> {
300        Self::from_chars_until_nul(bytes)
301    }
302}
303
304unsafe impl<T: Kind> Send for NulStr<'_, T> {}
305unsafe impl<T: Kind> Sync for NulStr<'_, T> {}
306
307impl From<&core::ffi::CStr> for CStr<'_> {
308    fn from(s: &core::ffi::CStr) -> Self {
309        // SAFETY:
310        // * We can assume that `s` is valid because the caller should have upheld its
311        // safety concerns when constructing it.
312        unsafe { Self::from_ptr(s.as_ptr()) }
313    }
314}
315
316#[derive(Debug)]
317pub struct FromCharsWithNulError;
318
319#[derive(Debug)]
320pub struct FromCharsUntilNulError;
321
322pub use alloc::ffi::CString;