use std::alloc::{Layout, dealloc};
use std::ffi::c_void;
use std::ptr::drop_in_place;
pub type CppDestructors = cfg_select! {
target_os = "linux" => ItaniumDestructors,
target_os = "windows" => MsvcDestructor,
};
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct ItaniumDestructors {
pub complete_dtor: unsafe extern "C" fn(this: *mut c_void),
pub delete_dtor: unsafe extern "C" fn(this: *mut c_void),
}
impl ItaniumDestructors {
pub const VTABLE_SLOTS: usize = 2;
pub const fn new<T>() -> Self {
unsafe extern "C" fn delete_dtor<T>(this: *mut c_void) {
unsafe {
drop_in_place(this.cast::<T>());
dealloc(this.cast(), Layout::new::<T>());
}
}
Self {
complete_dtor: c_drop_in_place::<T>,
delete_dtor: delete_dtor::<T>,
}
}
pub const fn new_dealloc_abort<T>() -> Self {
unsafe extern "C" fn delete_dtor<T>(this: *mut c_void) {
unsafe { drop_in_place(this.cast::<T>()) };
std::process::abort()
}
Self {
complete_dtor: c_drop_in_place::<T>,
delete_dtor: delete_dtor::<T>,
}
}
pub const fn new_dealloc_panic<T>() -> Self {
unsafe extern "C" fn delete_dtor<T>(this: *mut c_void) {
unsafe { drop_in_place(this.cast::<T>()) };
panic!()
}
Self {
complete_dtor: c_drop_in_place::<T>,
delete_dtor: delete_dtor::<T>,
}
}
pub const fn new_leak<T>() -> Self {
Self {
complete_dtor: c_drop_in_place::<T>,
delete_dtor: c_drop_in_place::<T>,
}
}
pub const fn new_noop() -> Self {
extern "C" fn noop(_: *mut c_void) {}
Self {
complete_dtor: noop,
delete_dtor: noop,
}
}
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct MsvcDestructor(pub unsafe extern "C" fn(this: *mut c_void, flags: u32) -> *mut c_void);
impl MsvcDestructor {
pub const VTABLE_SLOTS: usize = 1;
pub const fn new<T>() -> Self {
unsafe extern "C" fn dtor<T>(this: *mut c_void, flags: u32) -> *mut c_void {
unsafe { drop_in_place(this.cast::<T>()) };
if flags & 1 == 1 {
unsafe { dealloc(this.cast(), Layout::new::<T>()) }
}
this
}
Self(dtor::<T>)
}
pub const fn new_dealloc_abort<T>() -> Self {
unsafe extern "C" fn dtor<T>(this: *mut c_void, flags: u32) -> *mut c_void {
unsafe { drop_in_place(this.cast::<T>()) };
if flags & 1 == 1 {
std::process::abort()
} else {
this
}
}
Self(dtor::<T>)
}
pub const fn new_dealloc_panic<T>() -> Self {
unsafe extern "C" fn dtor<T>(this: *mut c_void, flags: u32) -> *mut c_void {
unsafe { drop_in_place(this.cast::<T>()) };
if flags & 1 == 1 { panic!() } else { this }
}
Self(dtor::<T>)
}
pub const fn new_leak<T>() -> Self {
unsafe extern "C" fn dtor<T>(this: *mut c_void, _: u32) -> *mut c_void {
unsafe { drop_in_place(this.cast::<T>()) };
this
}
Self(dtor::<T>)
}
pub const fn new_noop() -> Self {
extern "C" fn noop(this: *mut c_void, _: u32) -> *mut c_void {
this
}
Self(noop)
}
}
pub type WChar = cfg_select! {
all(target_os = "windows", target_pointer_width = "64") => u16,
all(target_os = "linux", target_pointer_width = "64") => u32,
_ => compile_error!("Unsupported target"),
};
unsafe extern "C" fn c_drop_in_place<T>(to_drop: *mut c_void) {
unsafe { drop_in_place(to_drop.cast::<T>()) };
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::mem::ManuallyDrop;
struct DropCounter<'counter>(&'counter Cell<usize>);
impl Drop for DropCounter<'_> {
fn drop(&mut self) {
self.0.set(self.0.get() + 1);
}
}
#[test]
fn itanium_complete_destructor_drops_the_concrete_type() {
let drops = Cell::new(0);
let mut value = ManuallyDrop::new(DropCounter(&drops));
let destructors = ItaniumDestructors::new_leak::<DropCounter<'_>>();
unsafe { (destructors.complete_dtor)((&raw mut value).cast()) };
assert_eq!(drops.get(), 1);
}
#[test]
fn msvc_non_deleting_destructor_drops_the_concrete_type() {
let drops = Cell::new(0);
let mut value = ManuallyDrop::new(DropCounter(&drops));
let destructor = MsvcDestructor::new_leak::<DropCounter<'_>>();
unsafe { destructor.0((&raw mut value).cast(), 0) };
assert_eq!(drops.get(), 1);
}
}