use crate::errors::NulError;
#[cfg(any(test, feature = "proptest"))]
use proptest::prelude::*;
#[allow(unused)]
#[cfg(test)]
use similar_asserts::assert_eq;
use std::{
ffi::{c_char, c_void},
fmt::{self, Debug, Display},
ptr::NonNull,
};
pub(crate) struct LibcString(NonNull<[c_char]>);
impl LibcString {
pub(crate) fn new(s: impl AsRef<str>) -> Result<Self, NulError> {
fn polymorphized(s: &str) -> Result<LibcString, NulError> {
if s.contains('\0') {
return Err(NulError);
}
let len = s.len() + 1;
#[cfg(not(tarpaulin_include))]
assert!(
len < isize::MAX as usize,
"Cannot add a final NUL without breaking Rust's slice requirements"
);
let buf = unsafe { libc::malloc(len) }.cast::<c_char>();
let buf = NonNull::new(buf).expect("Failed to allocate string buffer");
let buf = NonNull::slice_from_raw_parts(buf, len);
let result = LibcString(buf);
let start = buf.as_ptr().cast::<u8>();
unsafe { start.copy_from_nonoverlapping(s.as_ptr(), s.len()) };
unsafe { start.add(s.len()).write(b'\0') };
Ok(result)
}
polymorphized(s.as_ref())
}
pub(crate) fn as_str(&self) -> &str {
unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
self.borrow().cast::<u8>(),
self.len() - 1,
))
}
}
pub(crate) fn len(&self) -> usize {
self.0.len()
}
pub(crate) fn borrow(&self) -> *const c_char {
self.0.as_ptr().cast::<c_char>()
}
#[cfg(any(test, feature = "hwloc-2_3_0"))]
pub(crate) unsafe fn into_raw(self) -> *mut c_char {
let ptr = self.0.as_ptr().cast::<c_char>();
std::mem::forget(self);
ptr
}
}
#[cfg(any(test, feature = "proptest"))]
impl Arbitrary for LibcString {
type Parameters = ();
type Strategy = prop::strategy::Perturb<
crate::strategies::AnyString,
fn(String, prop::test_runner::TestRng) -> Self,
>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
crate::strategies::any_string().prop_perturb(|s, mut rng| {
let s = s
.chars()
.map(|c| {
if c == '\0' {
char::from(rng.random_range(1..=127))
} else {
c
}
})
.collect::<String>();
Self::new(s).expect("input was sanitized above, can't fail")
})
}
}
impl AsRef<str> for LibcString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Clone for LibcString {
fn clone(&self) -> Self {
let len = self.len();
let buf = unsafe { libc::malloc(len) }.cast::<c_char>();
let buf = NonNull::new(buf).expect("Failed to allocate string buffer");
let buf = NonNull::slice_from_raw_parts(buf, len);
let result = Self(buf);
let output_start = buf.as_ptr().cast::<u8>();
let input_start = self.as_ref().as_bytes().as_ptr();
unsafe { output_start.copy_from_nonoverlapping(input_start, len) };
result
}
}
impl Debug for LibcString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s: &str = self.as_ref();
f.pad(&format!("{s:?}"))
}
}
impl Display for LibcString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s: &str = self.as_ref();
f.pad(s)
}
}
impl Drop for LibcString {
fn drop(&mut self) {
unsafe { libc::free(self.0.as_ptr().cast::<c_void>()) }
}
}
impl Eq for LibcString {}
impl PartialEq for LibcString {
fn eq(&self, other: &Self) -> bool {
self.as_ref() == other.as_ref()
}
}
unsafe impl Send for LibcString {}
unsafe impl Sync for LibcString {}
#[cfg(test)]
mod tests {
use super::*;
use crate::strategies::any_string;
#[allow(unused)]
use similar_asserts::assert_eq;
use std::ffi::CStr;
macro_rules! check_new {
($result:expr, $input:expr) => {
match ($input.contains('\0'), $result) {
(false, Ok(result)) => result,
(true, Err(NulError)) => return Ok(()),
(false, Err(NulError)) | (true, Ok(_)) => {
prop_assert!(
false,
"LibcString::new should error out if and only if NUL is present"
);
unreachable!()
}
}
};
}
proptest! {
#[test]
fn unary(s in any_string()) {
let c = check_new!(LibcString::new(&s), &s);
prop_assert_eq!(c.len(), s.len() + 1);
prop_assert_eq!(unsafe { CStr::from_ptr(c.borrow()) }.to_str().unwrap(), &s);
prop_assert_eq!(c.as_ref(), &s);
prop_assert_eq!(&c.to_string(), &s);
prop_assert_eq!(format!("{c:?}"), format!("{s:?}"));
let c2 = c.clone();
prop_assert_eq!(c2.len(), c.len());
prop_assert_ne!(c2.borrow(), c.borrow());
prop_assert_eq!(c2.as_ref(), c.as_ref());
let backup = c.0;
let raw = unsafe { c.into_raw() };
prop_assert_eq!(raw, backup.cast::<c_char>().as_ptr());
unsafe { libc::free(raw.cast::<c_void>()) };
}
#[test]
fn binary((s1, s2) in (any_string(), any_string())) {
let c1 = check_new!(LibcString::new(&s1), &s1);
let c2 = check_new!(LibcString::new(&s2), &s2);
prop_assert_eq!(c1 == c2, s1 == s2);
}
}
}