#![allow(unsafe_code)]
use core::{marker::PhantomData, ptr, slice};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct BorrowedStr<'a> {
pub ptr: *const u8,
pub len: usize,
_phantom: PhantomData<&'a [u8]>,
}
unsafe impl Send for BorrowedStr<'_> {}
unsafe impl Sync for BorrowedStr<'_> {}
impl<'a> BorrowedStr<'a> {
#[must_use]
pub const fn empty() -> Self {
Self {
ptr: ptr::null(),
len: 0,
_phantom: PhantomData,
}
}
#[must_use]
pub const fn from_str(s: &'a str) -> Self {
Self {
ptr: s.as_ptr(),
len: s.len(),
_phantom: PhantomData,
}
}
#[must_use]
pub unsafe fn as_str(&self) -> &'a str {
if self.ptr.is_null() || self.len == 0 {
return "";
}
let bytes = unsafe { slice::from_raw_parts(self.ptr, self.len) };
unsafe { core::str::from_utf8_unchecked(bytes) }
}
}
impl core::fmt::Debug for BorrowedStr<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let s = unsafe { self.as_str() };
write!(f, "BorrowedStr({s:?})")
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Slice<'a, T> {
pub ptr: *const T,
pub len: usize,
_phantom: PhantomData<&'a [T]>,
}
unsafe impl<T: Sync> Send for Slice<'_, T> {}
unsafe impl<T: Sync> Sync for Slice<'_, T> {}
impl<'a, T> Slice<'a, T> {
#[must_use]
pub const fn empty() -> Self {
Self {
ptr: ptr::null(),
len: 0,
_phantom: PhantomData,
}
}
#[must_use]
pub const fn from_slice(s: &'a [T]) -> Self {
Self {
ptr: s.as_ptr(),
len: s.len(),
_phantom: PhantomData,
}
}
#[must_use]
pub unsafe fn as_slice(&self) -> &'a [T] {
if self.ptr.is_null() || self.len == 0 {
return &[];
}
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PluginErrorCode {
Ok = 0,
Generic = 1,
Panic = 2,
InvalidArgument = 3,
NotImplemented = 4,
AbiMismatch = 5,
SerializationFailed = 6,
}
#[repr(C)]
pub struct OwnedBytes {
pub ptr: *mut u8,
pub len: usize,
pub cap: usize,
pub drop_fn: Option<unsafe extern "C" fn(ptr: *mut u8, len: usize, cap: usize)>,
}
unsafe impl Send for OwnedBytes {}
impl OwnedBytes {
#[must_use]
pub const fn empty() -> Self {
Self {
ptr: ptr::null_mut(),
len: 0,
cap: 0,
drop_fn: None,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0 || self.ptr.is_null()
}
#[must_use]
pub fn from_vec(v: Vec<u8>) -> Self {
let mut v = core::mem::ManuallyDrop::new(v);
let ptr = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();
Self {
ptr,
len,
cap,
drop_fn: Some(drop_owned_bytes),
}
}
#[must_use]
pub unsafe fn as_bytes(&self) -> &[u8] {
if self.is_empty() {
return &[];
}
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
}
impl Drop for OwnedBytes {
fn drop(&mut self) {
if let Some(f) = self.drop_fn.take()
&& !self.ptr.is_null()
{
unsafe { f(self.ptr, self.len, self.cap) };
self.ptr = ptr::null_mut();
self.len = 0;
self.cap = 0;
}
}
}
pub unsafe extern "C" fn drop_owned_bytes(ptr: *mut u8, len: usize, cap: usize) {
if ptr.is_null() {
return;
}
unsafe {
let _ = Vec::from_raw_parts(ptr, len, cap);
}
}
#[repr(C)]
pub struct PluginError {
pub code: PluginErrorCode,
pub message: OwnedBytes,
}
impl PluginError {
#[must_use]
pub fn generic(message: impl AsRef<str>) -> Self {
Self {
code: PluginErrorCode::Generic,
message: OwnedBytes::from_vec(message.as_ref().as_bytes().to_vec()),
}
}
#[must_use]
pub fn new(code: PluginErrorCode, message: impl AsRef<str>) -> Self {
Self {
code,
message: OwnedBytes::from_vec(message.as_ref().as_bytes().to_vec()),
}
}
#[must_use]
pub fn panic(message: impl AsRef<str>) -> Self {
Self::new(PluginErrorCode::Panic, message)
}
#[must_use]
pub fn message_string(&self) -> String {
let bytes = unsafe { self.message.as_bytes() };
String::from_utf8_lossy(bytes).into_owned()
}
}
impl core::fmt::Debug for PluginError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct(stringify!(PluginError))
.field("code", &self.code)
.field("message", &self.message_string())
.finish()
}
}
#[repr(C, u8)]
pub enum PluginResult<T> {
Ok(T),
Err(PluginError),
}
impl<T> PluginResult<T> {
pub fn into_result(self) -> Result<T, PluginError> {
match self {
Self::Ok(t) => Ok(t),
Self::Err(e) => Err(e),
}
}
pub fn from_result(r: Result<T, PluginError>) -> Self {
match r {
Ok(t) => Self::Ok(t),
Err(e) => Self::Err(e),
}
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use rstest::rstest;
use super::*;
#[rstest]
#[case::ascii("hello")]
#[case::empty("")]
#[case::utf8("héllo wörld")]
#[case::multibyte("\u{1F600}\u{1F4A9}")]
fn borrowed_str_round_trips(#[case] s: &str) {
let b = BorrowedStr::from_str(s);
let back = unsafe { b.as_str() };
assert_eq!(back, s);
}
#[rstest]
fn slice_round_trips() {
let data: [u32; 3] = [1, 2, 3];
let s = Slice::from_slice(&data);
let back = unsafe { s.as_slice() };
assert_eq!(back, &[1u32, 2, 3]);
}
#[rstest]
fn empty_slice_returns_empty() {
let s: Slice<u8> = Slice::empty();
let back = unsafe { s.as_slice() };
assert!(back.is_empty());
}
#[rstest]
fn owned_bytes_round_trip_and_drop() {
let payload = b"hello world".to_vec();
let owned = OwnedBytes::from_vec(payload.clone());
let view = unsafe { owned.as_bytes() }.to_vec();
assert_eq!(view, payload);
drop(owned);
}
#[rstest]
fn owned_bytes_drop_fn_runs_exactly_once() {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn counting_drop(ptr: *mut u8, len: usize, cap: usize) {
if !ptr.is_null() {
COUNTER.fetch_add(1, Ordering::SeqCst);
unsafe {
let _ = Vec::from_raw_parts(ptr, len, cap);
}
}
}
COUNTER.store(0, Ordering::SeqCst);
let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4]);
let ptr = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();
let owned = OwnedBytes {
ptr,
len,
cap,
drop_fn: Some(counting_drop),
};
assert_eq!(COUNTER.load(Ordering::SeqCst), 0);
drop(owned);
assert_eq!(COUNTER.load(Ordering::SeqCst), 1);
}
#[rstest]
fn plugin_error_carries_message() {
let err = PluginError::generic("bad input");
assert_eq!(err.code, PluginErrorCode::Generic);
assert_eq!(err.message_string(), "bad input");
}
#[rstest]
fn plugin_result_round_trips() {
let ok: PluginResult<u32> = PluginResult::Ok(42);
let r = ok.into_result();
assert_eq!(r.unwrap(), 42);
let err: PluginResult<u32> = PluginResult::Err(PluginError::generic("nope"));
let r = err.into_result();
assert!(r.is_err());
}
#[rstest]
fn plugin_result_from_result_round_trips() {
let r: PluginResult<u32> = PluginResult::from_result(Ok(7));
assert_eq!(r.into_result().unwrap(), 7);
let r: PluginResult<u32> = PluginResult::from_result(Err(PluginError::generic("x")));
let e = r.into_result().unwrap_err();
assert_eq!(e.code, PluginErrorCode::Generic);
assert_eq!(e.message_string(), "x");
}
#[rstest]
fn borrowed_str_empty_is_empty_when_borrowed_back() {
let b = BorrowedStr::empty();
assert!(b.ptr.is_null());
assert_eq!(b.len, 0);
assert_eq!(unsafe { b.as_str() }, "");
}
#[rstest]
fn borrowed_str_debug_prints_contents() {
let b = BorrowedStr::from_str("hello");
let rendered = format!("{b:?}");
assert!(rendered.contains("hello"));
}
#[rstest]
fn slice_empty_descriptor_is_null_and_zero_len() {
let s: Slice<u32> = Slice::empty();
assert!(s.ptr.is_null());
assert_eq!(s.len, 0);
}
#[rstest]
fn owned_bytes_empty_is_empty() {
let owned = OwnedBytes::empty();
assert!(owned.is_empty());
assert!(owned.ptr.is_null());
assert_eq!(owned.len, 0);
assert_eq!(owned.cap, 0);
assert!(owned.drop_fn.is_none());
assert!(unsafe { owned.as_bytes() }.is_empty());
}
#[rstest]
fn owned_bytes_is_empty_for_zero_length_buffer() {
let owned = OwnedBytes::from_vec(Vec::new());
assert!(owned.is_empty());
drop(owned);
}
#[rstest]
fn owned_bytes_drop_with_null_ptr_short_circuits() {
let owned = OwnedBytes {
ptr: ptr::null_mut(),
len: 0,
cap: 0,
drop_fn: Some(drop_owned_bytes),
};
drop(owned);
}
#[rstest]
fn drop_owned_bytes_handles_null_ptr_without_panic() {
unsafe {
drop_owned_bytes(ptr::null_mut(), 0, 0);
};
}
#[rstest]
fn drop_owned_bytes_frees_vec_leaked_with_from_vec_layout() {
let mut v = core::mem::ManuallyDrop::new(vec![1u8, 2, 3, 4, 5]);
let ptr = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();
unsafe {
drop_owned_bytes(ptr, len, cap);
};
}
#[rstest]
fn plugin_error_new_carries_code_and_message() {
let err = PluginError::new(PluginErrorCode::InvalidArgument, "bad arg");
assert_eq!(err.code, PluginErrorCode::InvalidArgument);
assert_eq!(err.message_string(), "bad arg");
}
#[rstest]
fn plugin_error_panic_sets_panic_code() {
let err = PluginError::panic("oops");
assert_eq!(err.code, PluginErrorCode::Panic);
assert_eq!(err.message_string(), "oops");
}
#[rstest]
fn plugin_error_debug_renders_code_and_message() {
let err = PluginError::new(PluginErrorCode::NotImplemented, "todo");
let rendered = format!("{err:?}");
assert!(rendered.contains("NotImplemented"));
assert!(rendered.contains("todo"));
}
#[rstest]
#[case::ok(PluginErrorCode::Ok, 0u32)]
#[case::generic(PluginErrorCode::Generic, 1)]
#[case::panic(PluginErrorCode::Panic, 2)]
#[case::invalid_argument(PluginErrorCode::InvalidArgument, 3)]
#[case::not_implemented(PluginErrorCode::NotImplemented, 4)]
#[case::abi_mismatch(PluginErrorCode::AbiMismatch, 5)]
#[case::serialization_failed(PluginErrorCode::SerializationFailed, 6)]
fn plugin_error_code_has_stable_discriminant(
#[case] code: PluginErrorCode,
#[case] expected: u32,
) {
assert_eq!(code as u32, expected);
}
}