#[cfg(not(all(feature = "napi8", not(target_family = "wasm"))))]
use std::any::TypeId;
use std::ffi::c_void;
#[cfg(not(all(feature = "napi8", not(target_family = "wasm"))))]
pub(crate) type ClassId = Option<TypeId>;
#[cfg(all(feature = "napi8", not(target_family = "wasm")))]
#[derive(Clone, Copy, Default)]
pub(crate) struct ClassId;
#[cfg(not(all(feature = "napi8", not(target_family = "wasm"))))]
#[inline(always)]
pub(crate) fn class_id_of<T: 'static>() -> ClassId {
Some(TypeId::of::<T>())
}
#[cfg(all(feature = "napi8", not(target_family = "wasm")))]
#[inline(always)]
#[allow(clippy::extra_unused_type_parameters)]
pub(crate) fn class_id_of<T: 'static>() -> ClassId {
ClassId
}
pub(crate) fn wrong_class_error(class_name: &str) -> crate::Error {
crate::Error::new(
crate::Status::InvalidArg,
format!("Value is not an instance of class `{class_name}`"),
)
}
#[cfg(not(all(feature = "napi8", not(target_family = "wasm"))))]
#[inline(always)]
fn type_id_of<T: ?Sized>() -> TypeId {
trait NonStaticAny {
fn get_type_id(&self) -> TypeId
where
Self: 'static;
}
impl<T: ?Sized> NonStaticAny for std::marker::PhantomData<T> {
#[inline(always)]
fn get_type_id(&self) -> TypeId
where
Self: 'static,
{
TypeId::of::<T>()
}
}
let phantom_data = std::marker::PhantomData::<T>;
NonStaticAny::get_type_id(unsafe {
std::mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&phantom_data)
})
}
#[cfg(not(all(feature = "napi8", not(target_family = "wasm"))))]
#[inline]
pub(crate) fn check_class_identity<T>(
wrapped: *mut c_void,
class_name: fn() -> &'static str,
) -> crate::Result<()> {
if std::mem::size_of::<T>() == 0 {
return Ok(());
}
let recorded = super::REFERENCE_MAP
.with(|cell| cell.borrow_mut(|map| map.get(&wrapped).map(|entry| entry.class_id)));
let is_t = match recorded {
Some(Some(class_id)) => class_id == type_id_of::<T>(),
_ => {
let addr = wrapped as usize;
addr >= 4096 && addr.is_multiple_of(std::mem::align_of::<T>())
}
};
if is_t {
Ok(())
} else {
Err(wrong_class_error(class_name()))
}
}
#[cfg(all(feature = "napi8", not(target_family = "wasm")))]
#[inline(always)]
#[allow(clippy::extra_unused_type_parameters)]
pub(crate) fn check_class_identity<T>(
_wrapped: *mut c_void,
_class_name: fn() -> &'static str,
) -> crate::Result<()> {
Ok(())
}
pub(crate) fn short_type_name<T>() -> &'static str {
let name = std::any::type_name::<T>();
let name = name.split('<').next().unwrap_or(name);
name.rsplit("::").next().unwrap_or(name)
}
#[cfg(all(test, not(all(feature = "napi8", not(target_family = "wasm")))))]
mod tests {
use std::ffi::c_void;
use super::{check_class_identity, class_id_of, short_type_name, ClassId};
use crate::bindgen_runtime::{RefEntry, REFERENCE_MAP};
use crate::Status;
struct ClassA(#[allow(dead_code)] u64);
struct ClassB(#[allow(dead_code)] u64);
struct WithLifetime<'a>(#[allow(dead_code)] &'a u64);
struct Empty;
fn record(wrapped: *mut c_void, class_id: ClassId) {
let info = (wrapped, std::ptr::null_mut(), std::ptr::null());
REFERENCE_MAP
.with(|cell| cell.borrow_mut(|map| map.insert(wrapped, RefEntry { info, class_id })));
}
fn forget(wrapped: *mut c_void) {
REFERENCE_MAP.with(|cell| cell.borrow_mut(|map| map.remove(&wrapped)));
}
fn name() -> &'static str {
"ClassB"
}
#[test]
fn recorded_class_must_match() {
let mut value = ClassA(1);
let wrapped: *mut c_void = (&mut value as *mut ClassA).cast();
record(wrapped, class_id_of::<ClassA>());
assert!(check_class_identity::<ClassA>(wrapped, name).is_ok());
let err = check_class_identity::<ClassB>(wrapped, name).unwrap_err();
assert_eq!(err.status, Status::InvalidArg);
assert_eq!(err.reason, "Value is not an instance of class `ClassB`");
assert!(check_class_identity::<Empty>(wrapped, name).is_ok());
record(wrapped, class_id_of::<WithLifetime<'static>>());
assert!(check_class_identity::<WithLifetime<'_>>(wrapped, name).is_ok());
assert!(check_class_identity::<ClassA>(wrapped, name).is_err());
forget(wrapped);
}
#[test]
fn unrecorded_class_is_accepted_unless_it_cannot_be_a_value() {
let mut value = ClassB(1);
let wrapped: *mut c_void = (&mut value as *mut ClassB).cast();
assert!(check_class_identity::<ClassB>(wrapped, name).is_ok());
record(wrapped, None);
assert!(check_class_identity::<ClassB>(wrapped, name).is_ok());
forget(wrapped);
let dangling = std::ptr::NonNull::<Empty>::dangling().as_ptr().cast();
assert!(check_class_identity::<ClassB>(dangling, name).is_err());
assert!(check_class_identity::<ClassB>(wrapped.wrapping_byte_add(1), name).is_err());
}
#[test]
fn short_type_name_drops_the_module_path() {
assert_eq!(short_type_name::<ClassA>(), "ClassA");
assert_eq!(short_type_name::<WithLifetime<'static>>(), "WithLifetime");
}
}