#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
#![deny(rustdoc::missing_crate_level_docs)]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
#[cfg(feature = "validation")]
pub mod validation;
#[cfg(feature = "vtable_cache")]
use core::sync::atomic::{AtomicU64, Ordering};
use core::{
alloc::Layout,
any::Any,
hash::{Hash, Hasher},
marker::PhantomData,
ptr,
};
use ptr_meta::{DynMetadata, Pointee};
#[cfg(feature = "vtable_cache")]
use rkyv::with::{Atomic, With};
use rkyv::{
from_archived,
ser::{ScratchSpace, Serializer},
to_archived, Archived, Fallible, Serialize,
};
pub use rkyv_dyn_derive::archive_dyn;
use rkyv_typename::TypeName;
use std::collections::{hash_map::DefaultHasher, HashMap};
#[cfg(feature = "validation")]
pub use validation::{CheckDynError, DynContext};
#[doc(hidden)]
pub use inventory;
#[cfg(all(feature = "vtable_cache", feature = "nightly"))]
use core::intrinsics::likely;
#[cfg(all(feature = "vtable_cache", not(feature = "nightly")))]
#[inline]
fn likely(b: bool) -> bool {
b
}
pub type DynError = Box<dyn Any>;
pub trait DynSerializer {
fn pos_dyn(&self) -> usize;
fn write_dyn(&mut self, bytes: &[u8]) -> Result<(), DynError>;
unsafe fn push_scratch_dyn(&mut self, layout: Layout) -> Result<ptr::NonNull<[u8]>, DynError>;
unsafe fn pop_scratch_dyn(
&mut self,
ptr: ptr::NonNull<u8>,
layout: Layout,
) -> Result<(), DynError>;
}
impl<'a> Fallible for dyn DynSerializer + 'a {
type Error = DynError;
}
impl<'a> Serializer for dyn DynSerializer + 'a {
fn pos(&self) -> usize {
self.pos_dyn()
}
fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
self.write_dyn(bytes)
}
}
impl<'a> ScratchSpace for dyn DynSerializer + 'a {
unsafe fn push_scratch(&mut self, layout: Layout) -> Result<ptr::NonNull<[u8]>, Self::Error> {
self.push_scratch_dyn(layout)
}
unsafe fn pop_scratch(
&mut self,
ptr: ptr::NonNull<u8>,
layout: Layout,
) -> Result<(), Self::Error> {
self.pop_scratch_dyn(ptr, layout)
}
}
impl<S: ScratchSpace + Serializer + ?Sized> DynSerializer for &mut S {
fn pos_dyn(&self) -> usize {
self.pos()
}
fn write_dyn(&mut self, bytes: &[u8]) -> Result<(), DynError> {
self.write(bytes).map_err(|e| Box::new(e) as DynError)
}
unsafe fn push_scratch_dyn(&mut self, layout: Layout) -> Result<ptr::NonNull<[u8]>, DynError> {
self.push_scratch(layout)
.map_err(|e| Box::new(e) as DynError)
}
unsafe fn pop_scratch_dyn(
&mut self,
ptr: ptr::NonNull<u8>,
layout: Layout,
) -> Result<(), DynError> {
self.pop_scratch(ptr, layout)
.map_err(|e| Box::new(e) as DynError)
}
}
fn hash_type<T: TypeName + ?Sized>() -> u64 {
let mut hasher = DefaultHasher::new();
T::build_type_name(|piece| piece.hash(&mut hasher));
hasher.finish()
}
pub trait SerializeDyn {
fn serialize_dyn(&self, serializer: &mut dyn DynSerializer) -> Result<usize, DynError>;
fn archived_type_id(&self) -> u64;
}
impl<T: for<'a> Serialize<dyn DynSerializer + 'a>> SerializeDyn for T
where
T::Archived: TypeName,
{
fn serialize_dyn(&self, serializer: &mut dyn DynSerializer) -> Result<usize, DynError> {
serializer.serialize_value(self)
}
fn archived_type_id(&self) -> u64 {
hash_type::<T::Archived>()
}
}
pub trait DynDeserializer {}
impl<'a> Fallible for dyn DynDeserializer + 'a {
type Error = DynError;
}
impl<D: Fallible + ?Sized> DynDeserializer for &mut D {}
pub trait DeserializeDyn<T: Pointee + ?Sized> {
unsafe fn deserialize_dyn(
&self,
deserializer: &mut dyn DynDeserializer,
alloc: &mut dyn FnMut(Layout) -> *mut u8,
) -> Result<*mut (), DynError>;
fn deserialize_dyn_metadata(
&self,
deserializer: &mut dyn DynDeserializer,
) -> Result<T::Metadata, DynError>;
}
#[cfg_attr(feature = "strict", repr(C))]
pub struct ArchivedDynMetadata<T: ?Sized> {
type_id: Archived<u64>,
#[cfg(feature = "vtable_cache")]
cached_vtable: Archived<With<AtomicU64, Atomic>>,
#[cfg(not(feature = "vtable_cache"))]
#[allow(dead_code)]
cached_vtable: Archived<u64>,
phantom: PhantomData<T>,
}
impl<T: TypeName + ?Sized> ArchivedDynMetadata<T> {
pub unsafe fn emplace(type_id: u64, out: *mut Self) {
ptr::addr_of_mut!((*out).type_id).write(to_archived!(type_id));
#[cfg(feature = "vtable_cache")]
{
let cached_vtable = ptr::addr_of_mut!((*out).cached_vtable);
(*cached_vtable).store(0u64, Ordering::Relaxed);
}
#[cfg(not(feature = "vtable_cache"))]
ptr::addr_of_mut!((*out).cached_vtable).write(to_archived!(0u64));
}
fn lookup_vtable(&self) -> usize {
IMPL_REGISTRY
.get::<T>(from_archived!(self.type_id))
.expect("attempted to get vtable for an unregistered impl")
.vtable
}
#[cfg(feature = "vtable_cache")]
pub fn vtable(&self) -> usize {
let cached_vtable = self.cached_vtable.load(Ordering::Relaxed);
if likely(cached_vtable != 0) {
return cached_vtable as usize;
}
let vtable = self.lookup_vtable();
self.cached_vtable
.store(vtable as usize as u64, Ordering::Relaxed);
vtable
}
#[cfg(not(feature = "vtable_cache"))]
pub fn vtable(&self) -> usize {
self.lookup_vtable()
}
pub fn pointer_metadata(&self) -> DynMetadata<T> {
unsafe { core::mem::transmute(self.vtable()) }
}
}
#[cfg(debug_assertions)]
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct ImplDebugInfo {
pub file: &'static str,
pub line: u32,
pub column: u32,
}
#[cfg(debug_assertions)]
#[doc(hidden)]
#[macro_export]
macro_rules! debug_info {
() => {
rkyv_dyn::ImplDebugInfo {
file: core::file!(),
line: core::line!(),
column: core::column!(),
}
};
}
#[cfg(not(debug_assertions))]
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct ImplDebugInfo;
#[cfg(not(debug_assertions))]
#[doc(hidden)]
#[macro_export]
macro_rules! debug_info {
() => {
rkyv_dyn::ImplDebugInfo
};
}
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct ImplData {
pub vtable: usize,
pub debug_info: ImplDebugInfo,
}
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
struct ImplId {
trait_id: u64,
type_id: u64,
}
impl ImplId {
fn new<TY: TypeName, TR: TypeName + ?Sized>() -> Self {
Self::from_type_id::<TR>(hash_type::<TY>())
}
fn from_type_id<TR: TypeName + ?Sized>(type_id: u64) -> Self {
Self {
trait_id: hash_type::<TR>(),
type_id: type_id | 1,
}
}
}
#[doc(hidden)]
pub struct ImplEntry {
impl_id: ImplId,
data: ImplData,
}
impl ImplEntry {
#[doc(hidden)]
pub fn new<TY: TypeName + RegisteredImpl<TR>, TR: TypeName + ?Sized>() -> Self {
Self {
impl_id: ImplId::new::<TY, TR>(),
data: ImplData {
vtable: <TY as RegisteredImpl<TR>>::vtable(),
debug_info: <TY as RegisteredImpl<TR>>::debug_info(),
},
}
}
}
inventory::collect!(ImplEntry);
struct ImplRegistry {
id_to_data: HashMap<ImplId, ImplData>,
}
impl ImplRegistry {
fn new() -> Self {
Self {
id_to_data: HashMap::new(),
}
}
fn add_entry(&mut self, entry: &ImplEntry) {
let old_value = self.id_to_data.insert(entry.impl_id, entry.data);
#[cfg(debug_assertions)]
if let Some(old_data) = old_value {
eprintln!("impl id conflict, a trait implementation was likely added twice (but it's possible there was a hash collision)");
eprintln!(
"existing impl registered at {}:{}:{}",
old_data.debug_info.file, old_data.debug_info.line, old_data.debug_info.column
);
eprintln!(
"new impl registered at {}:{}:{}",
entry.data.debug_info.file,
entry.data.debug_info.line,
entry.data.debug_info.column
);
panic!();
}
debug_assert!(old_value.is_none(), "impl id conflict, a trait implementation was likely added twice (but it's possible there was a hash collision)");
}
fn get<T: TypeName + ?Sized>(&self, type_id: u64) -> Option<&ImplData> {
self.id_to_data.get(&ImplId::from_type_id::<T>(type_id))
}
}
lazy_static::lazy_static! {
static ref IMPL_REGISTRY: ImplRegistry = {
let mut result = ImplRegistry::new();
for entry in inventory::iter::<ImplEntry> {
result.add_entry(entry);
}
result
};
}
#[doc(hidden)]
pub unsafe trait RegisteredImpl<T: ?Sized> {
fn vtable() -> usize;
fn debug_info() -> ImplDebugInfo;
}
#[doc(hidden)]
#[cfg(not(feature = "validation"))]
#[macro_export]
macro_rules! register_validation {
($type:ty as $trait:ty) => {};
}
#[macro_export]
macro_rules! register_impl {
($type:ty as $trait:ty) => {
const _: () = {
use rkyv_dyn::{
debug_info, inventory, register_validation, ImplData, ImplDebugInfo, ImplEntry,
RegisteredImpl,
};
unsafe impl RegisteredImpl<$trait> for $type {
fn vtable() -> usize {
unsafe {
core::mem::transmute(ptr_meta::metadata(
core::ptr::null::<$type>() as *const $trait
))
}
}
fn debug_info() -> ImplDebugInfo {
debug_info!()
}
}
inventory::submit! { ImplEntry::new::<$type, $trait>() }
register_validation!($type as $trait);
};
};
}