diff --git a/src/scope.rs b/src/scope.rs index 753f81b7..bb6a36e3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -83,6 +83,7 @@ use crate::function::FunctionCallbackInfo; use crate::function::PropertyCallbackInfo; use crate::Context; use crate::Data; +use crate::Handle; use crate::Isolate; use crate::Local; use crate::Message; @@ -143,6 +144,24 @@ impl<'s> HandleScope<'s> { .as_scope() } + /// Opens a new `HandleScope` and enters a `Context` in one step. + /// The first argument should be an `Isolate` or `OwnedIsolate`. + /// The second argument can be any handle that refers to a `Context` object; + /// usually this will be a `Global`. + pub fn with_context< + P: param::NewHandleScopeWithContext<'s>, + H: Handle, + >( + param: &'s mut P, + context: H, + ) -> Self { + let context_ref = context.get(param.get_isolate_mut()); + param + .get_scope_data_mut() + .new_handle_scope_data_with_context(context_ref) + .as_scope() + } + /// Returns the context of the currently running JavaScript, or the context /// on the top of the stack if no JavaScript is running. pub fn get_current_context(&self) -> Local<'s, Context> { @@ -645,6 +664,22 @@ mod param { type NewScope = HandleScope<'s>; } + pub trait NewHandleScopeWithContext<'s>: data::GetScopeData { + fn get_isolate_mut(&mut self) -> &mut Isolate; + } + + impl<'s> NewHandleScopeWithContext<'s> for Isolate { + fn get_isolate_mut(&mut self) -> &mut Isolate { + self + } + } + + impl<'s> NewHandleScopeWithContext<'s> for OwnedIsolate { + fn get_isolate_mut(&mut self) -> &mut Isolate { + &mut *self + } + } + pub trait NewEscapableHandleScope<'s, 'e: 's>: data::GetScopeData { type NewScope: Scope; } @@ -846,23 +881,74 @@ pub(crate) mod data { }) } - pub(super) fn new_handle_scope_data(&mut self) -> &mut Self { + /// Implementation helper function, which creates the raw `HandleScope`, but + /// defers (maybe) entering a context to the provided callback argument. + /// This function gets called by `Self::new_handle_scope_data()` and + /// `Self::new_handle_scope_data_with_context()`. + #[inline(always)] + fn new_handle_scope_data_with(&mut self, init_context_fn: F) -> &mut Self + where + F: FnOnce( + NonNull, + &mut Cell>>, + &mut Option, + ), + { self.new_scope_data_with(|data| { let isolate = data.isolate; data.scope_type_specific_data.init_with(|| { ScopeTypeSpecificData::HandleScope { raw_handle_scope: unsafe { raw::HandleScope::uninit() }, + raw_context_scope: None, } }); match &mut data.scope_type_specific_data { - ScopeTypeSpecificData::HandleScope { raw_handle_scope } => { + ScopeTypeSpecificData::HandleScope { + raw_handle_scope, + raw_context_scope, + } => { unsafe { raw_handle_scope.init(isolate) }; + init_context_fn(isolate, &mut data.context, raw_context_scope); } _ => unreachable!(), - } + }; }) } + pub(super) fn new_handle_scope_data(&mut self) -> &mut Self { + self.new_handle_scope_data_with(|_, _, raw_context_scope| { + debug_assert!(raw_context_scope.is_none()) + }) + } + + pub(super) fn new_handle_scope_data_with_context( + &mut self, + context_ref: &Context, + ) -> &mut Self { + self.new_handle_scope_data_with( + move |isolate, context_data, raw_context_scope| unsafe { + let context_nn = NonNull::from(context_ref); + // Copy the `Context` reference to a new local handle to enure that it + // cannot get garbage collected until after this scope is dropped. + let local_context_ptr = + raw::v8__Local__New(isolate.as_ptr(), context_nn.cast().as_ptr()) + as *const Context; + let local_context_nn = + NonNull::new_unchecked(local_context_ptr as *mut _); + let local_context = Local::from_non_null(local_context_nn); + // Initialize the `raw::ContextScope`. This enters the context too. + debug_assert!(raw_context_scope.is_none()); + ptr::write( + raw_context_scope, + Some(raw::ContextScope::new(local_context)), + ); + // Also store the newly created `Local` in the `Cell` that + // serves as a look-up cache for the current context. + context_data.set(Some(local_context_nn)); + }, + ) + } + pub(super) fn new_escapable_handle_scope_data(&mut self) -> &mut Self { self.new_scope_data_with(|data| { // Note: the `raw_escape_slot` field must be initialized _before_ the @@ -1203,6 +1289,7 @@ pub(crate) mod data { }, HandleScope { raw_handle_scope: raw::HandleScope, + raw_context_scope: Option, }, EscapableHandleScope { raw_handle_scope: raw::HandleScope, @@ -1219,6 +1306,22 @@ pub(crate) mod data { } } + impl Drop for ScopeTypeSpecificData { + fn drop(&mut self) { + // For `HandleScope`s that also enter a `Context`, drop order matters. The + // context is stored in a `Local` handle, which is allocated in this + // scope's own private `raw::HandleScope`. When that `raw::HandleScope` + // is dropped first, we immediately lose the `Local` handle, + // which we need in order to exit `ContextScope`. + if let Self::HandleScope { + raw_context_scope, .. + } = self + { + *raw_context_scope = None + } + } + } + impl ScopeTypeSpecificData { pub fn is_none(&self) -> bool { match self { @@ -1272,22 +1375,21 @@ mod raw { pub(super) struct Address(NonZeroUsize); pub(super) struct ContextScope { - entered_context: *const Context, + entered_context: NonNull, } impl ContextScope { pub fn new(context: Local) -> Self { unsafe { v8__Context__Enter(&*context) }; Self { - entered_context: &*context, + entered_context: context.as_non_null(), } } } impl Drop for ContextScope { fn drop(&mut self) { - debug_assert!(!self.entered_context.is_null()); - unsafe { v8__Context__Exit(self.entered_context) }; + unsafe { v8__Context__Exit(self.entered_context.as_ptr()) }; } } @@ -1452,6 +1554,7 @@ mod raw { mod tests { use super::*; use crate::new_default_platform; + use crate::Global; use crate::V8; use std::any::type_name; use std::sync::Once; @@ -1581,104 +1684,116 @@ mod tests { initialize_v8(); let isolate = &mut Isolate::new(Default::default()); AssertTypeOf(isolate).is::(); - let l1_hs = &mut HandleScope::new(isolate); - AssertTypeOf(l1_hs).is::>(); - let context = Context::new(l1_hs); - AssertTypeOf(&HandleScope::new(l1_hs)).is::>(); + let global_context: Global; { - let l2_cxs = &mut ContextScope::new(l1_hs, context); - AssertTypeOf(l2_cxs).is::>(); - AssertTypeOf(&ContextScope::new(l2_cxs, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l2_cxs)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l2_cxs)) - .is::(); - AssertTypeOf(&TryCatch::new(l2_cxs)).is::>(); - } - { - let l2_ehs = &mut EscapableHandleScope::new(l1_hs); - AssertTypeOf(l2_ehs).is::>(); - AssertTypeOf(&HandleScope::new(l2_ehs)).is::>(); - AssertTypeOf(&EscapableHandleScope::new(l2_ehs)) - .is::>(); + let l1_hs = &mut HandleScope::new(isolate); + AssertTypeOf(l1_hs).is::>(); + let context = Context::new(l1_hs); + global_context = Global::new(l1_hs, context); + AssertTypeOf(&HandleScope::new(l1_hs)).is::>(); { - let l3_cxs = &mut ContextScope::new(l2_ehs, context); - AssertTypeOf(l3_cxs).is::>(); - AssertTypeOf(&ContextScope::new(l3_cxs, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l3_cxs)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l3_cxs)) + let l2_cxs = &mut ContextScope::new(l1_hs, context); + AssertTypeOf(l2_cxs).is::>(); + AssertTypeOf(&ContextScope::new(l2_cxs, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l2_cxs)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l2_cxs)) .is::(); + AssertTypeOf(&TryCatch::new(l2_cxs)).is::>(); + } + { + let l2_ehs = &mut EscapableHandleScope::new(l1_hs); + AssertTypeOf(l2_ehs).is::>(); + AssertTypeOf(&HandleScope::new(l2_ehs)) + .is::>(); + AssertTypeOf(&EscapableHandleScope::new(l2_ehs)) + .is::>(); { - let l4_tc = &mut TryCatch::new(l3_cxs); - AssertTypeOf(l4_tc).is::>(); - AssertTypeOf(&ContextScope::new(l4_tc, context)) + let l3_cxs = &mut ContextScope::new(l2_ehs, context); + AssertTypeOf(l3_cxs).is::>(); + AssertTypeOf(&ContextScope::new(l3_cxs, context)) .is::>(); - AssertTypeOf(&HandleScope::new(l4_tc)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l4_tc)) + AssertTypeOf(&HandleScope::new(l3_cxs)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l3_cxs)) .is::(); - AssertTypeOf(&TryCatch::new(l4_tc)) - .is::>(); + { + let l4_tc = &mut TryCatch::new(l3_cxs); + AssertTypeOf(l4_tc).is::>(); + AssertTypeOf(&ContextScope::new(l4_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l4_tc)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l4_tc)) + .is::(); + AssertTypeOf(&TryCatch::new(l4_tc)) + .is::>(); + } + } + { + let l3_tc = &mut TryCatch::new(l2_ehs); + AssertTypeOf(l3_tc).is::>>(); + AssertTypeOf(&ContextScope::new(l3_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l3_tc)) + .is::>(); + AssertTypeOf(&EscapableHandleScope::new(l3_tc)) + .is::>(); + AssertTypeOf(&TryCatch::new(l3_tc)) + .is::>>(); } } { - let l3_tc = &mut TryCatch::new(l2_ehs); - AssertTypeOf(l3_tc).is::>>(); - AssertTypeOf(&ContextScope::new(l3_tc, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l3_tc)).is::>(); - AssertTypeOf(&EscapableHandleScope::new(l3_tc)) + let l2_tc = &mut TryCatch::new(l1_hs); + AssertTypeOf(l2_tc).is::>>(); + AssertTypeOf(&ContextScope::new(l2_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l2_tc)).is::>(); + AssertTypeOf(&EscapableHandleScope::new(l2_tc)) .is::>(); - AssertTypeOf(&TryCatch::new(l3_tc)) - .is::>>(); + AssertTypeOf(&TryCatch::new(l2_tc)).is::>>(); + } + { + let l2_cbs = &mut unsafe { CallbackScope::new(context) }; + AssertTypeOf(l2_cbs).is::(); + AssertTypeOf(&ContextScope::new(l2_cbs, context)) + .is::>(); + { + let l3_hs = &mut HandleScope::new(l2_cbs); + AssertTypeOf(l3_hs).is::(); + AssertTypeOf(&ContextScope::new(l3_hs, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l3_hs)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l3_hs)) + .is::(); + AssertTypeOf(&TryCatch::new(l3_hs)).is::>(); + } + { + let l3_ehs = &mut EscapableHandleScope::new(l2_cbs); + AssertTypeOf(l3_ehs).is::(); + AssertTypeOf(&ContextScope::new(l3_ehs, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l3_ehs)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l3_ehs)) + .is::(); + AssertTypeOf(&TryCatch::new(l3_ehs)) + .is::>(); + } + { + let l3_tc = &mut TryCatch::new(l2_cbs); + AssertTypeOf(l3_tc).is::>(); + AssertTypeOf(&ContextScope::new(l3_tc, context)) + .is::>(); + AssertTypeOf(&HandleScope::new(l3_tc)).is::(); + AssertTypeOf(&EscapableHandleScope::new(l3_tc)) + .is::(); + AssertTypeOf(&TryCatch::new(l3_tc)).is::>(); + } } } { - let l2_tc = &mut TryCatch::new(l1_hs); - AssertTypeOf(l2_tc).is::>>(); - AssertTypeOf(&ContextScope::new(l2_tc, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l2_tc)).is::>(); - AssertTypeOf(&EscapableHandleScope::new(l2_tc)) - .is::>(); - AssertTypeOf(&TryCatch::new(l2_tc)).is::>>(); - } - { - let l2_cbs = &mut unsafe { CallbackScope::new(context) }; - AssertTypeOf(l2_cbs).is::(); - AssertTypeOf(&ContextScope::new(l2_cbs, context)) - .is::>(); - { - let l3_hs = &mut HandleScope::new(l2_cbs); - AssertTypeOf(l3_hs).is::(); - AssertTypeOf(&ContextScope::new(l3_hs, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l3_hs)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l3_hs)) - .is::(); - AssertTypeOf(&TryCatch::new(l3_hs)).is::>(); - } - { - let l3_ehs = &mut EscapableHandleScope::new(l2_cbs); - AssertTypeOf(l3_ehs).is::(); - AssertTypeOf(&ContextScope::new(l3_ehs, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l3_ehs)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l3_ehs)) - .is::(); - AssertTypeOf(&TryCatch::new(l3_ehs)) - .is::>(); - } - { - let l3_tc = &mut TryCatch::new(l2_cbs); - AssertTypeOf(l3_tc).is::>(); - AssertTypeOf(&ContextScope::new(l3_tc, context)) - .is::>(); - AssertTypeOf(&HandleScope::new(l3_tc)).is::(); - AssertTypeOf(&EscapableHandleScope::new(l3_tc)) - .is::(); - AssertTypeOf(&TryCatch::new(l3_tc)).is::>(); - } + AssertTypeOf(&HandleScope::with_context(isolate, &global_context)) + .is::(); + AssertTypeOf(&HandleScope::with_context(isolate, global_context)) + .is::(); } } } diff --git a/tests/test_api.rs b/tests/test_api.rs index 0a9aa1a3..068f6b09 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -265,6 +265,30 @@ fn context_scope_param_and_context_must_share_isolate() { let _context_scope_21 = &mut v8::ContextScope::new(scope2, context1); } +#[test] +#[should_panic( + expected = "attempt to use Handle in an Isolate that is not its host" +)] +fn handle_scope_param_and_context_must_share_isolate() { + let _setup_guard = setup(); + let isolate1 = &mut v8::Isolate::new(Default::default()); + let isolate2 = &mut v8::Isolate::new(Default::default()); + let global_context1; + let global_context2; + { + let scope1 = &mut v8::HandleScope::new(isolate1); + let scope2 = &mut v8::HandleScope::new(isolate2); + let local_context_1 = v8::Context::new(scope1); + let local_context_2 = v8::Context::new(scope2); + global_context1 = v8::Global::new(scope1, local_context_1); + global_context2 = v8::Global::new(scope2, local_context_2); + } + let _handle_scope_12 = + &mut v8::HandleScope::with_context(isolate1, global_context2); + let _handle_scope_21 = + &mut v8::HandleScope::with_context(isolate2, global_context1); +} + #[test] fn microtasks() { let _setup_guard = setup();