use std::marker::PhantomData;
use std::mem::ManuallyDrop;
#[macro_export]
macro_rules! external {
($type:ty, $name:literal) => {
impl $crate::Externalizable for $type {
fn external_marker() -> ::core::primitive::usize {
static mut DEFINITION: $crate::ExternalDefinition =
$crate::ExternalDefinition::new($name);
let ptr = ::std::hint::black_box(unsafe {
::std::ptr::addr_of_mut!(DEFINITION)
});
ptr as ::core::primitive::usize
}
fn external_name() -> &'static ::core::primitive::str {
$name
}
}
};
}
pub trait Externalizable {
fn external_marker() -> usize;
fn external_name() -> &'static str;
}
#[doc(hidden)]
pub struct ExternalDefinition {
#[allow(unused)]
pub name: &'static str,
}
impl ExternalDefinition {
#[doc(hidden)]
pub const fn new(name: &'static str) -> Self {
Self { name }
}
}
#[repr(C)]
struct ExternalWithMarker<T> {
marker: usize,
external: T,
}
#[repr(transparent)]
pub struct ExternalPointer<E: Externalizable> {
ptr: *mut ManuallyDrop<ExternalWithMarker<E>>,
_type: std::marker::PhantomData<E>,
}
impl<E: Externalizable> ExternalPointer<E> {
pub fn new(external: E) -> Self {
let marker = E::external_marker();
let new =
Box::new(ManuallyDrop::new(ExternalWithMarker { marker, external }));
ExternalPointer {
ptr: Box::into_raw(new),
_type: PhantomData,
}
}
pub fn into_raw(self) -> *const std::ffi::c_void {
self.ptr as _
}
pub fn from_raw(ptr: *const std::ffi::c_void) -> Self {
ExternalPointer {
ptr: ptr as _,
_type: PhantomData,
}
}
fn validate_pointer(&self) -> *mut ExternalWithMarker<E> {
let expected_marker = E::external_marker();
if self.ptr.is_null()
|| self.ptr.align_offset(std::mem::align_of::<usize>()) != 0
|| unsafe { std::ptr::read::<usize>(self.ptr as _) } != expected_marker
{
panic!(
"Detected an invalid v8::External (expected {})",
E::external_name()
);
}
self.ptr as _
}
pub unsafe fn unsafely_deref(&self) -> &E {
let validated_ptr = self.validate_pointer();
let external = std::ptr::addr_of!((*validated_ptr).external);
&*external
}
pub unsafe fn unsafely_take(self) -> E {
let validated_ptr = self.validate_pointer();
let marker = std::ptr::addr_of_mut!((*validated_ptr).marker);
assert_ne!(std::ptr::replace(marker, 0), 0);
std::ptr::write(marker, 0);
let external =
std::ptr::read(std::ptr::addr_of!((*validated_ptr).external));
_ = Box::<ManuallyDrop<ExternalWithMarker<E>>>::from_raw(self.ptr);
external
}
}
#[cfg(test)]
mod tests {
use super::*;
struct External1(u32);
external!(External1, "external 1");
struct External2(());
external!(External2, "external 2");
struct External1b(());
external!(External1b, "external 1");
struct DeallocOnPanic<E: Externalizable>(Option<ExternalPointer<E>>);
impl<E: Externalizable> DeallocOnPanic<E> {
pub fn new(external: &ExternalPointer<E>) -> Self {
Self(Some(ExternalPointer {
ptr: external.ptr,
_type: PhantomData,
}))
}
}
impl<E: Externalizable> Drop for DeallocOnPanic<E> {
fn drop(&mut self) {
unsafe {
self.0.take().unwrap().unsafely_take();
}
}
}
#[test]
pub fn test_external() {
let external = ExternalPointer::new(External1(1));
assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
let ptr = external.into_raw();
let external = ExternalPointer::<External1>::from_raw(ptr);
assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
assert_eq!(unsafe { external.unsafely_take() }.0, 1);
}
#[test]
pub fn test_external_markers() {
let m1 = External1::external_marker();
let m2 = External2::external_marker();
let m1b = External1b::external_marker();
assert_ne!(m1, m2);
assert_ne!(m1, m1b);
}
#[test]
#[should_panic]
pub fn test_external_incompatible_same_name() {
let external = ExternalPointer::new(External1(1));
let _dealloc = DeallocOnPanic::new(&external);
assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
let ptr = external.into_raw();
let external = ExternalPointer::<External1b>::from_raw(ptr);
unsafe {
external.unsafely_deref();
}
}
#[cfg(not(miri))]
#[test]
#[should_panic]
pub fn test_external_deref_after_take() {
let external = ExternalPointer::new(External1(1));
let ptr = external.into_raw();
let external = ExternalPointer::<External1>::from_raw(ptr);
unsafe {
external.unsafely_take();
}
let external = ExternalPointer::<External1>::from_raw(ptr);
unsafe {
external.unsafely_deref();
}
}
#[test]
#[should_panic]
pub fn test_external_incompatible_deref() {
let external = ExternalPointer::new(External1(1));
let _dealloc = DeallocOnPanic::new(&external);
assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
let ptr = external.into_raw();
let external = ExternalPointer::<External2>::from_raw(ptr);
unsafe {
external.unsafely_deref();
}
}
#[test]
#[should_panic]
pub fn test_external_incompatible_take() {
let external = ExternalPointer::new(External1(1));
let _dealloc = DeallocOnPanic::new(&external);
assert_eq!(unsafe { external.unsafely_deref() }.0, 1);
let ptr = external.into_raw();
let external = ExternalPointer::<External2>::from_raw(ptr);
unsafe {
external.unsafely_take();
}
}
}