summaryrefslogtreecommitdiff
path: root/ext/node/ops/vm.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/ops/vm.rs')
-rw-r--r--ext/node/ops/vm.rs1293
1 files changed, 1204 insertions, 89 deletions
diff --git a/ext/node/ops/vm.rs b/ext/node/ops/vm.rs
index d9a16eeff..e75e05651 100644
--- a/ext/node/ops/vm.rs
+++ b/ext/node/ops/vm.rs
@@ -1,122 +1,1104 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-use deno_core::error::type_error;
-use deno_core::error::AnyError;
+use crate::create_host_defined_options;
use deno_core::op2;
+use deno_core::serde_v8;
use deno_core::v8;
+use deno_core::v8::MapFnTo;
+use deno_core::JsBuffer;
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering;
+use std::time::Duration;
-use super::vm_internal as i;
+pub const PRIVATE_SYMBOL_NAME: v8::OneByteConst =
+ v8::String::create_external_onebyte_const(b"node:contextify:context");
-pub use i::create_v8_context;
-pub use i::init_global_template;
-pub use i::ContextInitMode;
-pub use i::VM_CONTEXT_INDEX;
+/// An unbounded script that can be run in a context.
+pub struct ContextifyScript {
+ script: v8::TracedReference<v8::UnboundScript>,
+}
-pub use i::DEFINER_MAP_FN;
-pub use i::DELETER_MAP_FN;
-pub use i::DESCRIPTOR_MAP_FN;
-pub use i::ENUMERATOR_MAP_FN;
-pub use i::GETTER_MAP_FN;
-pub use i::SETTER_MAP_FN;
+impl v8::cppgc::GarbageCollected for ContextifyScript {
+ fn trace(&self, visitor: &v8::cppgc::Visitor) {
+ visitor.trace(&self.script);
+ }
+}
-pub use i::INDEXED_DEFINER_MAP_FN;
-pub use i::INDEXED_DELETER_MAP_FN;
-pub use i::INDEXED_DESCRIPTOR_MAP_FN;
-pub use i::INDEXED_GETTER_MAP_FN;
-pub use i::INDEXED_SETTER_MAP_FN;
+impl ContextifyScript {
+ #[allow(clippy::too_many_arguments)]
+ fn create<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ source: v8::Local<'s, v8::String>,
+ filename: v8::Local<'s, v8::Value>,
+ line_offset: i32,
+ column_offset: i32,
+ cached_data: Option<JsBuffer>,
+ produce_cached_data: bool,
+ parsing_context: Option<v8::Local<'s, v8::Object>>,
+ ) -> Option<CompileResult<'s>> {
+ let context = if let Some(parsing_context) = parsing_context {
+ let Some(context) =
+ ContextifyContext::from_sandbox_obj(scope, parsing_context)
+ else {
+ let message = v8::String::new_external_onebyte_static(
+ scope,
+ b"Invalid sandbox object",
+ )
+ .unwrap();
+ let exception = v8::Exception::type_error(scope, message);
+ scope.throw_exception(exception);
+ return None;
+ };
+ context.context(scope)
+ } else {
+ scope.get_current_context()
+ };
-pub struct Script {
- inner: i::ContextifyScript,
-}
+ let scope = &mut v8::ContextScope::new(scope, context);
+ let host_defined_options = create_host_defined_options(scope);
+ let origin = v8::ScriptOrigin::new(
+ scope,
+ filename,
+ line_offset,
+ column_offset,
+ true,
+ -1,
+ None,
+ false,
+ false,
+ false,
+ Some(host_defined_options),
+ );
-impl deno_core::GarbageCollected for Script {}
+ let mut source = if let Some(cached_data) = cached_data {
+ let cached_data = v8::script_compiler::CachedData::new(&cached_data);
+ v8::script_compiler::Source::new_with_cached_data(
+ source,
+ Some(&origin),
+ cached_data,
+ )
+ } else {
+ v8::script_compiler::Source::new(source, Some(&origin))
+ };
-impl Script {
- fn new(
- scope: &mut v8::HandleScope,
- source: v8::Local<v8::String>,
- ) -> Result<Self, AnyError> {
- Ok(Self {
- inner: i::ContextifyScript::new(scope, source)?,
+ let options = if source.get_cached_data().is_some() {
+ v8::script_compiler::CompileOptions::ConsumeCodeCache
+ } else {
+ v8::script_compiler::CompileOptions::NoCompileOptions
+ };
+
+ let scope = &mut v8::TryCatch::new(scope);
+
+ let Some(unbound_script) = v8::script_compiler::compile_unbound_script(
+ scope,
+ &mut source,
+ options,
+ v8::script_compiler::NoCacheReason::NoReason,
+ ) else {
+ if !scope.has_terminated() {
+ scope.rethrow();
+ }
+ return None;
+ };
+
+ let cached_data = if produce_cached_data {
+ unbound_script.create_code_cache()
+ } else {
+ None
+ };
+
+ let script = v8::TracedReference::new(scope, unbound_script);
+ let this = deno_core::cppgc::make_cppgc_object(scope, Self { script });
+
+ Some(CompileResult {
+ value: serde_v8::Value {
+ v8_value: this.into(),
+ },
+ cached_data: cached_data.as_ref().map(|c| {
+ let backing_store =
+ v8::ArrayBuffer::new_backing_store_from_vec(c.to_vec());
+ v8::ArrayBuffer::with_backing_store(scope, &backing_store.make_shared())
+ .into()
+ }),
+ cached_data_rejected: source
+ .get_cached_data()
+ .map(|c| c.rejected())
+ .unwrap_or(false),
+ cached_data_produced: cached_data.is_some(),
})
}
- fn run_in_this_context<'s>(
+ fn run_in_context<'s>(
&self,
- scope: &'s mut v8::HandleScope,
- ) -> Result<v8::Local<'s, v8::Value>, AnyError> {
- let context = scope.get_current_context();
+ scope: &mut v8::HandleScope<'s>,
+ sandbox: Option<v8::Local<'s, v8::Object>>,
+ timeout: i64,
+ display_errors: bool,
+ break_on_sigint: bool,
+ ) -> Option<v8::Local<'s, v8::Value>> {
+ let (context, microtask_queue) = if let Some(sandbox) = sandbox {
+ let Some(context) = ContextifyContext::from_sandbox_obj(scope, sandbox)
+ else {
+ let message = v8::String::new_external_onebyte_static(
+ scope,
+ b"Invalid sandbox object",
+ )
+ .unwrap();
+ let exception = v8::Exception::type_error(scope, message);
+ scope.throw_exception(exception);
+ return None;
+ };
+ (context.context(scope), context.microtask_queue())
+ } else {
+ (scope.get_current_context(), None)
+ };
+ self.eval_machine(
+ scope,
+ context,
+ timeout,
+ display_errors,
+ break_on_sigint,
+ microtask_queue,
+ )
+ }
+
+ pub fn eval_machine<'s>(
+ &self,
+ scope: &mut v8::HandleScope<'s>,
+ context: v8::Local<v8::Context>,
+ timeout: i64,
+ _display_errors: bool,
+ _break_on_sigint: bool,
+ microtask_queue: Option<&v8::MicrotaskQueue>,
+ ) -> Option<v8::Local<'s, v8::Value>> {
let context_scope = &mut v8::ContextScope::new(scope, context);
- let mut scope = v8::EscapableHandleScope::new(context_scope);
- let result = self
- .inner
- .eval_machine(&mut scope, context)
- .unwrap_or_else(|| v8::undefined(&mut scope).into());
- Ok(scope.escape(result))
+ let scope = &mut v8::EscapableHandleScope::new(context_scope);
+ let scope = &mut v8::TryCatch::new(scope);
+
+ let unbound_script = self.script.get(scope).unwrap();
+ let script = unbound_script.bind_to_current_context(scope);
+
+ let handle = scope.thread_safe_handle();
+
+ let mut run = || {
+ let r = script.run(scope);
+ if r.is_some() {
+ if let Some(mtask_queue) = microtask_queue {
+ mtask_queue.perform_checkpoint(scope);
+ }
+ }
+ r
+ };
+
+ #[allow(clippy::disallowed_types)]
+ let timed_out = std::sync::Arc::new(AtomicBool::new(false));
+ let result = if timeout != -1 {
+ let timed_out = timed_out.clone();
+ let (tx, rx) = std::sync::mpsc::channel();
+ deno_core::unsync::spawn_blocking(move || {
+ if rx
+ .recv_timeout(Duration::from_millis(timeout as _))
+ .is_err()
+ {
+ timed_out.store(true, Ordering::Relaxed);
+ handle.terminate_execution();
+ }
+ });
+ let r = run();
+ let _ = tx.send(());
+ r
+ } else {
+ run()
+ };
+
+ if timed_out.load(Ordering::Relaxed) {
+ if scope.has_terminated() {
+ scope.cancel_terminate_execution();
+ }
+ let message = v8::String::new(
+ scope,
+ &format!("Script execution timed out after {timeout}ms"),
+ )
+ .unwrap();
+ let exception = v8::Exception::error(scope, message);
+ let code_str =
+ v8::String::new_external_onebyte_static(scope, b"code").unwrap();
+ let code = v8::String::new_external_onebyte_static(
+ scope,
+ b"ERR_SCRIPT_EXECUTION_TIMEOUT",
+ )
+ .unwrap();
+ exception
+ .cast::<v8::Object>()
+ .set(scope, code_str.into(), code.into());
+ scope.throw_exception(exception);
+ }
+
+ if scope.has_caught() {
+ // If there was an exception thrown during script execution, re-throw it.
+ if !scope.has_terminated() {
+ scope.rethrow();
+ }
+
+ return None;
+ }
+
+ Some(scope.escape(result?))
}
+}
- fn run_in_context<'s>(
+pub struct ContextifyContext {
+ microtask_queue: *mut v8::MicrotaskQueue,
+ context: v8::TracedReference<v8::Context>,
+ sandbox: v8::TracedReference<v8::Object>,
+}
+
+impl deno_core::GarbageCollected for ContextifyContext {
+ fn trace(&self, visitor: &v8::cppgc::Visitor) {
+ visitor.trace(&self.context);
+ visitor.trace(&self.sandbox);
+ }
+}
+
+impl Drop for ContextifyContext {
+ fn drop(&mut self) {
+ if !self.microtask_queue.is_null() {
+ // SAFETY: If this isn't null, it is a valid MicrotaskQueue.
+ unsafe {
+ std::ptr::drop_in_place(self.microtask_queue);
+ }
+ }
+ }
+}
+
+struct AllowCodeGenWasm(bool);
+
+extern "C" fn allow_wasm_code_gen(
+ context: v8::Local<v8::Context>,
+ _source: v8::Local<v8::String>,
+) -> bool {
+ match context.get_slot::<AllowCodeGenWasm>() {
+ Some(b) => b.0,
+ None => true,
+ }
+}
+
+impl ContextifyContext {
+ pub fn attach(
+ scope: &mut v8::HandleScope,
+ sandbox_obj: v8::Local<v8::Object>,
+ _name: String,
+ _origin: String,
+ allow_code_gen_strings: bool,
+ allow_code_gen_wasm: bool,
+ own_microtask_queue: bool,
+ ) {
+ let main_context = scope.get_current_context();
+
+ let tmp = init_global_template(scope, ContextInitMode::UseSnapshot);
+
+ let microtask_queue = if own_microtask_queue {
+ v8::MicrotaskQueue::new(scope, v8::MicrotasksPolicy::Explicit).into_raw()
+ } else {
+ std::ptr::null_mut()
+ };
+
+ let context = create_v8_context(
+ scope,
+ tmp,
+ ContextInitMode::UseSnapshot,
+ microtask_queue,
+ );
+
+ let context_state = main_context.get_aligned_pointer_from_embedder_data(
+ deno_core::CONTEXT_STATE_SLOT_INDEX,
+ );
+ let module_map = main_context
+ .get_aligned_pointer_from_embedder_data(deno_core::MODULE_MAP_SLOT_INDEX);
+
+ context.set_security_token(main_context.get_security_token(scope));
+ // SAFETY: set embedder data from the creation context
+ unsafe {
+ context.set_aligned_pointer_in_embedder_data(
+ deno_core::CONTEXT_STATE_SLOT_INDEX,
+ context_state,
+ );
+ context.set_aligned_pointer_in_embedder_data(
+ deno_core::MODULE_MAP_SLOT_INDEX,
+ module_map,
+ );
+ }
+
+ scope.set_allow_wasm_code_generation_callback(allow_wasm_code_gen);
+ context.set_allow_generation_from_strings(allow_code_gen_strings);
+ context.set_slot(AllowCodeGenWasm(allow_code_gen_wasm));
+
+ let wrapper = {
+ let context = v8::TracedReference::new(scope, context);
+ let sandbox = v8::TracedReference::new(scope, sandbox_obj);
+ deno_core::cppgc::make_cppgc_object(
+ scope,
+ Self {
+ context,
+ sandbox,
+ microtask_queue,
+ },
+ )
+ };
+ let ptr =
+ deno_core::cppgc::try_unwrap_cppgc_object::<Self>(scope, wrapper.into());
+
+ // SAFETY: We are storing a pointer to the ContextifyContext
+ // in the embedder data of the v8::Context. The contextified wrapper
+ // lives longer than the execution context, so this should be safe.
+ unsafe {
+ context.set_aligned_pointer_in_embedder_data(
+ 3,
+ &*ptr.unwrap() as *const ContextifyContext as _,
+ );
+ }
+
+ let private_str =
+ v8::String::new_from_onebyte_const(scope, &PRIVATE_SYMBOL_NAME);
+ let private_symbol = v8::Private::for_api(scope, private_str);
+
+ sandbox_obj.set_private(scope, private_symbol, wrapper.into());
+ }
+
+ pub fn from_sandbox_obj<'a>(
+ scope: &mut v8::HandleScope<'a>,
+ sandbox_obj: v8::Local<v8::Object>,
+ ) -> Option<&'a Self> {
+ let private_str =
+ v8::String::new_from_onebyte_const(scope, &PRIVATE_SYMBOL_NAME);
+ let private_symbol = v8::Private::for_api(scope, private_str);
+
+ sandbox_obj
+ .get_private(scope, private_symbol)
+ .and_then(|wrapper| {
+ deno_core::cppgc::try_unwrap_cppgc_object::<Self>(scope, wrapper)
+ // SAFETY: the lifetime of the scope does not actually bind to
+ // the lifetime of this reference at all, but the object we read
+ // it from does, so it will be alive at least that long.
+ .map(|r| unsafe { &*(&*r as *const _) })
+ })
+ }
+
+ pub fn is_contextify_context(
+ scope: &mut v8::HandleScope,
+ object: v8::Local<v8::Object>,
+ ) -> bool {
+ Self::from_sandbox_obj(scope, object).is_some()
+ }
+
+ pub fn context<'a>(
+ &self,
+ scope: &mut v8::HandleScope<'a>,
+ ) -> v8::Local<'a, v8::Context> {
+ self.context.get(scope).unwrap()
+ }
+
+ fn global_proxy<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
- sandbox: v8::Local<'s, v8::Value>,
- ) -> Result<v8::Local<'s, v8::Value>, AnyError> {
- let context = if let Ok(sandbox_obj) = sandbox.try_into() {
- let context = i::ContextifyContext::from_sandbox_obj(scope, sandbox_obj)
- .ok_or_else(|| type_error("Invalid sandbox object"))?;
- context.context(scope)
+ ) -> v8::Local<'s, v8::Object> {
+ let ctx = self.context(scope);
+ ctx.global(scope)
+ }
+
+ fn sandbox<'a>(
+ &self,
+ scope: &mut v8::HandleScope<'a>,
+ ) -> v8::Local<'a, v8::Object> {
+ self.sandbox.get(scope).unwrap()
+ }
+
+ fn microtask_queue(&self) -> Option<&v8::MicrotaskQueue> {
+ if self.microtask_queue.is_null() {
+ None
} else {
- scope.get_current_context()
+ // SAFETY: If this isn't null, it is a valid MicrotaskQueue.
+ Some(unsafe { &*self.microtask_queue })
+ }
+ }
+
+ fn get<'a, 'c>(
+ scope: &mut v8::HandleScope<'a>,
+ object: v8::Local<'a, v8::Object>,
+ ) -> Option<&'c ContextifyContext> {
+ let context = object.get_creation_context(scope)?;
+
+ let context_ptr = context.get_aligned_pointer_from_embedder_data(3);
+ if context_ptr.is_null() {
+ return None;
+ }
+ // SAFETY: We are storing a pointer to the ContextifyContext
+ // in the embedder data of the v8::Context during creation.
+ Some(unsafe { &*(context_ptr as *const ContextifyContext) })
+ }
+}
+
+pub const VM_CONTEXT_INDEX: usize = 0;
+
+#[derive(PartialEq)]
+pub enum ContextInitMode {
+ ForSnapshot,
+ UseSnapshot,
+}
+
+pub fn create_v8_context<'a>(
+ scope: &mut v8::HandleScope<'a, ()>,
+ object_template: v8::Local<v8::ObjectTemplate>,
+ mode: ContextInitMode,
+ microtask_queue: *mut v8::MicrotaskQueue,
+) -> v8::Local<'a, v8::Context> {
+ let scope = &mut v8::EscapableHandleScope::new(scope);
+
+ let context = if mode == ContextInitMode::UseSnapshot {
+ v8::Context::from_snapshot(
+ scope,
+ VM_CONTEXT_INDEX,
+ v8::ContextOptions {
+ microtask_queue: Some(microtask_queue),
+ ..Default::default()
+ },
+ )
+ .unwrap()
+ } else {
+ let ctx = v8::Context::new(
+ scope,
+ v8::ContextOptions {
+ global_template: Some(object_template),
+ microtask_queue: Some(microtask_queue),
+ ..Default::default()
+ },
+ );
+ // SAFETY: ContextifyContexts will update this to a pointer to the native object
+ unsafe {
+ ctx.set_aligned_pointer_in_embedder_data(1, std::ptr::null_mut());
+ ctx.set_aligned_pointer_in_embedder_data(2, std::ptr::null_mut());
+ ctx.set_aligned_pointer_in_embedder_data(3, std::ptr::null_mut());
+ ctx.clear_all_slots();
};
+ ctx
+ };
- let context_scope = &mut v8::ContextScope::new(scope, context);
- let mut scope = v8::EscapableHandleScope::new(context_scope);
- let result = self
- .inner
- .eval_machine(&mut scope, context)
- .unwrap_or_else(|| v8::undefined(&mut scope).into());
- Ok(scope.escape(result))
+ scope.escape(context)
+}
+
+#[derive(Debug, Clone)]
+struct SlotContextifyGlobalTemplate(v8::Global<v8::ObjectTemplate>);
+
+pub fn init_global_template<'a>(
+ scope: &mut v8::HandleScope<'a, ()>,
+ mode: ContextInitMode,
+) -> v8::Local<'a, v8::ObjectTemplate> {
+ let maybe_object_template_slot =
+ scope.get_slot::<SlotContextifyGlobalTemplate>();
+
+ if maybe_object_template_slot.is_none() {
+ let global_object_template = init_global_template_inner(scope);
+
+ if mode == ContextInitMode::UseSnapshot {
+ let contextify_global_template_slot = SlotContextifyGlobalTemplate(
+ v8::Global::new(scope, global_object_template),
+ );
+ scope.set_slot(contextify_global_template_slot);
+ }
+ global_object_template
+ } else {
+ let object_template_slot = maybe_object_template_slot
+ .expect("ContextifyGlobalTemplate slot should be already populated.")
+ .clone();
+ v8::Local::new(scope, object_template_slot.0)
}
}
+// Using thread_local! to get around compiler bug.
+//
+// See NOTE in ext/node/global.rs#L12
+thread_local! {
+ pub static QUERY_MAP_FN: v8::NamedPropertyQueryCallback<'static> = property_query.map_fn_to();
+ pub static GETTER_MAP_FN: v8::NamedPropertyGetterCallback<'static> = property_getter.map_fn_to();
+ pub static SETTER_MAP_FN: v8::NamedPropertySetterCallback<'static> = property_setter.map_fn_to();
+ pub static DELETER_MAP_FN: v8::NamedPropertyDeleterCallback<'static> = property_deleter.map_fn_to();
+ pub static ENUMERATOR_MAP_FN: v8::NamedPropertyEnumeratorCallback<'static> = property_enumerator.map_fn_to();
+ pub static DEFINER_MAP_FN: v8::NamedPropertyDefinerCallback<'static> = property_definer.map_fn_to();
+ pub static DESCRIPTOR_MAP_FN: v8::NamedPropertyDescriptorCallback<'static> = property_descriptor.map_fn_to();
+}
+
+thread_local! {
+ pub static INDEXED_GETTER_MAP_FN: v8::IndexedPropertyGetterCallback<'static> = indexed_property_getter.map_fn_to();
+ pub static INDEXED_SETTER_MAP_FN: v8::IndexedPropertySetterCallback<'static> = indexed_property_setter.map_fn_to();
+ pub static INDEXED_DELETER_MAP_FN: v8::IndexedPropertyDeleterCallback<'static> = indexed_property_deleter.map_fn_to();
+ pub static INDEXED_DEFINER_MAP_FN: v8::IndexedPropertyDefinerCallback<'static> = indexed_property_definer.map_fn_to();
+ pub static INDEXED_DESCRIPTOR_MAP_FN: v8::IndexedPropertyDescriptorCallback<'static> = indexed_property_descriptor.map_fn_to();
+ pub static INDEXED_ENUMERATOR_MAP_FN: v8::IndexedPropertyEnumeratorCallback<'static> = indexed_property_enumerator.map_fn_to();
+ pub static INDEXED_QUERY_MAP_FN: v8::IndexedPropertyQueryCallback<'static> = indexed_property_query.map_fn_to();
+}
+
+pub fn init_global_template_inner<'a>(
+ scope: &mut v8::HandleScope<'a, ()>,
+) -> v8::Local<'a, v8::ObjectTemplate> {
+ let global_object_template = v8::ObjectTemplate::new(scope);
+ global_object_template.set_internal_field_count(3);
+
+ let named_property_handler_config = {
+ let mut config = v8::NamedPropertyHandlerConfiguration::new()
+ .flags(v8::PropertyHandlerFlags::HAS_NO_SIDE_EFFECT);
+
+ config = GETTER_MAP_FN.with(|getter| config.getter_raw(*getter));
+ config = SETTER_MAP_FN.with(|setter| config.setter_raw(*setter));
+ config = QUERY_MAP_FN.with(|query| config.query_raw(*query));
+ config = DELETER_MAP_FN.with(|deleter| config.deleter_raw(*deleter));
+ config =
+ ENUMERATOR_MAP_FN.with(|enumerator| config.enumerator_raw(*enumerator));
+ config = DEFINER_MAP_FN.with(|definer| config.definer_raw(*definer));
+ config =
+ DESCRIPTOR_MAP_FN.with(|descriptor| config.descriptor_raw(*descriptor));
+
+ config
+ };
+
+ let indexed_property_handler_config = {
+ let mut config = v8::IndexedPropertyHandlerConfiguration::new()
+ .flags(v8::PropertyHandlerFlags::HAS_NO_SIDE_EFFECT);
+
+ config = INDEXED_GETTER_MAP_FN.with(|getter| config.getter_raw(*getter));
+ config = INDEXED_SETTER_MAP_FN.with(|setter| config.setter_raw(*setter));
+ config = INDEXED_QUERY_MAP_FN.with(|query| config.query_raw(*query));
+ config =
+ INDEXED_DELETER_MAP_FN.with(|deleter| config.deleter_raw(*deleter));
+ config = INDEXED_ENUMERATOR_MAP_FN
+ .with(|enumerator| config.enumerator_raw(*enumerator));
+ config =
+ INDEXED_DEFINER_MAP_FN.with(|definer| config.definer_raw(*definer));
+ config = INDEXED_DESCRIPTOR_MAP_FN
+ .with(|descriptor| config.descriptor_raw(*descriptor));
+
+ config
+ };
+
+ global_object_template
+ .set_named_property_handler(named_property_handler_config);
+ global_object_template
+ .set_indexed_property_handler(indexed_property_handler_config);
+
+ global_object_template
+}
+
+fn property_query<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ property: v8::Local<'s, v8::Name>,
+ args: v8::PropertyCallbackArguments<'s>,
+ mut rv: v8::ReturnValue<v8::Integer>,
+) -> v8::Intercepted {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return v8::Intercepted::No;
+ };
+
+ let context = ctx.context(scope);
+ let scope = &mut v8::ContextScope::new(scope, context);
+ let sandbox = ctx.sandbox(scope);
+
+ match sandbox.has_real_named_property(scope, property) {
+ None => v8::Intercepted::No,
+ Some(true) => {
+ let Some(attr) =
+ sandbox.get_real_named_property_attributes(scope, property)
+ else {
+ return v8::Intercepted::No;
+ };
+ rv.set_uint32(attr.as_u32());
+ v8::Intercepted::Yes
+ }
+ Some(false) => {
+ match ctx
+ .global_proxy(scope)
+ .has_real_named_property(scope, property)
+ {
+ None => v8::Intercepted::No,
+ Some(true) => {
+ let Some(attr) = ctx
+ .global_proxy(scope)
+ .get_real_named_property_attributes(scope, property)
+ else {
+ return v8::Intercepted::No;
+ };
+ rv.set_uint32(attr.as_u32());
+ v8::Intercepted::Yes
+ }
+ Some(false) => v8::Intercepted::No,
+ }
+ }
+ }
+}
+
+fn property_getter<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ key: v8::Local<'s, v8::Name>,
+ args: v8::PropertyCallbackArguments<'s>,
+ mut ret: v8::ReturnValue,
+) -> v8::Intercepted {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return v8::Intercepted::No;
+ };
+
+ let sandbox = ctx.sandbox(scope);
+
+ let tc_scope = &mut v8::TryCatch::new(scope);
+ let maybe_rv = sandbox.get_real_named_property(tc_scope, key).or_else(|| {
+ ctx
+ .global_proxy(tc_scope)
+ .get_real_named_property(tc_scope, key)
+ });
+
+ if let Some(mut rv) = maybe_rv {
+ if tc_scope.has_caught() && !tc_scope.has_terminated() {
+ tc_scope.rethrow();
+ }
+
+ if rv == sandbox {
+ rv = ctx.global_proxy(tc_scope).into();
+ }
+
+ ret.set(rv);
+ return v8::Intercepted::Yes;
+ }
+
+ v8::Intercepted::No
+}
+
+fn property_setter<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ key: v8::Local<'s, v8::Name>,
+ value: v8::Local<'s, v8::Value>,
+ args: v8::PropertyCallbackArguments<'s>,
+ _rv: v8::ReturnValue<()>,
+) -> v8::Intercepted {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return v8::Intercepted::No;
+ };
+
+ let (attributes, is_declared_on_global_proxy) = match ctx
+ .global_proxy(scope)
+ .get_real_named_property_attributes(scope, key)
+ {
+ Some(attr) => (attr, true),
+ None => (v8::PropertyAttribute::NONE, false),
+ };
+ let mut read_only = attributes.is_read_only();
+
+ let (attributes, is_declared_on_sandbox) = match ctx
+ .sandbox(scope)
+ .get_real_named_property_attributes(scope, key)
+ {
+ Some(attr) => (attr, true),
+ None => (v8::PropertyAttribute::NONE, false),
+ };
+ read_only |= attributes.is_read_only();
+
+ if read_only {
+ return v8::Intercepted::No;
+ }
+
+ // true for x = 5
+ // false for this.x = 5
+ // false for Object.defineProperty(this, 'foo', ...)
+ // false for vmResult.x = 5 where vmResult = vm.runInContext();
+ let is_contextual_store = ctx.global_proxy(scope) != args.this();
+
+ // Indicator to not return before setting (undeclared) function declarations
+ // on the sandbox in strict mode, i.e. args.ShouldThrowOnError() = true.
+ // True for 'function f() {}', 'this.f = function() {}',
+ // 'var f = function()'.
+ // In effect only for 'function f() {}' because
+ // var f = function(), is_declared = true
+ // this.f = function() {}, is_contextual_store = false.
+ let is_function = value.is_function();
+
+ let is_declared = is_declared_on_global_proxy || is_declared_on_sandbox;
+ if !is_declared
+ && args.should_throw_on_error()
+ && is_contextual_store
+ && !is_function
+ {
+ return v8::Intercepted::No;
+ }
+
+ if !is_declared && key.is_symbol() {
+ return v8::Intercepted::No;
+ };
+
+ if ctx.sandbox(scope).set(scope, key.into(), value).is_none() {
+ return v8::Intercepted::No;
+ }
+
+ if is_declared_on_sandbox {
+ if let Some(desc) =
+ ctx.sandbox(scope).get_own_property_descriptor(scope, key)
+ {
+ if !desc.is_undefined() {
+ let desc_obj: v8::Local<v8::Object> = desc.try_into().unwrap();
+ // We have to specify the return value for any contextual or get/set
+ // property
+ let get_key =
+ v8::String::new_external_onebyte_static(scope, b"get").unwrap();
+ let set_key =
+ v8::String::new_external_onebyte_static(scope, b"set").unwrap();
+ if desc_obj
+ .has_own_property(scope, get_key.into())
+ .unwrap_or(false)
+ || desc_obj
+ .has_own_property(scope, set_key.into())
+ .unwrap_or(false)
+ {
+ return v8::Intercepted::Yes;
+ }
+ }
+ }
+ }
+
+ v8::Intercepted::No
+}
+
+fn property_descriptor<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ key: v8::Local<'s, v8::Name>,
+ args: v8::PropertyCallbackArguments<'s>,
+ mut rv: v8::ReturnValue,
+) -> v8::Intercepted {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return v8::Intercepted::No;
+ };
+
+ let context = ctx.context(scope);
+ let sandbox = ctx.sandbox(scope);
+ let scope = &mut v8::ContextScope::new(scope, context);
+
+ if sandbox.has_own_property(scope, key).unwrap_or(false) {
+ if let Some(desc) = sandbox.get_own_property_descriptor(scope, key) {
+ rv.set(desc);
+ return v8::Intercepted::Yes;
+ }
+ }
+
+ v8::Intercepted::No
+}
+
+fn property_definer<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ key: v8::Local<'s, v8::Name>,
+ desc: &v8::PropertyDescriptor,
+ args: v8::PropertyCallbackArguments<'s>,
+ _: v8::ReturnValue<()>,
+) -> v8::Intercepted {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return v8::Intercepted::No;
+ };
+
+ let context = ctx.context(scope);
+ let scope = &mut v8::ContextScope::new(scope, context);
+
+ let (attributes, is_declared) = match ctx
+ .global_proxy(scope)
+ .get_real_named_property_attributes(scope, key)
+ {
+ Some(attr) => (attr, true),
+ None => (v8::PropertyAttribute::NONE, false),
+ };
+
+ let read_only = attributes.is_read_only();
+ let dont_delete = attributes.is_dont_delete();
+
+ // If the property is set on the global as read_only, don't change it on
+ // the global or sandbox.
+ if is_declared && read_only && dont_delete {
+ return v8::Intercepted::No;
+ }
+
+ let sandbox = ctx.sandbox(scope);
+
+ let define_prop_on_sandbox =
+ |scope: &mut v8::HandleScope,
+ desc_for_sandbox: &mut v8::PropertyDescriptor| {
+ if desc.has_enumerable() {
+ desc_for_sandbox.set_enumerable(desc.enumerable());
+ }
+
+ if desc.has_configurable() {
+ desc_for_sandbox.set_configurable(desc.configurable());
+ }
+
+ sandbox.define_property(scope, key, desc_for_sandbox);
+ };
+
+ if desc.has_get() || desc.has_set() {
+ let mut desc_for_sandbox = v8::PropertyDescriptor::new_from_get_set(
+ if desc.has_get() {
+ desc.get()
+ } else {
+ v8::undefined(scope).into()
+ },
+ if desc.has_set() {
+ desc.set()
+ } else {
+ v8::undefined(scope).into()
+ },
+ );
+
+ define_prop_on_sandbox(scope, &mut desc_for_sandbox);
+ } else {
+ let value = if desc.has_value() {
+ desc.value()
+ } else {
+ v8::undefined(scope).into()
+ };
+
+ if desc.has_writable() {
+ let mut desc_for_sandbox =
+ v8::PropertyDescriptor::new_from_value_writable(value, desc.writable());
+ define_prop_on_sandbox(scope, &mut desc_for_sandbox);
+ } else {
+ let mut desc_for_sandbox = v8::PropertyDescriptor::new_from_value(value);
+ define_prop_on_sandbox(scope, &mut desc_for_sandbox);
+ }
+ }
+
+ v8::Intercepted::Yes
+}
+
+fn property_deleter<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ key: v8::Local<'s, v8::Name>,
+ args: v8::PropertyCallbackArguments<'s>,
+ mut rv: v8::ReturnValue<v8::Boolean>,
+) -> v8::Intercepted {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return v8::Intercepted::No;
+ };
+
+ let context = ctx.context(scope);
+ let sandbox = ctx.sandbox(scope);
+ let context_scope = &mut v8::ContextScope::new(scope, context);
+ if sandbox.delete(context_scope, key.into()).unwrap_or(false) {
+ return v8::Intercepted::No;
+ }
+
+ rv.set_bool(false);
+ v8::Intercepted::Yes
+}
+
+fn property_enumerator<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ args: v8::PropertyCallbackArguments<'s>,
+ mut rv: v8::ReturnValue<v8::Array>,
+) {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return;
+ };
+
+ let context = ctx.context(scope);
+ let sandbox = ctx.sandbox(scope);
+ let context_scope = &mut v8::ContextScope::new(scope, context);
+ let Some(properties) = sandbox
+ .get_property_names(context_scope, v8::GetPropertyNamesArgs::default())
+ else {
+ return;
+ };
+
+ rv.set(properties);
+}
+
+fn indexed_property_enumerator<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ args: v8::PropertyCallbackArguments<'s>,
+ mut rv: v8::ReturnValue<v8::Array>,
+) {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return;
+ };
+ let context = ctx.context(scope);
+ let scope = &mut v8::ContextScope::new(scope, context);
+
+ // By default, GetPropertyNames returns string and number property names, and
+ // doesn't convert the numbers to strings.
+ let Some(properties) = ctx
+ .sandbox(scope)
+ .get_property_names(scope, v8::GetPropertyNamesArgs::default())
+ else {
+ return;
+ };
+
+ let Ok(properties_vec) =
+ serde_v8::from_v8::<Vec<serde_v8::Value>>(scope, properties.into())
+ else {
+ return;
+ };
+
+ let mut indices = vec![];
+ for prop in properties_vec {
+ if prop.v8_value.is_number() {
+ indices.push(prop.v8_value);
+ }
+ }
+
+ rv.set(v8::Array::new_with_elements(scope, &indices));
+}
+
+fn uint32_to_name<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ index: u32,
+) -> v8::Local<'s, v8::Name> {
+ let int = v8::Integer::new_from_unsigned(scope, index);
+ let u32 = v8::Local::<v8::Uint32>::try_from(int).unwrap();
+ u32.to_string(scope).unwrap().into()
+}
+
+fn indexed_property_query<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ index: u32,
+ args: v8::PropertyCallbackArguments<'s>,
+ rv: v8::ReturnValue<v8::Integer>,
+) -> v8::Intercepted {
+ let name = uint32_to_name(scope, index);
+ property_query(scope, name, args, rv)
+}
+
+fn indexed_property_getter<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ index: u32,
+ args: v8::PropertyCallbackArguments<'s>,
+ rv: v8::ReturnValue,
+) -> v8::Intercepted {
+ let key = uint32_to_name(scope, index);
+ property_getter(scope, key, args, rv)
+}
+
+fn indexed_property_setter<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ index: u32,
+ value: v8::Local<'s, v8::Value>,
+ args: v8::PropertyCallbackArguments<'s>,
+ rv: v8::ReturnValue<()>,
+) -> v8::Intercepted {
+ let key = uint32_to_name(scope, index);
+ property_setter(scope, key, value, args, rv)
+}
+
+fn indexed_property_descriptor<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ index: u32,
+ args: v8::PropertyCallbackArguments<'s>,
+ rv: v8::ReturnValue,
+) -> v8::Intercepted {
+ let key = uint32_to_name(scope, index);
+ property_descriptor(scope, key, args, rv)
+}
+
+fn indexed_property_definer<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ index: u32,
+ descriptor: &v8::PropertyDescriptor,
+ args: v8::PropertyCallbackArguments<'s>,
+ rv: v8::ReturnValue<()>,
+) -> v8::Intercepted {
+ let key = uint32_to_name(scope, index);
+ property_definer(scope, key, descriptor, args, rv)
+}
+
+fn indexed_property_deleter<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ index: u32,
+ args: v8::PropertyCallbackArguments<'s>,
+ mut rv: v8::ReturnValue<v8::Boolean>,
+) -> v8::Intercepted {
+ let Some(ctx) = ContextifyContext::get(scope, args.this()) else {
+ return v8::Intercepted::No;
+ };
+
+ let context = ctx.context(scope);
+ let sandbox = ctx.sandbox(scope);
+ let context_scope = &mut v8::ContextScope::new(scope, context);
+ if !sandbox.delete_index(context_scope, index).unwrap_or(false) {
+ return v8::Intercepted::No;
+ }
+
+ // Delete failed on the sandbox, intercept and do not delete on
+ // the global object.
+ rv.set_bool(false);
+ v8::Intercepted::No
+}
+
+#[allow(clippy::too_many_arguments)]
#[op2]
+#[serde]
pub fn op_vm_create_script<'a>(
scope: &mut v8::HandleScope<'a>,
source: v8::Local<'a, v8::String>,
-) -> Result<v8::Local<'a, v8::Object>, AnyError> {
- let script = Script::new(scope, source)?;
- Ok(deno_core::cppgc::make_cppgc_object(scope, script))
+ filename: v8::Local<'a, v8::Value>,
+ line_offset: i32,
+ column_offset: i32,
+ #[buffer] cached_data: Option<JsBuffer>,
+ produce_cached_data: bool,
+ parsing_context: Option<v8::Local<'a, v8::Object>>,
+) -> Option<CompileResult<'a>> {
+ ContextifyScript::create(
+ scope,
+ source,
+ filename,
+ line_offset,
+ column_offset,
+ cached_data,
+ produce_cached_data,
+ parsing_context,
+ )
}
#[op2(reentrant)]
pub fn op_vm_script_run_in_context<'a>(
scope: &mut v8::HandleScope<'a>,
- #[cppgc] script: &Script,
- sandbox: v8::Local<'a, v8::Value>,
-) -> Result<v8::Local<'a, v8::Value>, AnyError> {
- script.run_in_context(scope, sandbox)
-}
-
-#[op2(reentrant)]
-pub fn op_vm_script_run_in_this_context<'a>(
- scope: &'a mut v8::HandleScope,
- #[cppgc] script: &Script,
-) -> Result<v8::Local<'a, v8::Value>, AnyError> {
- script.run_in_this_context(scope)
+ #[cppgc] script: &ContextifyScript,
+ sandbox: Option<v8::Local<'a, v8::Object>>,
+ #[serde] timeout: i64,
+ display_errors: bool,
+ break_on_sigint: bool,
+) -> Option<v8::Local<'a, v8::Value>> {
+ script.run_in_context(
+ scope,
+ sandbox,
+ timeout,
+ display_errors,
+ break_on_sigint,
+ )
}
#[op2]
pub fn op_vm_create_context(
scope: &mut v8::HandleScope,
sandbox_obj: v8::Local<v8::Object>,
+ #[string] name: String,
+ #[string] origin: String,
+ allow_code_gen_strings: bool,
+ allow_code_gen_wasm: bool,
+ own_microtask_queue: bool,
) {
// Don't allow contextifying a sandbox multiple times.
- assert!(!i::ContextifyContext::is_contextify_context(
+ assert!(!ContextifyContext::is_contextify_context(
scope,
sandbox_obj
));
- i::ContextifyContext::attach(scope, sandbox_obj);
+ ContextifyContext::attach(
+ scope,
+ sandbox_obj,
+ name,
+ origin,
+ allow_code_gen_strings,
+ allow_code_gen_wasm,
+ own_microtask_queue,
+ );
}
#[op2]
@@ -127,31 +1109,164 @@ pub fn op_vm_is_context(
sandbox_obj
.try_into()
.map(|sandbox_obj| {
- i::ContextifyContext::is_contextify_context(scope, sandbox_obj)
+ ContextifyContext::is_contextify_context(scope, sandbox_obj)
})
.unwrap_or(false)
}
-#[cfg(test)]
-mod tests {
- use super::*;
- use deno_core::v8;
+#[derive(serde::Serialize)]
+struct CompileResult<'s> {
+ value: serde_v8::Value<'s>,
+ cached_data: Option<serde_v8::Value<'s>>,
+ cached_data_rejected: bool,
+ cached_data_produced: bool,
+}
- #[test]
- fn test_run_in_this_context() {
- let platform = v8::new_default_platform(0, false).make_shared();
- deno_core::JsRuntime::init_platform(Some(platform), false);
+#[allow(clippy::too_many_arguments)]
+#[op2]
+#[serde]
+pub fn op_vm_compile_function<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ source: v8::Local<'s, v8::String>,
+ filename: v8::Local<'s, v8::Value>,
+ line_offset: i32,
+ column_offset: i32,
+ #[buffer] cached_data: Option<JsBuffer>,
+ produce_cached_data: bool,
+ parsing_context: Option<v8::Local<'s, v8::Object>>,
+ context_extensions: Option<v8::Local<'s, v8::Array>>,
+ params: Option<v8::Local<'s, v8::Array>>,
+) -> Option<CompileResult<'s>> {
+ let context = if let Some(parsing_context) = parsing_context {
+ let Some(context) =
+ ContextifyContext::from_sandbox_obj(scope, parsing_context)
+ else {
+ let message = v8::String::new(scope, "Invalid sandbox object").unwrap();
+ let exception = v8::Exception::type_error(scope, message);
+ scope.throw_exception(exception);
+ return None;
+ };
+ context.context(scope)
+ } else {
+ scope.get_current_context()
+ };
- let isolate = &mut v8::Isolate::new(Default::default());
+ let scope = &mut v8::ContextScope::new(scope, context);
+ let host_defined_options = create_host_defined_options(scope);
+ let origin = v8::ScriptOrigin::new(
+ scope,
+ filename,
+ line_offset,
+ column_offset,
+ true,
+ -1,
+ None,
+ false,
+ false,
+ false,
+ Some(host_defined_options),
+ );
- let scope = &mut v8::HandleScope::new(isolate);
- let context = v8::Context::new(scope, Default::default());
- let scope = &mut v8::ContextScope::new(scope, context);
+ let mut source = if let Some(cached_data) = cached_data {
+ let cached_data = v8::script_compiler::CachedData::new(&cached_data);
+ v8::script_compiler::Source::new_with_cached_data(
+ source,
+ Some(&origin),
+ cached_data,
+ )
+ } else {
+ v8::script_compiler::Source::new(source, Some(&origin))
+ };
- let source = v8::String::new(scope, "1 + 2").unwrap();
- let script = Script::new(scope, source).unwrap();
+ let context_extensions = if let Some(context_extensions) = context_extensions
+ {
+ let mut exts = Vec::with_capacity(context_extensions.length() as _);
+ for i in 0..context_extensions.length() {
+ let ext = context_extensions.get_index(scope, i)?.try_into().ok()?;
+ exts.push(ext);
+ }
+ exts
+ } else {
+ vec![]
+ };
- let result = script.run_in_this_context(scope).unwrap();
- assert!(result.is_number());
- }
+ let params = if let Some(params) = params {
+ let mut exts = Vec::with_capacity(params.length() as _);
+ for i in 0..params.length() {
+ let ext = params.get_index(scope, i)?.try_into().ok()?;
+ exts.push(ext);
+ }
+ exts
+ } else {
+ vec![]
+ };
+
+ let options = if source.get_cached_data().is_some() {
+ v8::script_compiler::CompileOptions::ConsumeCodeCache
+ } else {
+ v8::script_compiler::CompileOptions::NoCompileOptions
+ };
+
+ let scope = &mut v8::TryCatch::new(scope);
+
+ let Some(function) = v8::script_compiler::compile_function(
+ scope,
+ &mut source,
+ &params,
+ &context_extensions,
+ options,
+ v8::script_compiler::NoCacheReason::NoReason,
+ ) else {
+ if scope.has_caught() && !scope.has_terminated() {
+ scope.rethrow();
+ }
+ return None;
+ };
+
+ let cached_data = if produce_cached_data {
+ function.create_code_cache()
+ } else {
+ None
+ };
+
+ Some(CompileResult {
+ value: serde_v8::Value {
+ v8_value: function.into(),
+ },
+ cached_data: cached_data.as_ref().map(|c| {
+ let backing_store =
+ v8::ArrayBuffer::new_backing_store_from_vec(c.to_vec());
+ v8::ArrayBuffer::with_backing_store(scope, &backing_store.make_shared())
+ .into()
+ }),
+ cached_data_rejected: source
+ .get_cached_data()
+ .map(|c| c.rejected())
+ .unwrap_or(false),
+ cached_data_produced: cached_data.is_some(),
+ })
+}
+
+#[op2]
+pub fn op_vm_script_get_source_map_url<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ #[cppgc] script: &ContextifyScript,
+) -> v8::Local<'s, v8::Value> {
+ let unbound_script = script.script.get(scope).unwrap();
+ unbound_script.get_source_mapping_url(scope)
+}
+
+#[op2]
+pub fn op_vm_script_create_cached_data<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ #[cppgc] script: &ContextifyScript,
+) -> v8::Local<'s, v8::Value> {
+ let unbound_script = script.script.get(scope).unwrap();
+ let data = match unbound_script.create_code_cache() {
+ Some(c) => c.to_vec(),
+ None => vec![],
+ };
+ let backing_store = v8::ArrayBuffer::new_backing_store_from_vec(data);
+ v8::ArrayBuffer::with_backing_store(scope, &backing_store.make_shared())
+ .into()
}