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.
This commit is contained in:
Eric Seppanen 2022-03-15 16:47:26 -07:00
parent 1bfe40d11c
commit d5fe4cad5a
2 changed files with 106 additions and 0 deletions

View File

@ -328,6 +328,27 @@ pub fn into_bytes(self) -> Vec<u8> {
}
}
/// 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 @@ pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a 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
///
/// ```

View File

@ -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";