#![doc(html_root_url = "https://docs.rs/relative/0.2.2")]
#![cfg_attr(feature = "nightly", feature(raw))]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications,
unused_results,
clippy::pedantic
)]
#![allow(
clippy::inline_always,
clippy::trivially_copy_pass_by_ref,
clippy::must_use_candidate
)]
use serde::{
de::{self, Deserialize, Deserializer}, ser::{Serialize, Serializer}
};
use std::{
any::{type_name, Any, TypeId}, cmp, fmt, hash, marker, mem::transmute
};
use uuid::Uuid;
#[doc(hidden)]
#[used]
#[no_mangle]
pub static RELATIVE_VTABLE_BASE: &(dyn Any + Sync) = &();
fn type_id<T: ?Sized + 'static>() -> u64 {
use std::hash::{Hash, Hasher};
let type_id = TypeId::of::<T>();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
type_id.hash(&mut hasher);
hasher.finish()
}
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(missing_debug_implementations, missing_docs)]
struct TraitObject {
data: *mut (),
vtable: *mut (),
}
pub struct Vtable<T: ?Sized>(usize, marker::PhantomData<fn(T)>);
impl<T: ?Sized> Vtable<T> {
#[inline(always)]
fn new(p: usize) -> Self {
Self(p, marker::PhantomData)
}
#[inline(always)]
pub unsafe fn from(ptr: &'static ()) -> Self {
let base = transmute::<*const dyn Any, TraitObject>(RELATIVE_VTABLE_BASE).vtable as usize;
#[cfg(feature = "nightly")]
{
let check_base =
transmute::<*const dyn Any, std::raw::TraitObject>(RELATIVE_VTABLE_BASE).vtable
as usize;
assert_eq!(check_base, base);
}
Self::new(
({
let ptr: *const () = ptr;
ptr
} as usize)
.wrapping_sub(base),
)
}
#[inline(always)]
pub fn to(&self) -> &'static () {
let base = unsafe { transmute::<*const dyn Any, TraitObject>(RELATIVE_VTABLE_BASE) }.vtable
as usize;
#[cfg(feature = "nightly")]
{
let check_base =
unsafe { transmute::<*const dyn Any, std::raw::TraitObject>(RELATIVE_VTABLE_BASE) }
.vtable as usize;
assert_eq!(check_base, base);
}
unsafe { &*(base.wrapping_add(self.0) as *const ()) }
}
}
impl<T: ?Sized> Clone for Vtable<T> {
#[inline(always)]
fn clone(&self) -> Self {
Self(self.0, marker::PhantomData)
}
}
impl<T: ?Sized> Copy for Vtable<T> {}
impl<T: ?Sized> PartialEq for Vtable<T> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T: ?Sized> Eq for Vtable<T> {}
impl<T: ?Sized> hash::Hash for Vtable<T> {
#[inline(always)]
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl<T: ?Sized> PartialOrd for Vtable<T> {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<T: ?Sized> Ord for Vtable<T> {
#[inline(always)]
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T: ?Sized> fmt::Debug for Vtable<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.debug_struct("Vtable")
.field(type_name::<T>(), &self.0)
.finish()
}
}
impl<T: ?Sized + 'static> Serialize for Vtable<T> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
<(Uuid, u64, usize) as Serialize>::serialize(
&(build_id::get(), type_id::<T>(), self.0),
serializer,
)
}
}
impl<'de, T: ?Sized + 'static> Deserialize<'de> for Vtable<T> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
<(Uuid, u64, usize) as Deserialize<'de>>::deserialize(deserializer).and_then(
|(build, id, ptr)| {
let local = build_id::get();
if build == local {
if id == type_id::<T>() {
Ok(Self::new(ptr))
} else {
Err(de::Error::custom(format_args!(
"relative reference to wrong type ???:{}, expected {}:{}",
id,
type_name::<T>(),
type_id::<T>()
)))
}
} else {
Err(de::Error::custom(format_args!(
"relative reference came from a different binary {}, expected {}",
build, local
)))
}
},
)
}
}
#[cfg(test)]
mod tests {
use super::{type_id, Vtable};
use bincode;
use metatype;
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::{any::Any, env, fmt, process, str};
#[test]
fn type_id_sanity() {
struct A;
struct B;
assert_ne!(type_id::<u8>(), type_id::<u16>());
assert_ne!(type_id::<A>(), type_id::<B>());
assert_eq!(type_id::<u8>(), type_id::<u8>());
assert_eq!(type_id::<A>(), type_id::<A>());
}
#[test]
fn multi_process() {
#[derive(Serialize, Deserialize)]
#[serde(bound(serialize = ""), bound(deserialize = ""))]
struct Xxx<A: 'static + ?Sized> {
a: Vtable<()>,
b: Vtable<A>,
}
impl<A: 'static + ?Sized> PartialEq for Xxx<A> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.a == other.a && self.b == other.b
}
}
impl<A: 'static + ?Sized> fmt::Debug for Xxx<A> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.debug_struct("Xxx")
.field("a", &self.a)
.field("b", &self.b)
.finish()
}
}
unsafe fn vtable<T: ?Sized>(_: &T, ptr: &'static ()) -> Vtable<T> {
Vtable::from(ptr)
}
fn eq<T: ?Sized>(_: &T, _: &T) {}
let trait_object: Box<dyn Any> = Box::new(1234_usize);
let meta: metatype::TraitObject =
metatype::type_coerce(<dyn Any as metatype::Type>::meta(&*trait_object));
let a = Xxx {
a: unsafe { Vtable::from(meta.vtable) },
b: unsafe { vtable(&*trait_object, meta.vtable) },
};
let bincoded = bincode::serialize(&a).unwrap();
let jsoned = serde_json::to_string(&a).unwrap();
let unbincoded = bincode::deserialize(&bincoded).unwrap();
let unjsoned = serde_json::from_str(&jsoned).unwrap();
eq(&a, &unbincoded);
eq(&a, &unjsoned);
assert_eq!(a, unbincoded);
assert_eq!(a, unjsoned);
if cfg!(not(miri)) {
if let Ok(x) = env::var("SPAWNED_TOKEN_RELATIVE") {
let (a2, bc): (_, Vec<u8>) = serde_json::from_str(&x).unwrap();
eq(&a, &a2);
let a3 = bincode::deserialize(&bc).unwrap();
eq(&a, &a3);
assert_eq!(a, a2);
assert_eq!(a, a3);
println!("success_token_relative {:?}", a2);
return;
}
let exe = env::current_exe().unwrap();
for i in 0..100 {
let output = process::Command::new(&exe)
.arg("--nocapture")
.arg("--exact")
.arg("tests::multi_process")
.env(
"SPAWNED_TOKEN_RELATIVE",
serde_json::to_string(&(&a, bincode::serialize(&a).unwrap())).unwrap(),
)
.output()
.unwrap();
if !str::from_utf8(&output.stdout)
.unwrap()
.contains("success_token_relative")
|| !output.status.success()
{
panic!("{}: {:?}", i, output);
}
}
}
}
}