use std::ffi::{CStr, CString, c_char};
use std::marker::PhantomData;
pub(crate) type NotThreadSafe = PhantomData<*mut ()>;
macro_rules! vcall {
($this:expr => $method:ident($($argument:expr),* $(,)?)) => {{
let this = $this;
let vtable = (&raw const (*this).vtable_).read();
((*vtable).$method)(this $(, $argument)*)
}};
}
pub(crate) use vcall;
pub(crate) unsafe fn borrow_cstr<'a>(pointer: *const c_char) -> Option<&'a CStr> {
(!pointer.is_null()).then(|| unsafe { CStr::from_ptr(pointer) })
}
pub(crate) unsafe fn copy_cstr(pointer: *const c_char) -> Option<CString> {
unsafe { borrow_cstr(pointer) }.map(CStr::to_owned)
}
pub(crate) fn cstring_from_buffer(buffer: &[c_char]) -> CString {
let bytes = buffer
.iter()
.map(|&byte| byte as u8)
.take_while(|&byte| byte != 0)
.collect();
unsafe { CString::from_vec_unchecked(bytes) }
}
pub(crate) fn buffer_from_cstr<const N: usize>(value: &CStr) -> Option<[c_char; N]> {
let bytes = value.to_bytes_with_nul();
let mut buffer = [0; N];
if bytes.len() > N {
return None;
}
for (slot, &byte) in buffer.iter_mut().zip(bytes) {
*slot = byte as c_char;
}
Some(buffer)
}
#[cfg(test)]
pub(crate) mod test_support {
use std::mem::size_of;
pub(crate) unsafe fn mock_vtable<V>(fill: *const (), patch: impl FnOnce(*mut V)) -> Box<V> {
const SLOT: usize = size_of::<*const ()>();
assert_eq!(size_of::<V>() % SLOT, 0);
let mut vtable = Box::<V>::new_uninit();
let slots = vtable.as_mut_ptr().cast::<*const ()>();
for slot in 0..size_of::<V>() / SLOT {
unsafe { slots.add(slot).write(fill) };
}
patch(vtable.as_mut_ptr());
unsafe { vtable.assume_init() }
}
pub(crate) unsafe extern "C" fn unexpected_call() {
panic!("unexpected virtual call");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buffers_round_trip_strings() {
let buffer = buffer_from_cstr::<8>(c"ctf_2f").unwrap();
assert_eq!(cstring_from_buffer(&buffer).as_c_str(), c"ctf_2f");
assert_eq!(buffer_from_cstr::<6>(c"ctf_2f"), None);
assert_eq!(
cstring_from_buffer(&[b'a' as c_char, b'b' as c_char]).as_c_str(),
c"ab"
);
}
}