1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
//! This crate provides a [`DynamicBuffer`] struct that allows to easily share a pointer to u8 with
//! C APIs and free that pointer properly by carrying a `destructor_callback`. In that regard it is
//! carrying a very barebone vtable so that freeing the memory pointed to by the [`DynamicBuffer`]
//! is easy either on the C or Rust side.
//!
//! A `From` implementation is provided to convert a `Vec` of `u8` into a [`DynamicBuffer`] easily,
//! the destructor being populated automatically.
//!
//! A [`DynamicBufferView`] is also provided to indicate that the struct does not own the data and
//! is merely used to share data in a read-only way.
#![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))]
#![cfg_attr(all(doc, not(doctest)), feature(doc_cfg))]
#![warn(rustdoc::broken_intra_doc_links)]
use std::ffi::c_int;
#[repr(C)]
pub struct DynamicBufferView {
pub pointer: *const u8,
pub length: usize,
}
impl DynamicBufferView {
/// Returns a view to the memory borrowed by the [`DynamicBufferView`].
///
/// # Safety
///
/// This is safe to call as long as the pointer is valid and the length corresponds to the
/// length of the underlying buffer.
pub unsafe fn as_slice(&self) -> &[u8] {
std::slice::from_raw_parts(self.pointer, self.length)
}
}
impl From<&[u8]> for DynamicBufferView {
fn from(a: &[u8]) -> Self {
Self {
pointer: a.as_ptr(),
length: a.len(),
}
}
}
#[repr(C)]
pub struct DynamicBuffer {
pub pointer: *mut u8,
pub length: usize,
pub destructor: Option<unsafe extern "C" fn(*mut u8, usize) -> c_int>,
}
impl DynamicBuffer {
/// Destroy the [`DynamicBuffer`] freeing the underlying memory using the provided
/// `destructor_callback` and clearing or zeroing all the members.
///
/// If the `pointer` stored in [`DynamicBuffer`] is NULL, then `length` is zeroed out and the
/// `destructor_callback` is set to `None`. It is similar to how free ignores NULL in C, we just
/// do some additional housekeeping to signal the [`DynamicBuffer`] is an empty shell.
///
/// # Safety
///
/// Destroy is safe to call only if the `destructor_callback` is the method that needs to be
/// called to free the stored pointer. For example in C++, memory allocated with `new` must be
/// freed with `delete`, memory allocated with `new[]` must be freed with `delete[]`.
///
/// Length must indicate how many `u8` are present in the allocation and can be used by the
/// `destructor_callback` to free memory. For example in the case of a `Vec` being turned into a
/// [`DynamicBuffer`] the length is obtained by first calling the `len` function on the `Vec`.
pub unsafe fn destroy(&mut self) -> Result<(), &str> {
if self.pointer.is_null() {
// Finish emptying stuff
self.length = 0;
self.destructor = None;
return Ok(());
}
match self.destructor {
Some(destructor_callback) => {
let res = destructor_callback(self.pointer, self.length);
if res == 0 {
// If the deallocation is successful then we empty the buffer
self.pointer = std::ptr::null_mut();
self.length = 0;
self.destructor = None;
return Ok(());
}
Err("destructor returned a non 0 error code")
}
// We could not free because of a missing destructor, return an error
None => Err("destructor is NULL, could not destroy DynamicBuffer"),
}
}
}
unsafe extern "C" fn free_u8_ptr_built_from_vec_u8(pointer: *mut u8, length: usize) -> c_int {
if pointer.is_null() {
return 0;
}
let slice = std::slice::from_raw_parts_mut(pointer, length);
drop(Box::from_raw(slice));
0
}
impl From<Vec<u8>> for DynamicBuffer {
fn from(value: Vec<u8>) -> Self {
let boxed_slice = value.into_boxed_slice();
let length = boxed_slice.len();
let pointer = Box::leak(boxed_slice);
Self {
pointer: pointer.as_mut_ptr(),
length,
destructor: Some(free_u8_ptr_built_from_vec_u8),
}
}
}
fn check_ptr_is_non_null_and_aligned<T>(ptr: *const T) -> Result<(), String> {
if ptr.is_null() {
return Err(format!("pointer is null, got: {ptr:p}"));
}
let expected_alignment = std::mem::align_of::<T>();
if ptr as usize % expected_alignment != 0 {
return Err(format!(
"pointer is misaligned, expected {expected_alignment} bytes alignment, got pointer: \
{ptr:p}. You May have mixed some pointers in your function call.",
));
}
Ok(())
}
/// Get a mutable reference from a pointer checking the pointer is well aligned for the given type.
///
/// # Safety
///
/// Caller of this function needs to make sure the pointer type corresponds to the data type being
/// pointed to.
///
/// Caller of this function needs to make sure the aliasing rules for mutable reference are
/// respected.
///
/// The basics are: at any time only a single mutable reference may exist to a given memory location
/// XOR any number of immutable reference may exist to a given memory location.
///
/// Failure to abide by the above rules will result in undefined behavior (UB).
unsafe fn get_mut_checked<'a, T>(ptr: *mut T) -> Result<&'a mut T, String> {
match check_ptr_is_non_null_and_aligned(ptr) {
Ok(()) => ptr
.as_mut()
.ok_or_else(|| "Error while converting to mut reference".into()),
Err(e) => Err(e),
}
}
/// C API to destroy a [`DynamicBuffer`].
///
/// # Safety
///
/// This function is safe to call if `dynamic_buffer` is not aliased to avoid double frees.
#[no_mangle]
pub unsafe extern "C" fn destroy_dynamic_buffer(dynamic_buffer: *mut DynamicBuffer) -> c_int {
// Mimicks C for calls of free on NULL, nothing occurs
if dynamic_buffer.is_null() {
return 0;
}
let dynamic_buffer = match get_mut_checked(dynamic_buffer) {
Ok(dynamic_buffer) => dynamic_buffer,
Err(cause) => {
if cfg!(feature = "c_api_print_error_source") {
println!("{cause}");
}
return 1;
}
};
match dynamic_buffer.destroy() {
Ok(_) => 0,
Err(cause) => {
if cfg!(feature = "c_api_print_error_source") {
println!("{cause}");
}
1
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_dynamic_buffer_vec_u8_custom_destructor() {
let vec = vec![99u8; 1000];
let len = vec.len();
let ptr = vec.leak();
unsafe extern "C" fn custom_destroy_vec_u8_buffer(
pointer: *mut u8,
length: usize,
) -> c_int {
if pointer.is_null() {
return 0;
}
let slice = std::slice::from_raw_parts_mut(pointer, length);
drop(Box::from_raw(slice));
0
}
let mut dynamic_buffer = DynamicBuffer {
pointer: ptr.as_mut_ptr(),
length: len,
destructor: Some(custom_destroy_vec_u8_buffer),
};
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
assert_eq!(res, 0);
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
assert!(dynamic_buffer.pointer.is_null());
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
// Same as free in C, destroy on a NULL pointer does nothing
assert_eq!(res, 0);
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
let mut some_u8 = 0u8;
dynamic_buffer.pointer = &mut some_u8 as *mut u8;
assert!(dynamic_buffer.destructor.is_none());
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
assert_eq!(res, 1);
}
#[test]
fn test_dynamic_buffer_vec_u8_default_destructor() {
let vec = vec![99u8; 1000];
let mut dynamic_buffer: DynamicBuffer = vec.clone().into();
let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
assert_eq!(res, 0);
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
let mut dynamic_buffer: DynamicBuffer = vec.into();
let res = unsafe { dynamic_buffer.destroy() };
assert!(res.is_ok());
assert!(dynamic_buffer.pointer.is_null());
assert_eq!(dynamic_buffer.length, 0);
assert!(dynamic_buffer.destructor.is_none());
}
}