From d5fe4cad5a68a305b9fa7d421b0ffa358b14b0a9 Mon Sep 17 00:00:00 2001 From: Eric Seppanen Date: Tue, 15 Mar 2022 16:47:26 -0700 Subject: [PATCH] add CStr::from_bytes_until_nul This adds a member fn that converts a slice into a CStr; it is intended to be safer than from_ptr (which is unsafe and may read out of bounds), and more useful than from_bytes_with_nul (which requires that the caller already know where the nul byte is). feature gate: cstr_from_bytes_until_nul Also add an error type FromBytesUntilNulError for this fn. --- library/std/src/ffi/c_str.rs | 69 ++++++++++++++++++++++++++++++ library/std/src/ffi/c_str/tests.rs | 37 ++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/library/std/src/ffi/c_str.rs b/library/std/src/ffi/c_str.rs index b833d0e2ca507..a68def1e83dbb 100644 --- a/library/std/src/ffi/c_str.rs +++ b/library/std/src/ffi/c_str.rs @@ -328,6 +328,27 @@ impl FromVecWithNulError { } } +/// An error indicating that no nul byte was present. +/// +/// A slice used to create a [`CStr`] must contain a nul byte somewhere +/// within the slice. +/// +/// This error is created by the [`CStr::from_bytes_until_nul`] method. +/// +#[derive(Clone, PartialEq, Eq, Debug)] +#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")] +pub struct FromBytesUntilNulError(()); + +#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")] +impl Error for FromBytesUntilNulError {} + +#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")] +impl fmt::Display for FromBytesUntilNulError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "data provided does not contain a nul") + } +} + /// An error indicating invalid UTF-8 when converting a [`CString`] into a [`String`]. /// /// `CString` is just a wrapper over a buffer of bytes with a nul terminator; @@ -1239,12 +1260,60 @@ impl CStr { } } + /// Creates a C string wrapper from a byte slice. + /// + /// This method will create a `CStr` from any byte slice that contains at + /// least one nul byte. The caller does not need to know or specify where + /// the nul byte is located. + /// + /// If the first byte is a nul character, this method will return an + /// empty `CStr`. If multiple nul characters are present, the `CStr` will + /// end at the first one. + /// + /// If the slice only has a single nul byte at the end, this method is + /// equivalent to [`CStr::from_bytes_with_nul`]. + /// + /// # Examples + /// ``` + /// #![feature(cstr_from_bytes_until_nul)] + /// + /// use std::ffi::CStr; + /// + /// let mut buffer = [0u8; 16]; + /// unsafe { + /// // Here we might call an unsafe C function that writes a string + /// // into the buffer. + /// let buf_ptr = buffer.as_mut_ptr(); + /// buf_ptr.write_bytes(b'A', 8); + /// } + /// // Attempt to extract a C nul-terminated string from the buffer. + /// let c_str = CStr::from_bytes_until_nul(&buffer[..]).unwrap(); + /// assert_eq!(c_str.to_str().unwrap(), "AAAAAAAA"); + /// ``` + /// + #[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")] + pub fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError> { + let nul_pos = memchr::memchr(0, bytes); + match nul_pos { + Some(nul_pos) => { + // SAFETY: We know there is a nul byte at nul_pos, so this slice + // (ending at the nul byte) is a well-formed C string. + let subslice = &bytes[..nul_pos + 1]; + Ok(unsafe { CStr::from_bytes_with_nul_unchecked(subslice) }) + } + None => Err(FromBytesUntilNulError(())), + } + } + /// Creates a C string wrapper from a byte slice. /// /// This function will cast the provided `bytes` to a `CStr` /// wrapper after ensuring that the byte slice is nul-terminated /// and does not contain any interior nul bytes. /// + /// If the nul byte may not be at the end, + /// [`CStr::from_bytes_until_nul`] can be used instead. + /// /// # Examples /// /// ``` diff --git a/library/std/src/ffi/c_str/tests.rs b/library/std/src/ffi/c_str/tests.rs index 8d603229315c0..c20da138a18d0 100644 --- a/library/std/src/ffi/c_str/tests.rs +++ b/library/std/src/ffi/c_str/tests.rs @@ -117,6 +117,43 @@ fn from_bytes_with_nul_interior() { assert!(cstr.is_err()); } +#[test] +fn cstr_from_bytes_until_nul() { + // Test an empty slice. This should fail because it + // does not contain a nul byte. + let b = b""; + assert_eq!(CStr::from_bytes_until_nul(&b[..]), Err(FromBytesUntilNulError(()))); + + // Test a non-empty slice, that does not contain a nul byte. + let b = b"hello"; + assert_eq!(CStr::from_bytes_until_nul(&b[..]), Err(FromBytesUntilNulError(()))); + + // Test an empty nul-terminated string + let b = b"\0"; + let r = CStr::from_bytes_until_nul(&b[..]).unwrap(); + assert_eq!(r.to_bytes(), b""); + + // Test a slice with the nul byte in the middle + let b = b"hello\0world!"; + let r = CStr::from_bytes_until_nul(&b[..]).unwrap(); + assert_eq!(r.to_bytes(), b"hello"); + + // Test a slice with the nul byte at the end + let b = b"hello\0"; + let r = CStr::from_bytes_until_nul(&b[..]).unwrap(); + assert_eq!(r.to_bytes(), b"hello"); + + // Test a slice with two nul bytes at the end + let b = b"hello\0\0"; + let r = CStr::from_bytes_until_nul(&b[..]).unwrap(); + assert_eq!(r.to_bytes(), b"hello"); + + // Test a slice containing lots of nul bytes + let b = b"\0\0\0\0"; + let r = CStr::from_bytes_until_nul(&b[..]).unwrap(); + assert_eq!(r.to_bytes(), b""); +} + #[test] fn into_boxed() { let orig: &[u8] = b"Hello, world!\0";