rquickjs_core/runtime/
userdata.rs

1use alloc::boxed::Box;
2use core::fmt;
3use core::{
4    any::{Any, TypeId},
5    cell::{Cell, UnsafeCell},
6    hash::{BuildHasherDefault, Hasher},
7    mem::ManuallyDrop,
8    ops::Deref,
9};
10#[cfg(not(feature = "std"))]
11use hashbrown::HashMap;
12#[cfg(feature = "std")]
13use std::collections::HashMap;
14
15use crate::JsLifetime;
16
17unsafe fn to_static<'js, T>(this: T) -> T::Changed<'static>
18where
19    T: JsLifetime<'js> + Sized,
20{
21    assert_eq!(
22        core::mem::size_of::<T>(),
23        core::mem::size_of::<T::Changed<'static>>(),
24        "Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
25    );
26    assert_eq!(
27        core::mem::align_of::<T>(),
28        core::mem::align_of::<T::Changed<'static>>(),
29        "Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
30    );
31
32    // a super unsafe way to cast between types, This is necessary because normal transmute will
33    // complain that Self and Self::Static are not related so might not have the same size.
34    union Trans<A, B> {
35        from: ManuallyDrop<A>,
36        to: ManuallyDrop<B>,
37    }
38
39    ManuallyDrop::into_inner(
40        (Trans {
41            from: ManuallyDrop::new(this),
42        })
43        .to,
44    )
45}
46
47unsafe fn from_static_box<'js, T>(this: Box<T::Changed<'static>>) -> Box<T>
48where
49    T: JsLifetime<'js> + Sized,
50{
51    assert_eq!(
52        core::mem::size_of::<T>(),
53        core::mem::size_of::<T::Changed<'static>>(),
54        "Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
55    );
56    assert_eq!(
57        core::mem::align_of::<T>(),
58        core::mem::align_of::<T::Changed<'static>>(),
59        "Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
60    );
61
62    Box::from_raw(Box::into_raw(this) as *mut T)
63}
64
65unsafe fn from_static_ref<'a, 'js, T>(this: &'a T::Changed<'static>) -> &'a T
66where
67    T: JsLifetime<'js> + Sized,
68{
69    core::mem::transmute(this)
70}
71
72pub struct UserDataError<T>(pub T);
73
74impl<T> fmt::Display for UserDataError<T> {
75    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76        write!(
77            f,
78            "tried to mutate the user data store while it was being referenced"
79        )
80    }
81}
82
83impl<T> fmt::Debug for UserDataError<T> {
84    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85        fmt::Display::fmt(self, f)
86    }
87}
88
89#[derive(Default)]
90struct IdHasher(u64);
91
92impl Hasher for IdHasher {
93    fn write(&mut self, _: &[u8]) {
94        unreachable!("TypeId calls write_u64");
95    }
96
97    fn write_u64(&mut self, id: u64) {
98        self.0 = id;
99    }
100
101    fn finish(&self) -> u64 {
102        self.0
103    }
104}
105
106/// Typeid hashmap taken from axum.
107#[derive(Default)]
108pub(crate) struct UserDataMap {
109    map: UnsafeCell<HashMap<TypeId, Box<dyn Any>, BuildHasherDefault<IdHasher>>>,
110    count: Cell<usize>,
111}
112
113impl UserDataMap {
114    pub fn insert<'js, U>(&self, data: U) -> Result<Option<Box<U>>, UserDataError<U>>
115    where
116        U: JsLifetime<'js>,
117        U::Changed<'static>: Any,
118    {
119        if self.count.get() > 0 {
120            return Err(UserDataError(data));
121        }
122        let user_static = unsafe { to_static(data) };
123        let id = TypeId::of::<U::Changed<'static>>();
124        let r = unsafe { (*self.map.get()).insert(id, Box::new(user_static)) }.map(|x| {
125            let r = x
126                .downcast()
127                .expect("type confusion! userdata not stored under the right type id");
128            unsafe { from_static_box(r) }
129        });
130        Ok(r)
131    }
132
133    pub fn remove<'js, U>(&self) -> Result<Option<Box<U>>, UserDataError<()>>
134    where
135        U: JsLifetime<'js>,
136        U::Changed<'static>: Any,
137    {
138        if self.count.get() > 0 {
139            return Err(UserDataError(()));
140        }
141        let id = TypeId::of::<U::Changed<'static>>();
142        let r = unsafe { (*self.map.get()).remove(&id) }.map(|x| {
143            let r = x
144                .downcast()
145                .expect("type confusion! userdata not stored under the right type id");
146            unsafe { from_static_box(r) }
147        });
148        Ok(r)
149    }
150
151    pub fn get<'js, U>(&self) -> Option<UserDataGuard<U>>
152    where
153        U: JsLifetime<'js>,
154        U::Changed<'static>: Any,
155    {
156        let id = TypeId::of::<U::Changed<'static>>();
157        unsafe { (*self.map.get()).get(&id) }.map(|x| {
158            self.count.set(self.count.get() + 1);
159            let u = x
160                .downcast_ref()
161                .expect("type confusion! userdata not stored under the right type id");
162
163            let r = unsafe { from_static_ref(u) };
164            UserDataGuard { map: self, r }
165        })
166    }
167
168    pub fn clear(&mut self) {
169        self.map.get_mut().clear()
170    }
171}
172
173/// Guard for user data to avoid inserting new userdata while exisiting userdata is being
174/// referenced.
175pub struct UserDataGuard<'a, U> {
176    map: &'a UserDataMap,
177    r: &'a U,
178}
179
180impl<'a, U> Deref for UserDataGuard<'a, U> {
181    type Target = U;
182
183    fn deref(&self) -> &Self::Target {
184        self.r
185    }
186}
187
188impl<'a, U> Drop for UserDataGuard<'a, U> {
189    fn drop(&mut self) {
190        self.map.count.set(self.map.count.get() - 1)
191    }
192}