|
| 1 | +//! Work around lack of `core::ffi::CStr` prior to Rust 1.64, and the lack of |
| 2 | +//! `const fn` support for `CStr` in later versions. |
| 3 | +
|
| 4 | +// TODO(MSRV 1.64): Use `core::ffi::c_char`. |
| 5 | +use libc::c_char; |
| 6 | + |
| 7 | +// TODO(MSRV 1.64): Replace with `&core::ffi::CStr`. |
| 8 | +pub struct Ref(&'static [u8]); |
| 9 | + |
| 10 | +impl Ref { |
| 11 | + #[inline(always)] |
| 12 | + pub fn as_ptr(&self) -> *const c_char { |
| 13 | + const _SAME_ALIGNMENT: () = |
| 14 | + assert!(core::mem::align_of::<u8>() == core::mem::align_of::<c_char>()); |
| 15 | + const _SAME_SIZE: () = |
| 16 | + assert!(core::mem::size_of::<u8>() == core::mem::size_of::<c_char>()); |
| 17 | + |
| 18 | + // It is safe to cast a `*const u8` to a `const c_char` as they are the |
| 19 | + // same size and alignment. |
| 20 | + self.0.as_ptr().cast() |
| 21 | + } |
| 22 | + |
| 23 | + // SAFETY: Same as `CStr::from_bytes_with_nul_unchecked`. |
| 24 | + const unsafe fn from_bytes_with_nul_unchecked(value: &'static [u8]) -> Self { |
| 25 | + Self(value) |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +pub const fn unwrap_const_from_bytes_with_nul(value: &'static [u8]) -> Ref { |
| 30 | + // XXX: We cannot use `unwrap_const` since `Ref`/`CStr` is not `Copy`. |
| 31 | + match const_from_bytes_with_nul(value) { |
| 32 | + Some(r) => r, |
| 33 | + None => panic!("const_from_bytes_with_nul failed"), |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +// TODO(MSRV 1.72): Replace with `CStr::from_bytes_with_nul`. |
| 38 | +#[inline(always)] |
| 39 | +const fn const_from_bytes_with_nul(value: &'static [u8]) -> Option<Ref> { |
| 40 | + const fn const_contains(mut value: &[u8], needle: &u8) -> bool { |
| 41 | + while let [head, tail @ ..] = value { |
| 42 | + if *head == *needle { |
| 43 | + return true; |
| 44 | + } |
| 45 | + value = tail; |
| 46 | + } |
| 47 | + false |
| 48 | + } |
| 49 | + |
| 50 | + // TODO(MSRV 1.69): Use `core::ffi::CStr::from_bytes_until_nul` |
| 51 | + match value { |
| 52 | + [before_nul @ .., 0] if !const_contains(before_nul, &0) => { |
| 53 | + // SAFETY: |
| 54 | + // * `value` is nul-terminated according to the slice pattern. |
| 55 | + // * `value` doesn't contain any interior null, by the guard. |
| 56 | + // TODO(MSRV 1.64): Use `CStr::from_bytes_with_nul_unchecked` |
| 57 | + Some(unsafe { Ref::from_bytes_with_nul_unchecked(value) }) |
| 58 | + } |
| 59 | + _ => None, |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +mod tests { |
| 64 | + use super::const_from_bytes_with_nul; |
| 65 | + |
| 66 | + // Bad. |
| 67 | + const _EMPTY_UNTERMINATED: () = assert!(const_from_bytes_with_nul(b"").is_none()); |
| 68 | + const _EMPTY_DOUBLE_TERMINATED: () = assert!(const_from_bytes_with_nul(b"\0\0").is_none()); |
| 69 | + const _DOUBLE_NUL: () = assert!(const_from_bytes_with_nul(b"\0\0").is_none()); |
| 70 | + const _LEADINGL_NUL: () = assert!(const_from_bytes_with_nul(b"\0a\0").is_none()); |
| 71 | + const _INTERNAL_NUL_UNTERMINATED: () = assert!(const_from_bytes_with_nul(b"\0a").is_none()); |
| 72 | + |
| 73 | + // Good. |
| 74 | + const EMPTY_TERMINATED: () = assert!(const_from_bytes_with_nul(b"\0").is_some()); |
| 75 | + const _NONEMPTY: () = assert!(const_from_bytes_with_nul(b"asdf\0").is_some()); |
| 76 | + const _1_CHAR: () = assert!(const_from_bytes_with_nul(b"a\0").is_some()); |
| 77 | +} |
0 commit comments