use std::ffi::{CStr, c_char};
pub(crate) unsafe fn write_out<T>(out: *mut T, value: T) {
if !out.is_null() {
unsafe { out.write(value) };
}
}
pub(crate) unsafe fn c_str<'a>(ptr: *const c_char) -> Option<&'a CStr> {
if ptr.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(ptr) })
}
pub(crate) unsafe fn c_string_lossy(ptr: *const c_char) -> Option<String> {
unsafe { c_str(ptr) }.map(|s| s.to_string_lossy().into_owned())
}
pub(crate) unsafe fn as_ref<'a, T>(ptr: *const T) -> Option<&'a T> {
unsafe { ptr.as_ref() }
}
pub(crate) unsafe fn as_mut<'a, T>(ptr: *mut T) -> Option<&'a mut T> {
unsafe { ptr.as_mut() }
}
pub(crate) struct CMallocBuf {
ptr: std::ptr::NonNull<u8>,
len: usize,
}
impl CMallocBuf {
pub(crate) fn copy_from(bytes: &[u8]) -> Option<Self> {
let raw = unsafe { libc::malloc(bytes.len().max(1)) }.cast::<u8>();
let ptr = std::ptr::NonNull::new(raw)?;
unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), bytes.len()) };
Some(Self { ptr, len: bytes.len() })
}
pub(crate) fn len(&self) -> usize {
self.len
}
pub(crate) fn into_raw(self) -> *mut std::ffi::c_void {
let ptr = self.ptr.as_ptr().cast::<std::ffi::c_void>();
std::mem::forget(self);
ptr
}
}
impl Drop for CMallocBuf {
fn drop(&mut self) {
unsafe { libc::free(self.ptr.as_ptr().cast()) };
}
}
impl std::fmt::Debug for CMallocBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CMallocBuf").field("len", &self.len).finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_out_ignores_a_null_destination() {
unsafe { write_out(std::ptr::null_mut::<u32>(), 7) };
}
#[test]
fn write_out_does_not_read_the_previous_contents() {
let mut slot = std::mem::MaybeUninit::<*const c_char>::uninit();
unsafe { write_out(slot.as_mut_ptr(), c"hi".as_ptr()) };
let written = unsafe { slot.assume_init() };
assert_eq!(unsafe { CStr::from_ptr(written) }, c"hi");
}
#[test]
fn c_str_rejects_null_and_reads_the_rest() {
assert!(unsafe { c_str(std::ptr::null()) }.is_none());
assert_eq!(unsafe { c_str(c"abc".as_ptr()) }, Some(c"abc"));
}
#[test]
fn a_malloc_buffer_round_trips_and_is_aligned() {
let buf = CMallocBuf::copy_from(&[1u8, 2, 3]).expect("malloc");
assert_eq!(buf.len(), 3);
let raw = buf.into_raw();
assert!(!raw.is_null());
assert_eq!(unsafe { std::slice::from_raw_parts(raw.cast::<u8>(), 3) }, [1, 2, 3]);
unsafe { libc::free(raw) };
}
#[test]
fn an_empty_buffer_is_still_a_freeable_pointer() {
let buf = CMallocBuf::copy_from(&[]).expect("malloc");
assert_eq!(buf.len(), 0);
unsafe { libc::free(buf.into_raw()) };
}
#[test]
fn dropping_an_unreleased_buffer_frees_it() {
drop(CMallocBuf::copy_from(&[9u8; 32]).expect("malloc"));
}
}