mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-12-25 00:29:14 -05:00
Refactor handle types 'Local' and 'Global' (#410)
* Merged all handle type implementations into one file ('handle.h'). * Made it so that `Global` handles cannot be empty. * Renamed the `AsHandle` trait to `Handle`, and made it more generally useful. * Simplified how `PartialEq` is implemented for V8 heap objects and/or the `Local`/`Global` handles that reference them.
This commit is contained in:
parent
850af2e857
commit
c13625148f
7 changed files with 453 additions and 343 deletions
|
@ -220,17 +220,9 @@ const v8::Data* v8__Global__New(v8::Isolate* isolate, const v8::Data& other) {
|
|||
return make_pod<v8::Data*>(std::move(global));
|
||||
}
|
||||
|
||||
void v8__Global__Reset__0(const v8::Data*& self) {
|
||||
auto global = ptr_to_global(self);
|
||||
void v8__Global__Reset(const v8::Data* data) {
|
||||
auto global = ptr_to_global(data);
|
||||
global.Reset();
|
||||
self = make_pod<v8::Data*>(std::move(global));
|
||||
}
|
||||
|
||||
void v8__Global__Reset__2(const v8::Data*& self, v8::Isolate* isolate,
|
||||
const v8::Data* const& other) {
|
||||
auto global = ptr_to_global(self);
|
||||
global.Reset(isolate, ptr_to_local(other));
|
||||
self = make_pod<v8::Data*>(std::move(global));
|
||||
}
|
||||
|
||||
void v8__ScriptCompiler__Source__CONSTRUCT(
|
||||
|
|
37
src/data.rs
37
src/data.rs
|
@ -53,33 +53,38 @@ macro_rules! impl_eq {
|
|||
};
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn v8__Data__EQ(this: *const Data, other: *const Data) -> bool;
|
||||
fn v8__Value__StrictEquals(this: *const Value, other: *const Value) -> bool;
|
||||
}
|
||||
|
||||
macro_rules! impl_partial_eq {
|
||||
{ $rhs:ident for $type:ident use identity } => {
|
||||
impl<'s> PartialEq<Local<'s, $rhs>> for Local<'s, $type> {
|
||||
fn eq(&self, other: &Local<'s, $rhs>) -> bool {
|
||||
self.eq_identity((*other).into())
|
||||
impl<'s> PartialEq<$rhs> for $type {
|
||||
fn eq(&self, other: &$rhs) -> bool {
|
||||
unsafe {
|
||||
v8__Data__EQ(
|
||||
self as *const _ as *const Data,
|
||||
other as *const _ as *const Data,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
{ $rhs:ident for $type:ident use strict_equals } => {
|
||||
impl<'s> PartialEq<Local<'s, $rhs>> for Local<'s, $type> {
|
||||
fn eq(&self, other: &Local<'s, $rhs>) -> bool {
|
||||
self.strict_equals((*other).into())
|
||||
impl<'s> PartialEq<$rhs> for $type {
|
||||
fn eq(&self, other: &$rhs) -> bool {
|
||||
unsafe {
|
||||
v8__Value__StrictEquals(
|
||||
self as *const _ as *const Value,
|
||||
other as *const _ as *const Value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn v8__Data__EQ(this: *const Data, other: *const Data) -> bool;
|
||||
}
|
||||
|
||||
impl Data {
|
||||
fn eq_identity(&self, other: Local<Self>) -> bool {
|
||||
unsafe { v8__Data__EQ(self, &*other) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TryFromTypeError {
|
||||
expected_type: &'static str,
|
||||
|
|
179
src/global.rs
179
src/global.rs
|
@ -1,179 +0,0 @@
|
|||
use std::mem::transmute;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::Data;
|
||||
use crate::HandleScope;
|
||||
use crate::Isolate;
|
||||
use crate::IsolateHandle;
|
||||
use crate::Local;
|
||||
|
||||
extern "C" {
|
||||
fn v8__Local__New(isolate: *mut Isolate, other: *const Data) -> *const Data;
|
||||
|
||||
fn v8__Global__New(isolate: *mut Isolate, other: *const Data) -> *const Data;
|
||||
|
||||
fn v8__Global__Reset__0(this: *mut *const Data);
|
||||
|
||||
fn v8__Global__Reset__2(
|
||||
this: *mut *const Data,
|
||||
isolate: *mut Isolate,
|
||||
other: *const *const Data,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[repr(C)]
|
||||
pub struct Global<T> {
|
||||
value: Option<NonNull<T>>,
|
||||
isolate_handle: Option<IsolateHandle>,
|
||||
}
|
||||
|
||||
impl<T> Global<T> {
|
||||
/// Construct a Global with no storage cell.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
value: None,
|
||||
isolate_handle: 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(scope: &mut Isolate, other: impl AnyHandle<T>) -> Self {
|
||||
let other_value = other.read(scope);
|
||||
Self {
|
||||
value: other_value
|
||||
.map(|v| unsafe { transmute(v8__Global__New(scope, transmute(v))) }),
|
||||
isolate_handle: other_value.map(|_| scope.thread_safe_handle()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<T> from this global handle.
|
||||
pub fn get<'s>(
|
||||
&self,
|
||||
scope: &mut HandleScope<'s, ()>,
|
||||
) -> Option<Local<'s, T>> {
|
||||
self.check_isolate(scope);
|
||||
self
|
||||
.value
|
||||
.map(|g| g.as_ptr() as *const Data)
|
||||
.and_then(|g| unsafe {
|
||||
scope
|
||||
.cast_local(|sd| v8__Local__New(sd.get_isolate_ptr(), g) as *const T)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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, scope: &mut Isolate, other: impl AnyHandle<T>) {
|
||||
self.check_isolate(scope);
|
||||
let other_value = other.read(scope);
|
||||
match (&mut self.value, &other_value) {
|
||||
(None, None) => {}
|
||||
(target, None) => unsafe {
|
||||
v8__Global__Reset__0(
|
||||
&mut *(target as *mut Option<NonNull<T>> as *mut *const Data),
|
||||
)
|
||||
},
|
||||
(target, source) => unsafe {
|
||||
v8__Global__Reset__2(
|
||||
&mut *(target as *mut Option<NonNull<T>> as *mut *const Data),
|
||||
scope,
|
||||
&*(source as *const Option<NonNull<T>> as *const *const Data),
|
||||
)
|
||||
},
|
||||
}
|
||||
self.isolate_handle = other_value.map(|_| scope.thread_safe_handle());
|
||||
}
|
||||
|
||||
/// If non-empty, destroy the underlying storage cell
|
||||
/// IsEmpty() will return true after this call.
|
||||
pub fn reset(&mut self, scope: &mut Isolate) {
|
||||
self.set(scope, None);
|
||||
}
|
||||
|
||||
fn check_isolate(&self, isolate: &mut Isolate) {
|
||||
match self.value {
|
||||
None => assert!(self.isolate_handle.is_none()),
|
||||
Some(_) => assert_eq!(
|
||||
unsafe { self.isolate_handle.as_ref().unwrap().get_isolate_ptr() },
|
||||
isolate as *mut _
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Global<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Global<T> {
|
||||
fn drop(&mut self) {
|
||||
match &mut self.value {
|
||||
None => {
|
||||
// This global handle is empty.
|
||||
assert!(self.isolate_handle.is_none())
|
||||
}
|
||||
Some(_)
|
||||
if unsafe {
|
||||
self
|
||||
.isolate_handle
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_isolate_ptr()
|
||||
.is_null()
|
||||
} =>
|
||||
{
|
||||
// This global handle is associated with an Isolate that has already
|
||||
// been disposed.
|
||||
}
|
||||
addr @ Some(_) => unsafe {
|
||||
// Destroy the storage cell that contains the contents of this Global.
|
||||
v8__Global__Reset__0(
|
||||
&mut *(addr as *mut Option<NonNull<T>> as *mut *const Data),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnyHandle<T> {
|
||||
fn read(self, isolate: &mut Isolate) -> Option<NonNull<T>>;
|
||||
}
|
||||
|
||||
impl<'s, T> AnyHandle<T> for Local<'s, T> {
|
||||
fn read(self, _isolate: &mut Isolate) -> Option<NonNull<T>> {
|
||||
Some(self.as_non_null())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> AnyHandle<T> for Option<Local<'s, T>> {
|
||||
fn read(self, _isolate: &mut Isolate) -> Option<NonNull<T>> {
|
||||
self.map(|local| local.as_non_null())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> AnyHandle<T> for &Global<T> {
|
||||
fn read(self, isolate: &mut Isolate) -> Option<NonNull<T>> {
|
||||
self.check_isolate(isolate);
|
||||
self.value
|
||||
}
|
||||
}
|
373
src/handle.rs
Normal file
373
src/handle.rs
Normal file
|
@ -0,0 +1,373 @@
|
|||
use std::marker::PhantomData;
|
||||
use std::mem::transmute;
|
||||
use std::ops::Deref;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::Data;
|
||||
use crate::HandleScope;
|
||||
use crate::Isolate;
|
||||
use crate::IsolateHandle;
|
||||
|
||||
extern "C" {
|
||||
fn v8__Local__New(isolate: *mut Isolate, other: *const Data) -> *const Data;
|
||||
fn v8__Global__New(isolate: *mut Isolate, data: *const Data) -> *const Data;
|
||||
fn v8__Global__Reset(data: *const Data);
|
||||
}
|
||||
|
||||
/// An object reference managed by the v8 garbage collector.
|
||||
///
|
||||
/// All objects returned from v8 have to be tracked by the garbage
|
||||
/// collector so that it knows that the objects are still alive. Also,
|
||||
/// because the garbage collector may move objects, it is unsafe to
|
||||
/// point directly to an object. Instead, all objects are stored in
|
||||
/// handles which are known by the garbage collector and updated
|
||||
/// whenever an object moves. Handles should always be passed by value
|
||||
/// (except in cases like out-parameters) and they should never be
|
||||
/// allocated on the heap.
|
||||
///
|
||||
/// There are two types of handles: local and persistent handles.
|
||||
///
|
||||
/// Local handles are light-weight and transient and typically used in
|
||||
/// local operations. They are managed by HandleScopes. That means that a
|
||||
/// HandleScope must exist on the stack when they are created and that they are
|
||||
/// only valid inside of the `HandleScope` active during their creation.
|
||||
/// For passing a local handle to an outer `HandleScope`, an
|
||||
/// `EscapableHandleScope` and its `Escape()` method must be used.
|
||||
///
|
||||
/// Persistent handles can be used when storing objects across several
|
||||
/// independent operations and have to be explicitly deallocated when they're no
|
||||
/// longer used.
|
||||
///
|
||||
/// It is safe to extract the object stored in the handle by
|
||||
/// dereferencing the handle (for instance, to extract the *Object from
|
||||
/// a Local<Object>); the value will still be governed by a handle
|
||||
/// behind the scenes and the same rules apply to these values as to
|
||||
/// their handles.
|
||||
///
|
||||
/// 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<Local>.
|
||||
#[repr(C)]
|
||||
pub struct Local<'s, T>(NonNull<T>, PhantomData<&'s ()>);
|
||||
|
||||
impl<'s, T> Local<'s, T> {
|
||||
/// Construct a new Local from an existing Handle.
|
||||
pub fn new(
|
||||
scope: &mut HandleScope<'s, ()>,
|
||||
handle: impl Handle<Data = T>,
|
||||
) -> Self {
|
||||
let HandleInfo { data, host } = handle.get_handle_info();
|
||||
host.assert_match_isolate(scope);
|
||||
unsafe {
|
||||
scope.cast_local(|sd| {
|
||||
v8__Local__New(sd.get_isolate_ptr(), data.cast().as_ptr()) as *const T
|
||||
})
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Create a local handle by downcasting from one of its super types.
|
||||
/// This function is unsafe because the cast is unchecked.
|
||||
pub unsafe fn cast<A>(other: Local<'s, A>) -> Self
|
||||
where
|
||||
Local<'s, A>: From<Self>,
|
||||
{
|
||||
transmute(other)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_raw(ptr: *const T) -> Option<Self> {
|
||||
NonNull::new(ptr as *mut _).map(|nn| Self::from_non_null(nn))
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_non_null(nn: NonNull<T>) -> Self {
|
||||
Self(nn, PhantomData)
|
||||
}
|
||||
|
||||
pub(crate) fn as_non_null(self) -> NonNull<T> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn slice_into_raw(slice: &[Self]) -> &[*const T] {
|
||||
unsafe { &*(slice as *const [Self] as *const [*const T]) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> Copy for Local<'s, T> {}
|
||||
|
||||
impl<'s, T> Clone for Local<'s, T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> Deref for Local<'s, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub struct Global<T> {
|
||||
data: NonNull<T>,
|
||||
isolate_handle: IsolateHandle,
|
||||
}
|
||||
|
||||
impl<T> Global<T> {
|
||||
/// Construct a new Global from an existing Handle.
|
||||
pub fn new(isolate: &mut Isolate, handle: impl Handle<Data = T>) -> Self {
|
||||
let HandleInfo { data, host } = handle.get_handle_info();
|
||||
host.assert_match_isolate(isolate);
|
||||
unsafe { Self::new_raw(isolate, data) }
|
||||
}
|
||||
|
||||
/// Implementation helper function that contains the code that can be shared
|
||||
/// between `Global::new()` and `Global::clone()`.
|
||||
unsafe fn new_raw(isolate: *mut Isolate, data: NonNull<T>) -> Self {
|
||||
let data = data.cast().as_ptr();
|
||||
let data = v8__Global__New(isolate, data) as *const T;
|
||||
let data = NonNull::new_unchecked(data as *mut _);
|
||||
let isolate_handle = (*isolate).thread_safe_handle();
|
||||
Self {
|
||||
data,
|
||||
isolate_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<'a>(&'a self, scope: &mut Isolate) -> &'a T {
|
||||
Handle::get(self, scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Global<T> {
|
||||
fn clone(&self) -> Self {
|
||||
let HandleInfo { data, host } = self.get_handle_info();
|
||||
unsafe { Self::new_raw(host.get_isolate().as_mut(), data) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Global<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if self.isolate_handle.get_isolate_ptr().is_null() {
|
||||
// This `Global` handle is associated with an `Isolate` that has already
|
||||
// been disposed.
|
||||
} else {
|
||||
// Destroy the storage cell that contains the contents of this Global.
|
||||
v8__Global__Reset(self.data.cast().as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Handle: Sized {
|
||||
type Data;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn get_handle_info(&self) -> HandleInfo<Self::Data>;
|
||||
|
||||
/// Returns a reference to the V8 heap object that this handle represents.
|
||||
/// The handle does not get cloned, nor is it converted to a `Local` handle.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics in the following situations:
|
||||
/// - The handle is not hosted by the specified Isolate.
|
||||
/// - The Isolate that hosts this handle has been disposed.
|
||||
fn get<'a>(&'a self, isolate: &mut Isolate) -> &'a Self::Data {
|
||||
let HandleInfo { data, host } = self.get_handle_info();
|
||||
host.assert_match_isolate(isolate);
|
||||
unsafe { &*data.as_ptr() }
|
||||
}
|
||||
|
||||
/// Reads the inner value contained in this handle, _without_ verifying that
|
||||
/// the this handle is hosted by the currently active `Isolate`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Using a V8 heap object with another `Isolate` than the `Isolate` that
|
||||
/// hosts it is not permitted under any circumstance. Doing so leads to
|
||||
/// undefined behavior, likely a crash.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the `Isolate` that hosts the handle has been
|
||||
/// disposed.
|
||||
unsafe fn get_unchecked(&self) -> &Self::Data {
|
||||
let HandleInfo { data, host } = self.get_handle_info();
|
||||
if let HandleHost::DisposedIsolate = host {
|
||||
panic!("attempt to access Handle hosted by disposed Isolate");
|
||||
}
|
||||
&*data.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> Handle for Local<'s, T> {
|
||||
type Data = T;
|
||||
fn get_handle_info(&self) -> HandleInfo<T> {
|
||||
HandleInfo::new(self.as_non_null(), HandleHost::Scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 's: 'a, T> Handle for &'a Local<'s, T> {
|
||||
type Data = T;
|
||||
fn get_handle_info(&self) -> HandleInfo<T> {
|
||||
HandleInfo::new(self.as_non_null(), HandleHost::Scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Handle for Global<T> {
|
||||
type Data = T;
|
||||
fn get_handle_info(&self) -> HandleInfo<T> {
|
||||
HandleInfo::new(self.data, (&self.isolate_handle).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Handle for &'a Global<T> {
|
||||
type Data = T;
|
||||
fn get_handle_info(&self) -> HandleInfo<T> {
|
||||
HandleInfo::new(self.data, (&self.isolate_handle).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T, Rhs: Handle> PartialEq<Rhs> for Local<'s, T>
|
||||
where
|
||||
T: PartialEq<Rhs::Data>,
|
||||
{
|
||||
fn eq(&self, other: &Rhs) -> bool {
|
||||
let i1 = self.get_handle_info();
|
||||
let i2 = other.get_handle_info();
|
||||
i1.host.match_host(i2.host, None)
|
||||
&& unsafe { i1.data.as_ref() == i2.data.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T, Rhs: Handle> PartialEq<Rhs> for Global<T>
|
||||
where
|
||||
T: PartialEq<Rhs::Data>,
|
||||
{
|
||||
fn eq(&self, other: &Rhs) -> bool {
|
||||
let i1 = self.get_handle_info();
|
||||
let i2 = other.get_handle_info();
|
||||
i1.host.match_host(i2.host, None)
|
||||
&& unsafe { i1.data.as_ref() == i2.data.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct HandleInfo<T> {
|
||||
data: NonNull<T>,
|
||||
host: HandleHost,
|
||||
}
|
||||
|
||||
impl<T> HandleInfo<T> {
|
||||
fn new(data: NonNull<T>, host: HandleHost) -> Self {
|
||||
Self { data, host }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum HandleHost {
|
||||
// Note: the `HandleHost::Scope` variant does not indicate that the handle
|
||||
// it applies to is not associated with an `Isolate`. It only means that
|
||||
// the handle is a `Local` handle that was unable to provide a pointer to
|
||||
// the `Isolate` that hosts it (the handle) and the currently entered
|
||||
// scope.
|
||||
Scope,
|
||||
Isolate(NonNull<Isolate>),
|
||||
DisposedIsolate,
|
||||
}
|
||||
|
||||
impl From<&'_ mut Isolate> for HandleHost {
|
||||
fn from(isolate: &'_ mut Isolate) -> Self {
|
||||
Self::Isolate(NonNull::from(isolate))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'_ IsolateHandle> for HandleHost {
|
||||
fn from(isolate_handle: &IsolateHandle) -> Self {
|
||||
NonNull::new(unsafe { isolate_handle.get_isolate_ptr() })
|
||||
.map(Self::Isolate)
|
||||
.unwrap_or(Self::DisposedIsolate)
|
||||
}
|
||||
}
|
||||
|
||||
impl HandleHost {
|
||||
/// Compares two `HandleHost` values, returning `true` if they refer to the
|
||||
/// same `Isolate`, or `false` if they refer to different isolates.
|
||||
///
|
||||
/// If the caller knows which `Isolate` the currently entered scope (if any)
|
||||
/// belongs to, it should pass on this information via the second argument
|
||||
/// (`scope_isolate_opt`).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if one of the `HandleHost` values refers to an
|
||||
/// `Isolate` that has been disposed.
|
||||
///
|
||||
/// # Safety / Bugs
|
||||
///
|
||||
/// The current implementation is a bit too forgiving. If it cannot decide
|
||||
/// whether two hosts refer to the same `Isolate`, it just returns `true`.
|
||||
/// Note that this can only happen when the caller does _not_ provide a value
|
||||
/// for the `scope_isolate_opt` argument.
|
||||
fn match_host(
|
||||
self,
|
||||
other: Self,
|
||||
scope_isolate_opt: Option<&mut Isolate>,
|
||||
) -> bool {
|
||||
let scope_isolate_opt_nn = scope_isolate_opt.map(NonNull::from);
|
||||
match (self, other, scope_isolate_opt_nn) {
|
||||
(Self::Scope, Self::Scope, _) => true,
|
||||
(Self::Isolate(ile1), Self::Isolate(ile2), _) => ile1 == ile2,
|
||||
(Self::Scope, Self::Isolate(ile1), Some(ile2)) => ile1 == ile2,
|
||||
(Self::Isolate(ile1), Self::Scope, Some(ile2)) => ile1 == ile2,
|
||||
// TODO(pisciaureus): If the caller didn't provide a `scope_isolate_opt`
|
||||
// value that works, we can't do a meaningful check. So all we do for now
|
||||
// is pretend the Isolates match and hope for the best. This eventually
|
||||
// needs to be tightened up.
|
||||
(Self::Scope, Self::Isolate(_), _) => true,
|
||||
(Self::Isolate(_), Self::Scope, _) => true,
|
||||
// Handles hosted in an Isolate that has been disposed aren't good for
|
||||
// anything, even if a pair of handles used to to be hosted in the same
|
||||
// now-disposed solate.
|
||||
(Self::DisposedIsolate, ..) | (_, Self::DisposedIsolate, _) => {
|
||||
panic!("attempt to access Handle hosted by disposed Isolate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_match_host(self, other: Self, scope_opt: Option<&mut Isolate>) {
|
||||
assert!(
|
||||
self.match_host(other, scope_opt),
|
||||
"attempt to use Handle in an Isolate that is not its host"
|
||||
)
|
||||
}
|
||||
|
||||
fn match_isolate(self, isolate: &mut Isolate) -> bool {
|
||||
self.match_host(isolate.into(), Some(isolate))
|
||||
}
|
||||
|
||||
fn assert_match_isolate(self, isolate: &mut Isolate) {
|
||||
self.assert_match_host(isolate.into(), Some(isolate))
|
||||
}
|
||||
|
||||
fn get_isolate(self) -> NonNull<Isolate> {
|
||||
match self {
|
||||
Self::Scope => panic!("host Isolate for Handle not available"),
|
||||
Self::Isolate(ile) => ile,
|
||||
Self::DisposedIsolate => panic!("attempt to access disposed Isolate"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_isolate_handle(self) -> IsolateHandle {
|
||||
unsafe { self.get_isolate().as_mut().thread_safe_handle() }
|
||||
}
|
||||
}
|
|
@ -40,10 +40,9 @@ mod data;
|
|||
mod exception;
|
||||
mod external_references;
|
||||
mod function;
|
||||
mod global;
|
||||
mod handle;
|
||||
mod isolate;
|
||||
mod isolate_create_params;
|
||||
mod local;
|
||||
mod module;
|
||||
mod number;
|
||||
mod object;
|
||||
|
@ -78,7 +77,9 @@ pub use exception::*;
|
|||
pub use external_references::ExternalReference;
|
||||
pub use external_references::ExternalReferences;
|
||||
pub use function::*;
|
||||
pub use global::Global;
|
||||
pub use handle::Global;
|
||||
pub use handle::Handle;
|
||||
pub use handle::Local;
|
||||
pub use isolate::HostImportModuleDynamicallyCallback;
|
||||
pub use isolate::HostInitializeImportMetaObjectCallback;
|
||||
pub use isolate::Isolate;
|
||||
|
@ -87,7 +88,6 @@ pub use isolate::MessageCallback;
|
|||
pub use isolate::OwnedIsolate;
|
||||
pub use isolate::PromiseRejectCallback;
|
||||
pub use isolate_create_params::CreateParams;
|
||||
pub use local::Local;
|
||||
pub use module::*;
|
||||
pub use object::*;
|
||||
pub use platform::new_default_platform;
|
||||
|
|
90
src/local.rs
90
src/local.rs
|
@ -1,90 +0,0 @@
|
|||
use std::marker::PhantomData;
|
||||
use std::mem::transmute;
|
||||
use std::ops::Deref;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
/// An object reference managed by the v8 garbage collector.
|
||||
///
|
||||
/// All objects returned from v8 have to be tracked by the garbage
|
||||
/// collector so that it knows that the objects are still alive. Also,
|
||||
/// because the garbage collector may move objects, it is unsafe to
|
||||
/// point directly to an object. Instead, all objects are stored in
|
||||
/// handles which are known by the garbage collector and updated
|
||||
/// whenever an object moves. Handles should always be passed by value
|
||||
/// (except in cases like out-parameters) and they should never be
|
||||
/// allocated on the heap.
|
||||
///
|
||||
/// There are two types of handles: local and persistent handles.
|
||||
///
|
||||
/// Local handles are light-weight and transient and typically used in
|
||||
/// local operations. They are managed by HandleScopes. That means that a
|
||||
/// HandleScope must exist on the stack when they are created and that they are
|
||||
/// only valid inside of the HandleScope active during their creation.
|
||||
/// For passing a local handle to an outer HandleScope, an EscapableHandleScope
|
||||
/// and its Escape() method must be used.
|
||||
///
|
||||
/// Persistent handles can be used when storing objects across several
|
||||
/// independent operations and have to be explicitly deallocated when they're no
|
||||
/// longer used.
|
||||
///
|
||||
/// It is safe to extract the object stored in the handle by
|
||||
/// dereferencing the handle (for instance, to extract the *Object from
|
||||
/// a Local<Object>); the value will still be governed by a handle
|
||||
/// behind the scenes and the same rules apply to these values as to
|
||||
/// their handles.
|
||||
///
|
||||
/// 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<Local>.
|
||||
#[repr(C)]
|
||||
pub struct Local<'s, T>(NonNull<T>, PhantomData<&'s ()>);
|
||||
|
||||
impl<'s, T> Copy for Local<'s, T> {}
|
||||
|
||||
impl<'s, T> Clone for Local<'s, T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> Local<'s, T> {
|
||||
/// Create a local handle by downcasting from one of its super types.
|
||||
/// This function is unsafe because the cast is unchecked.
|
||||
pub unsafe fn cast<A>(other: Local<'s, A>) -> Self
|
||||
where
|
||||
Local<'s, A>: From<Self>,
|
||||
{
|
||||
transmute(other)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_raw(ptr: *const T) -> Option<Self> {
|
||||
NonNull::new(ptr as *mut _).map(|nn| Self::from_non_null(nn))
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_non_null(nn: NonNull<T>) -> Self {
|
||||
Self(nn, PhantomData)
|
||||
}
|
||||
|
||||
pub(crate) fn as_non_null(self) -> NonNull<T> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn slice_into_raw(slice: &[Self]) -> &[*const T] {
|
||||
unsafe { &*(slice as *const [Self] as *const [*const T]) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> Deref for Local<'s, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_size_of_local() {
|
||||
use crate::Value;
|
||||
use std::mem::size_of;
|
||||
assert_eq!(size_of::<Local<Value>>(), size_of::<*const Value>());
|
||||
assert_eq!(size_of::<Option<Local<Value>>>(), size_of::<*const Value>());
|
||||
}
|
|
@ -87,47 +87,38 @@ fn handle_scope_non_lexical_lifetime() {
|
|||
fn global_handles() {
|
||||
let _setup_guard = setup();
|
||||
let isolate = &mut v8::Isolate::new(Default::default());
|
||||
let mut g1 = v8::Global::<v8::String>::new();
|
||||
let mut g2 = v8::Global::<v8::Integer>::new();
|
||||
let mut g3 = v8::Global::<v8::Integer>::new();
|
||||
let mut _g4 = v8::Global::<v8::Integer>::new();
|
||||
let g5 = v8::Global::<v8::Script>::new();
|
||||
let mut g6 = v8::Global::<v8::Integer>::new();
|
||||
let g1: v8::Global<v8::String>;
|
||||
let mut g2: Option<v8::Global<v8::Integer>> = None;
|
||||
let g3: v8::Global<v8::Integer>;
|
||||
let g4: v8::Global<v8::Integer>;
|
||||
let mut g5: Option<v8::Global<v8::Integer>> = None;
|
||||
let g6;
|
||||
{
|
||||
let scope = &mut v8::HandleScope::new(isolate);
|
||||
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);
|
||||
let l6 = v8::Integer::new(scope, 100);
|
||||
g6.set(scope, l6);
|
||||
g1 = v8::Global::new(scope, l1);
|
||||
g2.replace(v8::Global::new(scope, l2));
|
||||
g3 = v8::Global::new(scope, g2.as_ref().unwrap());
|
||||
g4 = v8::Global::new(scope, l2);
|
||||
let l5 = v8::Integer::new(scope, 100);
|
||||
g5.replace(v8::Global::new(scope, l5));
|
||||
g6 = g1.clone();
|
||||
}
|
||||
{
|
||||
let scope = &mut v8::HandleScope::new(isolate);
|
||||
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());
|
||||
let num = g6.get(scope).unwrap();
|
||||
g6.reset(scope);
|
||||
assert_eq!(num.value(), 100);
|
||||
assert_eq!(g1.get(scope).to_rust_string_lossy(scope), "bla");
|
||||
assert_eq!(g2.as_ref().unwrap().get(scope).value(), 123);
|
||||
assert_eq!(g3.get(scope).value(), 123);
|
||||
assert_eq!(g4.get(scope).value(), 123);
|
||||
{
|
||||
let num = g5.as_ref().unwrap().get(scope);
|
||||
assert_eq!(num.value(), 100);
|
||||
}
|
||||
g5.take();
|
||||
assert!(g6 == g1);
|
||||
assert_eq!(g6.get(scope).to_rust_string_lossy(scope), "bla");
|
||||
}
|
||||
g1.reset(isolate);
|
||||
assert!(g1.is_empty());
|
||||
g2.reset(isolate);
|
||||
assert!(g2.is_empty());
|
||||
g3.reset(isolate);
|
||||
assert!(g3.is_empty());
|
||||
_g4.reset(isolate);
|
||||
assert!(_g4.is_empty());
|
||||
assert!(g5.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -135,17 +126,17 @@ fn global_handle_drop() {
|
|||
let _setup_guard = setup();
|
||||
|
||||
// Global 'g1' will be dropped _after_ the Isolate has been disposed.
|
||||
let mut g1 = v8::Global::<v8::String>::new();
|
||||
let _g1: v8::Global<v8::String>;
|
||||
|
||||
let isolate = &mut v8::Isolate::new(Default::default());
|
||||
let scope = &mut v8::HandleScope::new(isolate);
|
||||
|
||||
let l1 = v8::String::new(scope, "foo").unwrap();
|
||||
g1.set(scope, l1);
|
||||
_g1 = v8::Global::new(scope, l1);
|
||||
|
||||
// Global 'g2' will be dropped _before_ the Isolate has been disposed.
|
||||
let l2 = v8::String::new(scope, "bar").unwrap();
|
||||
let _g2 = v8::Global::new_from(scope, l2);
|
||||
let _g2 = v8::Global::new(scope, l2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -341,10 +332,10 @@ fn get_isolate_from_handle() {
|
|||
// Check that we can get the isolate from a Local.
|
||||
check_handle_helper(scope, expect_some, local);
|
||||
|
||||
// Check that we can still get it after converting it to a Global and back.
|
||||
let global = v8::Global::new_from(scope, local);
|
||||
let local = global.get(scope).unwrap();
|
||||
check_handle_helper(scope, expect_some, local);
|
||||
// Check that we can still get it after converting it to a Global.
|
||||
let global = v8::Global::new(scope, local);
|
||||
let local2 = v8::Local::new(scope, &global);
|
||||
check_handle_helper(scope, expect_some, local2);
|
||||
};
|
||||
|
||||
fn check_eval<'s>(
|
||||
|
@ -1997,6 +1988,9 @@ fn value_checker() {
|
|||
assert!(value == value);
|
||||
assert!(value == v8::Local::<v8::Primitive>::try_from(value).unwrap());
|
||||
assert!(value == v8::null(scope));
|
||||
assert!(value == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == v8::null(scope));
|
||||
assert!(value != v8::undefined(scope));
|
||||
assert!(value != v8::Boolean::new(scope, false));
|
||||
assert!(value != v8::Integer::new(scope, 0));
|
||||
|
@ -2008,6 +2002,10 @@ fn value_checker() {
|
|||
assert!(value == value);
|
||||
assert!(value == v8::Local::<v8::Boolean>::try_from(value).unwrap());
|
||||
assert!(value == v8::Boolean::new(scope, true));
|
||||
assert!(value == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == eval(scope, "!false").unwrap());
|
||||
assert!(v8::Global::new(scope, value) != eval(scope, "1").unwrap());
|
||||
assert!(value != v8::Boolean::new(scope, false));
|
||||
|
||||
let value = eval(scope, "false").unwrap();
|
||||
|
@ -2017,6 +2015,10 @@ fn value_checker() {
|
|||
assert!(value == value);
|
||||
assert!(value == v8::Local::<v8::Boolean>::try_from(value).unwrap());
|
||||
assert!(value == v8::Boolean::new(scope, false));
|
||||
assert!(value == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == eval(scope, "!true").unwrap());
|
||||
assert!(v8::Global::new(scope, value) != eval(scope, "0").unwrap());
|
||||
assert!(value != v8::Boolean::new(scope, true));
|
||||
assert!(value != v8::null(scope));
|
||||
assert!(value != v8::undefined(scope));
|
||||
|
@ -2036,22 +2038,27 @@ fn value_checker() {
|
|||
assert!(value.is_symbol());
|
||||
assert!(value == value);
|
||||
assert!(value == v8::Local::<v8::Symbol>::try_from(value).unwrap());
|
||||
assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap());
|
||||
assert!(value == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == v8::Global::new(scope, value));
|
||||
assert!(value != eval(scope, "Symbol()").unwrap());
|
||||
assert!(v8::Global::new(scope, value) != eval(scope, "Symbol()").unwrap());
|
||||
|
||||
let value = eval(scope, "() => 0").unwrap();
|
||||
assert!(value.is_function());
|
||||
assert!(value == value);
|
||||
assert!(value == v8::Local::<v8::Function>::try_from(value).unwrap());
|
||||
assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap());
|
||||
assert!(value == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == v8::Global::new(scope, value));
|
||||
assert!(value != eval(scope, "() => 0").unwrap());
|
||||
assert!(v8::Global::new(scope, value) != eval(scope, "() => 0").unwrap());
|
||||
|
||||
let value = eval(scope, "async () => 0").unwrap();
|
||||
assert!(value.is_async_function());
|
||||
assert!(value == value);
|
||||
assert!(value == v8::Local::<v8::Function>::try_from(value).unwrap());
|
||||
assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap());
|
||||
assert!(v8::Global::new(scope, value) == v8::Global::new(scope, value));
|
||||
assert!(value != v8::Object::new(scope));
|
||||
assert!(v8::Global::new(scope, value) != v8::Object::new(scope));
|
||||
|
||||
let value = eval(scope, "[]").unwrap();
|
||||
assert!(value.is_array());
|
||||
|
@ -2128,8 +2135,10 @@ fn value_checker() {
|
|||
assert!(value.is_object());
|
||||
assert!(value == value);
|
||||
assert!(value == v8::Local::<v8::Object>::try_from(value).unwrap());
|
||||
assert!(value == v8::Global::new_from(scope, value).get(scope).unwrap());
|
||||
assert!(value == v8::Global::new(scope, value));
|
||||
assert!(v8::Global::new(scope, value) == v8::Global::new(scope, value));
|
||||
assert!(value != v8::Object::new(scope));
|
||||
assert!(v8::Global::new(scope, value) != v8::Object::new(scope));
|
||||
|
||||
let value = eval(scope, "new Date()").unwrap();
|
||||
assert!(value.is_date());
|
||||
|
|
Loading…
Reference in a new issue