about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Seppanen <eds@reric.net>2022-03-15 16:47:26 -0700
committerEric Seppanen <eds@reric.net>2022-03-18 15:46:49 -0700
commitd5fe4cad5a68a305b9fa7d421b0ffa358b14b0a9 (patch)
treecdae84e7294252b09030d812d04cda15ecbc7b96
parent1bfe40d11c3630254504fb73eeccfca28d50df52 (diff)
downloadrust-d5fe4cad5a68a305b9fa7d421b0ffa358b14b0a9.tar.gz
rust-d5fe4cad5a68a305b9fa7d421b0ffa358b14b0a9.zip
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.
-rw-r--r--library/std/src/ffi/c_str.rs69
-rw-r--r--library/std/src/ffi/c_str/tests.rs37
2 files changed, 106 insertions, 0 deletions
diff --git a/library/std/src/ffi/c_str.rs b/library/std/src/ffi/c_str.rs
index b833d0e2ca5..a68def1e83d 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;
@@ -1241,10 +1262,58 @@ 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 8d603229315..c20da138a18 100644
--- a/library/std/src/ffi/c_str/tests.rs
+++ b/library/std/src/ffi/c_str/tests.rs
@@ -118,6 +118,43 @@ fn from_bytes_with_nul_interior() {
 }
 
 #[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";
     let cstr = CStr::from_bytes_with_nul(orig).unwrap();