Skip to content

Commit

Permalink
Remove UserData trait in favor of the more general JsLifetime trait
Browse files Browse the repository at this point in the history
  • Loading branch information
DelSkayn committed Nov 18, 2024
1 parent 1c33082 commit 4c17cd8
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 133 deletions.
36 changes: 22 additions & 14 deletions core/src/context/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[cfg(feature = "futures")]
use std::future::Future;
use std::{
any::Any,
ffi::{CStr, CString},
fs,
mem::{self, MaybeUninit},
Expand All @@ -14,8 +15,8 @@ use crate::AsyncContext;
use crate::{
markers::Invariant,
qjs,
runtime::{opaque::Opaque, UserData, UserDataError, UserDataGuard},
Atom, Error, FromJs, Function, IntoJs, Object, Promise, Result, String, Value,
runtime::{opaque::Opaque, UserDataError, UserDataGuard},
Atom, Error, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, String, Value,
};

use super::Context;
Expand Down Expand Up @@ -446,27 +447,34 @@ impl<'js> Ctx<'js> {
/// Returns the value from the argument if the userdata is currently being accessed and
/// insertion is not possible.
/// Otherwise returns the exising value for this type if it existed.
pub fn store_userdata<U: UserData<'js>>(
&self,
data: U,
) -> StdResult<Option<Box<U>>, UserDataError<U>> {
pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
unsafe { self.get_opaque().insert_userdata(data) }
}

/// Remove the userdata of the given type from the userdata storage.
///
/// Returns Err(()) if the userdata is currently being accessed and removing isn't possible.
/// Returns Ok(None) if userdata of the given type wasn't inserted.
pub fn remove_userdata<U: UserData<'js>>(
&self,
) -> StdResult<Option<Box<U>>, UserDataError<()>> {
pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
unsafe { self.get_opaque().remove_userdata() }
}

/// Retrieves a borrow to the userdata of the given type from the userdata storage.
///
/// Returns None if userdata of the given type wasn't inserted.
pub fn userdata<U: UserData<'js>>(&self) -> Option<UserDataGuard<U>> {
pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
unsafe { self.get_opaque().get_userdata() }
}

Expand All @@ -478,7 +486,7 @@ impl<'js> Ctx<'js> {

#[cfg(test)]
mod test {
use crate::CatchResultExt;
use crate::{CatchResultExt, JsLifetime};

#[test]
fn exports() {
Expand Down Expand Up @@ -665,14 +673,14 @@ mod test {

#[test]
fn userdata() {
use crate::{runtime::UserData, Context, Function, Runtime};
use crate::{Context, Function, Runtime};

pub struct MyUserData<'js> {
base: Function<'js>,
}

unsafe impl<'js> UserData<'js> for MyUserData<'js> {
type Static = MyUserData<'static>;
unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
type Changed<'to> = MyUserData<'to>;
}

let rt = Runtime::new().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion core/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub(crate) mod schedular;
mod spawner;

pub use base::{Runtime, WeakRuntime};
pub use userdata::{UserData, UserDataError, UserDataGuard};
pub use userdata::{UserDataError, UserDataGuard};

#[cfg(feature = "futures")]
pub(crate) use r#async::InnerRuntime;
Expand Down
16 changes: 11 additions & 5 deletions core/src/runtime/opaque.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{
class::{self, ffi::VTable, JsClass},
qjs, Ctx, Error, Object,
qjs, Ctx, Error, JsLifetime, Object,
};

use super::{
userdata::{UserDataGuard, UserDataMap},
InterruptHandler, UserData, UserDataError,
InterruptHandler, UserDataError,
};
use std::{
any::{Any, TypeId},
Expand Down Expand Up @@ -142,19 +142,25 @@ impl<'js> Opaque<'js> {

pub fn insert_userdata<U>(&self, data: U) -> Result<Option<Box<U>>, UserDataError<U>>
where
U: UserData<'js>,
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
self.userdata.insert(data)
}

pub fn remove_userdata<U>(&self) -> Result<Option<Box<U>>, UserDataError<()>>
where
U: UserData<'js>,
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
self.userdata.remove()
}

pub fn get_userdata<U: UserData<'js>>(&self) -> Option<UserDataGuard<U>> {
pub fn get_userdata<U>(&self) -> Option<UserDataGuard<U>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
self.userdata.get()
}

Expand Down
181 changes: 68 additions & 113 deletions core/src/runtime/userdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,112 +8,61 @@ use std::{
ops::Deref,
};

/// A trait for userdata which is stored in the runtime.
///
/// # Safety
/// For safe implementation of this trait none of its default implemented functions must be
/// overwritten and the type Static must be the correct type.
///
/// The static type must be the original type with the `'js` lifetime changed to `'static`.
///
/// All rquickjs javascript value have a `'js` lifetime, this lifetime is managed by rquickjs to
/// ensure that rquickjs values are used correctly. You can derive some lifetimes from this
/// lifetimes but only lifetimes on rquickjs structs can be soundly changed by this trait.
///
/// If changing a values type to its `UserData::Static` type would cause any borrow, non-rquickjs
/// struct with a '`js` struct to no have a different lifetime then the implementation is unsound.
///
/// ## Example
/// Below is a correctly implemented UserData, the `'js` on `Function` is directly derived from a
/// `Ctx<'js>`.
/// ```
/// # use rquickjs::{Function, runtime::UserData};
///
/// struct MyUserData<'js>{
/// function: Option<Function<'js>>
/// }
///
/// unsafe impl<'js> UserData<'js> for MyUserData<'js>{
/// // The self type with the lifetime changed to static.
/// type Static = MyUserData<'static>;
/// }
/// ```
///
/// The example below is __unsound__ as it changes the `&'js` borrow to static.
///
/// ```no_run
/// # use rquickjs::{Function, runtime::UserData};
///
/// struct MyUserData<'js>{
/// // This is unsound!
/// // The &'js lifetime here is not a lifetime on a rquickjs struct.
/// function: &'js Function<'js>
/// }
///
/// unsafe impl<'js> UserData<'js> for MyUserData<'js>{
/// // The self type with the lifetime changed to static.
/// type Static = MyUserData<'static>;
/// }
/// ```
///
///
pub unsafe trait UserData<'js> {
type Static: 'static + Any;

unsafe fn to_static(this: Self) -> Self::Static
where
Self: Sized,
{
assert_eq!(
std::mem::size_of::<Self>(),
std::mem::size_of::<Self::Static>(),
"Invalid implementation of UserData, size_of::<Self>() != size_of::<Self::Static>()"
);
assert_eq!(
std::mem::align_of::<Self>(),
std::mem::align_of::<Self::Static>(),
"Invalid implementation of UserData, align_of::<Self>() != align_of::<Self::Static>()"
);

// a super unsafe way to cast between types, This is necessary because normal transmute will
// complain that Self and Self::Static are not related so might not have the same size.
union Trans<A, B> {
from: ManuallyDrop<A>,
to: ManuallyDrop<B>,
}

ManuallyDrop::into_inner(
(Trans {
from: ManuallyDrop::new(this),
})
.to,
)
use crate::JsLifetime;

unsafe fn to_static<'js, T: JsLifetime<'js>>(this: T) -> T::Changed<'static>
where
T: Sized,
{
assert_eq!(
std::mem::size_of::<T>(),
std::mem::size_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
);
assert_eq!(
std::mem::align_of::<T>(),
std::mem::align_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
);

// a super unsafe way to cast between types, This is necessary because normal transmute will
// complain that Self and Self::Static are not related so might not have the same size.
union Trans<A, B> {
from: ManuallyDrop<A>,
to: ManuallyDrop<B>,
}

unsafe fn from_static_box(this: Box<Self::Static>) -> Box<Self>
where
Self: Sized,
{
assert_eq!(
std::mem::size_of::<Self>(),
std::mem::size_of::<Self::Static>(),
"Invalid implementation of UserData, size_of::<Self>() != size_of::<Self::Static>()"
);
assert_eq!(
std::mem::align_of::<Self>(),
std::mem::align_of::<Self::Static>(),
"Invalid implementation of UserData, align_of::<Self>() != align_of::<Self::Static>()"
);

Box::from_raw(Box::into_raw(this) as *mut Self)
}
ManuallyDrop::into_inner(
(Trans {
from: ManuallyDrop::new(this),
})
.to,
)
}

unsafe fn from_static_ref<'a>(this: &'a Self::Static) -> &'a Self
where
Self: Sized,
{
std::mem::transmute(this)
}
unsafe fn from_static_box<'js, T: JsLifetime<'js>>(this: Box<T::Changed<'static>>) -> Box<T>
where
T: Sized,
{
assert_eq!(
std::mem::size_of::<T>(),
std::mem::size_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
);
assert_eq!(
std::mem::align_of::<T>(),
std::mem::align_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
);

Box::from_raw(Box::into_raw(this) as *mut T)
}

unsafe fn from_static_ref<'a, 'js, T: JsLifetime<'js>>(this: &'a T::Changed<'static>) -> &'a T
where
T: Sized,
{
std::mem::transmute(this)
}

pub struct UserDataError<T>(pub T);
Expand Down Expand Up @@ -160,48 +109,54 @@ pub(crate) struct UserDataMap {
impl UserDataMap {
pub fn insert<'js, U>(&self, data: U) -> Result<Option<Box<U>>, UserDataError<U>>
where
U: UserData<'js>,
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
if self.count.get() > 0 {
return Err(UserDataError(data));
}
let user_static = unsafe { U::to_static(data) };
let id = TypeId::of::<U::Static>();
let user_static = unsafe { to_static(data) };
let id = TypeId::of::<U::Changed<'static>>();
let r = unsafe { (*self.map.get()).insert(id, Box::new(user_static)) }.map(|x| {
let r = x
.downcast()
.expect("type confusion! userdata not stored under the right type id");
unsafe { U::from_static_box(r) }
unsafe { from_static_box(r) }
});
Ok(r)
}

pub fn remove<'js, U>(&self) -> Result<Option<Box<U>>, UserDataError<()>>
where
U: UserData<'js>,
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
if self.count.get() > 0 {
return Err(UserDataError(()));
}
let id = TypeId::of::<U::Static>();
let id = TypeId::of::<U::Changed<'static>>();
let r = unsafe { (*self.map.get()).remove(&id) }.map(|x| {
let r = x
.downcast()
.expect("type confusion! userdata not stored under the right type id");
unsafe { U::from_static_box(r) }
unsafe { from_static_box(r) }
});
Ok(r)
}

pub fn get<'js, U: UserData<'js>>(&self) -> Option<UserDataGuard<U>> {
let id = TypeId::of::<U::Static>();
pub fn get<'js, U>(&self) -> Option<UserDataGuard<U>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
let id = TypeId::of::<U::Changed<'static>>();
unsafe { (*self.map.get()).get(&id) }.map(|x| {
self.count.set(self.count.get() + 1);
let u = x
.downcast_ref()
.expect("type confusion! userdata not stored under the right type id");

let r = unsafe { U::from_static_ref(u) };
let r = unsafe { from_static_ref(u) };
UserDataGuard { map: self, r }
})
}
Expand Down

0 comments on commit 4c17cd8

Please sign in to comment.