From d43535c48bdbff528eeb463ab540ccb84d112e79 Mon Sep 17 00:00:00 2001 From: Andreu Botella Date: Tue, 24 May 2022 16:26:12 +0200 Subject: [PATCH] `ShadowRealm` integration callback (#959) --- src/binding.cc | 6 ++++ src/isolate.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + tests/test_api.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 159 insertions(+), 1 deletion(-) diff --git a/src/binding.cc b/src/binding.cc index 46f9c7ba..9dfba86c 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -4,6 +4,7 @@ #include #include "support.h" +#include "v8-callbacks.h" #include "v8/include/libplatform/libplatform.h" #include "v8/include/v8-fast-api-calls.h" #include "v8/include/v8-inspector.h" @@ -263,6 +264,11 @@ void v8__Isolate__SetHostImportModuleDynamicallyCallback( HostImportModuleDynamicallyCallback); } +void v8__Isolate__SetHostCreateShadowRealmContextCallback( + v8::Isolate* isolate, v8::HostCreateShadowRealmContextCallback callback) { + isolate->SetHostCreateShadowRealmContextCallback(callback); +} + bool v8__Isolate__AddMessageListener(v8::Isolate* isolate, v8::MessageCallback callback) { return isolate->AddMessageListener(callback); diff --git a/src/isolate.rs b/src/isolate.rs index 878b6441..736c098d 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -129,6 +129,19 @@ pub type HostImportModuleDynamicallyCallback = extern "C" fn( Local, ) -> *mut Promise; +/// `HostCreateShadowRealmContextCallback` is called each time a `ShadowRealm` +/// is being constructed. You can use [`HandleScope::get_current_context`] to +/// get the [`Context`] in which the constructor is being run. +/// +/// The method combines [`Context`] creation and the implementation-defined +/// abstract operation `HostInitializeShadowRealm` into one. +/// +/// The embedder should use [`Context::new`] to create a new context. If the +/// creation fails, the embedder must propagate that exception by returning +/// [`None`]. +pub type HostCreateShadowRealmContextCallback = + for<'s> fn(scope: &mut HandleScope<'s>) -> Option>; + pub type InterruptCallback = extern "C" fn(isolate: &mut Isolate, data: *mut c_void); @@ -222,6 +235,19 @@ extern "C" { isolate: *mut Isolate, callback: HostImportModuleDynamicallyCallback, ); + #[cfg(not(target_os = "windows"))] + fn v8__Isolate__SetHostCreateShadowRealmContextCallback( + isolate: *mut Isolate, + callback: extern "C" fn(initiator_context: Local) -> *mut Context, + ); + #[cfg(target_os = "windows")] + fn v8__Isolate__SetHostCreateShadowRealmContextCallback( + isolate: *mut Isolate, + callback: extern "C" fn( + rv: *mut *mut Context, + initiator_context: Local, + ) -> *mut *mut Context, + ); fn v8__Isolate__RequestInterrupt( isolate: *const Isolate, callback: InterruptCallback, @@ -609,6 +635,56 @@ impl Isolate { } } + /// This specifies the callback called by the upcoming `ShadowRealm` + /// construction language feature to retrieve host created globals. + pub fn set_host_create_shadow_realm_context_callback( + &mut self, + callback: HostCreateShadowRealmContextCallback, + ) { + #[inline] + extern "C" fn rust_shadow_realm_callback( + initiator_context: Local, + ) -> *mut Context { + let mut scope = unsafe { CallbackScope::new(initiator_context) }; + let callback = scope + .get_slot::() + .unwrap(); + let context = callback(&mut scope); + context + .map(|l| l.as_non_null().as_ptr()) + .unwrap_or_else(null_mut) + } + + // Windows x64 ABI: MaybeLocal must be returned on the stack. + #[cfg(target_os = "windows")] + extern "C" fn rust_shadow_realm_callback_windows( + rv: *mut *mut Context, + initiator_context: Local, + ) -> *mut *mut Context { + let ret = rust_shadow_realm_callback(initiator_context); + unsafe { + rv.write(ret); + } + rv + } + + let slot_didnt_exist_before = self.set_slot(callback); + if slot_didnt_exist_before { + unsafe { + #[cfg(target_os = "windows")] + v8__Isolate__SetHostCreateShadowRealmContextCallback( + self, + rust_shadow_realm_callback_windows, + ); + #[cfg(not(target_os = "windows"))] + v8__Isolate__SetHostCreateShadowRealmContextCallback( + self, + rust_shadow_realm_callback, + ); + } + } + } + /// Add a callback to invoke in case the heap size is close to the heap limit. /// If multiple callbacks are added, only the most recently added callback is /// invoked. diff --git a/src/lib.rs b/src/lib.rs index 3bb389d1..2f5f1d6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,7 @@ pub use handle::Handle; pub use handle::Local; pub use handle::Weak; pub use isolate::HeapStatistics; +pub use isolate::HostCreateShadowRealmContextCallback; pub use isolate::HostImportModuleDynamicallyCallback; pub use isolate::HostInitializeImportMetaObjectCallback; pub use isolate::Isolate; diff --git a/tests/test_api.rs b/tests/test_api.rs index fd35747a..2ba49be8 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -35,7 +35,9 @@ fn setup() -> SetupGuard { "../third_party/icu/common/icudtl.dat" )) .is_ok()); - v8::V8::set_flags_from_string("--expose_gc --harmony-import-assertions"); + v8::V8::set_flags_from_string( + "--expose_gc --harmony-import-assertions --harmony-shadow-realm", + ); v8::V8::initialize_platform( v8::new_default_platform(0, false).make_shared(), ); @@ -6728,3 +6730,76 @@ fn finalizer_on_kept_global() { drop(weak); drop(global); } + +#[test] +fn host_create_shadow_realm_context_callback() { + let _setup_guard = setup(); + + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + { + let tc_scope = &mut v8::TryCatch::new(scope); + assert!(eval(tc_scope, "new ShadowRealm()").is_none()); + assert!(tc_scope.has_caught()); + } + + struct CheckData { + callback_called: bool, + main_context: v8::Global, + } + + let main_context = v8::Global::new(scope, context); + scope.set_slot(CheckData { + callback_called: false, + main_context, + }); + + scope.set_host_create_shadow_realm_context_callback(|scope| { + let main_context = { + let data = scope.get_slot_mut::().unwrap(); + data.callback_called = true; + data.main_context.clone() + }; + assert_eq!(scope.get_current_context(), main_context); + + // Can't return None without throwing. + let message = v8::String::new(scope, "Unsupported").unwrap(); + let exception = v8::Exception::type_error(scope, message); + scope.throw_exception(exception); + None + }); + + { + let tc_scope = &mut v8::TryCatch::new(scope); + assert!(eval(tc_scope, "new ShadowRealm()").is_none()); + assert!(tc_scope.has_caught()); + assert!(tc_scope.get_slot::().unwrap().callback_called); + } + + scope.set_host_create_shadow_realm_context_callback(|scope| { + let main_context = { + let data = scope.get_slot_mut::().unwrap(); + data.callback_called = true; + data.main_context.clone() + }; + assert_eq!(scope.get_current_context(), main_context); + + let new_context = v8::Context::new(scope); + { + let scope = &mut v8::ContextScope::new(scope, new_context); + let global = new_context.global(scope); + let key = v8::String::new(scope, "test").unwrap(); + let value = v8::Integer::new(scope, 42); + global.set(scope, key.into(), value.into()).unwrap(); + } + Some(new_context) + }); + + let value = + eval(scope, "new ShadowRealm().evaluate(`globalThis.test`)").unwrap(); + assert_eq!(value.uint32_value(scope), Some(42)); + assert!(scope.get_slot::().unwrap().callback_called); +}