diff --git a/src/binding.cc b/src/binding.cc index 935788d1..91cc61ac 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -117,6 +117,28 @@ void v8__Locker__CONSTRUCT(uninit_t& buf, v8::Isolate* isolate) { void v8__Locker__DESTRUCT(v8::Locker& self) { self.~Locker(); } +v8::Value* v8__Local__New(v8::Isolate* isolate, v8::Value* other) { + return local_to_ptr(v8::Local::New(isolate, ptr_to_local(other))); +} + +v8::Value* v8__Global__New(v8::Isolate* isolate, v8::Value* other) { + auto global = v8::Global(isolate, ptr_to_local(other)); + return global_to_ptr(global); +} + +void v8__Global__Reset__0(v8::Value*& self) { + auto global = ptr_to_global(self); + global.Reset(); + self = global_to_ptr(global); +} + +void v8__Global__Reset__2(v8::Value*& self, v8::Isolate* isolate, + v8::Value* const& other) { + auto global = ptr_to_global(self); + global.Reset(isolate, ptr_to_local(other)); + self = global_to_ptr(global); +} + void v8__ScriptCompiler__Source__CONSTRUCT( uninit_t& buf, v8::String* source_string, v8::ScriptOrigin& origin) { diff --git a/src/global.rs b/src/global.rs new file mode 100644 index 00000000..094520c1 --- /dev/null +++ b/src/global.rs @@ -0,0 +1,161 @@ +use std::mem::transmute; +use std::ptr::NonNull; + +use crate::HandleScope; +use crate::Isolate; +use crate::Local; +use crate::Value; + +extern "C" { + fn v8__Local__New(isolate: *mut Isolate, other: *mut Value) -> *mut Value; + + fn v8__Global__New(isolate: *mut Isolate, other: *mut Value) -> *mut Value; + + fn v8__Global__Reset__0(this: &mut *mut Value); + + fn v8__Global__Reset__2( + this: &mut *mut Value, + isolate: *mut Isolate, + other: &*mut Value, + ); +} + +#[repr(C)] +pub struct Global { + value: Option>, + isolate: Option>, +} + +unsafe impl Send for Global {} + +/// An object reference that is independent of any handle scope. Where +/// a Local handle only lives as long as the HandleScope in which it was +/// allocated, a global handle remains valid until it is explicitly +/// disposed using reset(). +/// +/// A global handle contains a reference to a storage cell within +/// the V8 engine which holds an object value and which is updated by +/// the garbage collector whenever the object is moved. A new storage +/// cell can be created using the constructor or Global::set and +/// existing handles can be disposed using Global::reset. +impl Global { + /// Construct a Global with no storage cell. + pub fn new() -> Self { + Self { + value: None, + isolate: None, + } + } + + /// Construct a new Global from an existing handle. When the existing handle + /// is non-empty, a new storage cell is created pointing to the same object, + /// and no flags are set. + pub fn new_from( + isolate: &mut impl AsMut, + other: impl AnyHandle, + ) -> Self { + let isolate = isolate.as_mut(); + let other_value = other.read(isolate); + Self { + value: other_value + .map(|v| unsafe { transmute(v8__Global__New(isolate, transmute(v))) }), + isolate: other_value.map(|_| isolate.into()), + } + } + + /// Returns true if this Global is empty, i.e., has not been + /// assigned an object. + pub fn is_empty(&self) -> bool { + self.value.is_none() + } + + /// Construct a Local from this global handle. + pub fn get<'sc>( + &self, + scope: &'_ mut (impl AsMut + AsMut>), + ) -> Option> { + self.check_isolate(scope.as_mut()); + match &self.value { + None => None, + Some(p) => unsafe { Local::from_raw(p.as_ptr()) }, + } + } + + /// If non-empty, destroy the underlying storage cell + /// and create a new one with the contents of other if other is non empty. + pub fn set( + &mut self, + isolate: &mut impl AsMut, + other: impl AnyHandle, + ) { + let isolate = isolate.as_mut(); + self.check_isolate(isolate); + let other_value = other.read(isolate); + match (&mut self.value, &other_value) { + (None, None) => {} + (target, None) => unsafe { + v8__Global__Reset__0( + &mut *(target as *mut Option> as *mut *mut Value), + ) + }, + (target, source) => unsafe { + v8__Global__Reset__2( + &mut *(target as *mut Option> as *mut *mut Value), + isolate, + &*(source as *const Option> as *const *mut Value), + ) + }, + } + self.isolate = other_value.map(|_| isolate.into()); + } + + /// If non-empty, destroy the underlying storage cell + /// IsEmpty() will return true after this call. + pub fn reset(&mut self, isolate: &mut impl AsMut) { + self.set(isolate, None); + } + + fn check_isolate(&self, other: &Isolate) { + match self.value { + None => assert_eq!(self.isolate, None), + Some(_) => assert_eq!(self.isolate.unwrap(), other.into()), + } + } +} + +impl Default for Global { + fn default() -> Self { + Self::new() + } +} + +impl Drop for Global { + fn drop(&mut self) { + if !self.is_empty() { + panic!("Global handle dropped while holding a value"); + } + } +} + +pub trait AnyHandle { + fn read(self, isolate: &Isolate) -> Option>; +} + +impl<'sc, T> AnyHandle for Local<'sc, T> { + fn read(self, _isolate: &Isolate) -> Option> { + Some(self.as_non_null()) + } +} + +impl<'sc, T> AnyHandle for Option> { + fn read(self, _isolate: &Isolate) -> Option> { + self.map(|local| local.as_non_null()) + } +} + +impl<'sc, T> AnyHandle for &Global { + fn read(self, isolate: &Isolate) -> Option> { + self.check_isolate(isolate); + self.value + } +} diff --git a/src/lib.rs b/src/lib.rs index 519d2ec2..9c1b2282 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ mod array_buffer; mod context; mod exception; mod function; +mod global; mod handle_scope; mod isolate; mod local; @@ -48,6 +49,7 @@ pub use exception::*; pub use function::{ Function, FunctionCallbackInfo, FunctionTemplate, ReturnValue, }; +pub use global::Global; pub use handle_scope::HandleScope; pub use isolate::Isolate; pub use isolate::OwnedIsolate; diff --git a/src/local.rs b/src/local.rs index bf69398a..e64427f7 100644 --- a/src/local.rs +++ b/src/local.rs @@ -4,7 +4,6 @@ use std::ops::Deref; use std::ops::DerefMut; use std::ptr::NonNull; -#[repr(C)] /// An object reference managed by the v8 garbage collector. /// /// All objects returned from v8 have to be tracked by the garbage @@ -38,20 +37,25 @@ use std::ptr::NonNull; /// Note: Local handles in Rusty V8 differ from the V8 C++ API in that they are /// never empty. In situations where empty handles are needed, use /// Option. +#[repr(C)] pub struct Local<'sc, T>(NonNull, PhantomData<&'sc ()>); impl<'sc, T> Copy for Local<'sc, T> {} impl<'sc, T> Clone for Local<'sc, T> { fn clone(&self) -> Self { - Self(self.0, self.1) + *self } } impl<'sc, T> Local<'sc, T> { - pub unsafe fn from_raw(ptr: *mut T) -> Option { + pub(crate) unsafe fn from_raw(ptr: *mut T) -> Option { Some(Self(NonNull::new(ptr)?, PhantomData)) } + + pub(crate) fn as_non_null(self) -> NonNull { + self.0 + } } impl<'sc, T> Deref for Local<'sc, T> { diff --git a/src/support.h b/src/support.h index 098a3403..81bae366 100644 --- a/src/support.h +++ b/src/support.h @@ -1,6 +1,7 @@ #ifndef SUPPORT_H_ #define SUPPORT_H_ +#include #include #include #include @@ -70,6 +71,22 @@ inline static v8::MaybeLocal ptr_to_maybe_local(T* ptr) { static_assert(sizeof(v8::MaybeLocal) == sizeof(T*), ""); return *reinterpret_cast*>(&ptr); } + +template +inline static T* global_to_ptr(v8::Global& global) { + static_assert(sizeof(v8::Global) == sizeof(T*), ""); + T* ptr = nullptr; + std::swap(ptr, reinterpret_cast(global)); + return ptr; +} + +template +inline static v8::Global ptr_to_global(T* ptr) { + v8::Global global; + std::swap(ptr, *reinterpret_cast(&global)); + return global; +} + } // namespace support #endif // SUPPORT_H_ diff --git a/tests/test_api.rs b/tests/test_api.rs index ebaacccd..d97ad800 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -76,6 +76,48 @@ fn handle_scope_numbers() { drop(g); } +#[test] +fn global_handles() { + let _g = setup(); + let mut params = v8::Isolate::create_params(); + params.set_array_buffer_allocator(v8::Allocator::new_default_allocator()); + let isolate = v8::Isolate::new(params); + let mut locker = v8::Locker::new(&isolate); + let mut g1 = v8::Global::::new(); + let mut g2 = v8::Global::::new(); + let mut g3 = v8::Global::::new(); + let mut g4 = v8::Global::::new(); + let g5 = v8::Global::::new(); + v8::HandleScope::enter(&mut locker, |scope| { + let l1 = v8::String::new(scope, "bla").unwrap(); + let l2 = v8::Integer::new(scope, 123); + g1.set(scope, l1); + g2.set(scope, l2); + g3.set(scope, &g2); + g4 = v8::Global::new_from(scope, l2); + }); + v8::HandleScope::enter(&mut locker, |scope| { + assert!(!g1.is_empty()); + assert_eq!(g1.get(scope).unwrap().to_rust_string_lossy(scope), "bla"); + assert!(!g2.is_empty()); + assert_eq!(g2.get(scope).unwrap().value(), 123); + assert!(!g3.is_empty()); + assert_eq!(g3.get(scope).unwrap().value(), 123); + assert!(!g4.is_empty()); + assert_eq!(g4.get(scope).unwrap().value(), 123); + assert!(g5.is_empty()); + }); + g1.reset(&mut locker); + assert!(g1.is_empty()); + g2.reset(&mut locker); + assert!(g2.is_empty()); + g3.reset(&mut locker); + assert!(g3.is_empty()); + g4.reset(&mut locker); + assert!(g4.is_empty()); + assert!(g5.is_empty()); +} + #[test] fn test_string() { setup();