From 897159dc6e1b2319cf2f5f09d8d6cecc0d3175fa Mon Sep 17 00:00:00 2001 From: snek Date: Tue, 6 Aug 2024 14:52:53 +0200 Subject: [PATCH] feat: vm rewrite (#24596) rewrite vm implementation to increase compat. vm.Module+importModuleDynamically callbacks should be added in a followup. --- ext/node/lib.rs | 31 +- ext/node/ops/mod.rs | 1 - ext/node/ops/vm.rs | 1317 +++++++++++++++-- ext/node/polyfills/assert.ts | 3 + ext/node/polyfills/vm.js | 359 +++++ ext/node/polyfills/vm.ts | 100 -- runtime/snapshot.rs | 1 + tests/node_compat/config.jsonc | 49 +- tests/node_compat/runner/TODO.md | 42 - tests/node_compat/test.ts | 31 +- tests/node_compat/test/common/index.js | 7 +- .../parallel/test-vm-access-process-env.js | 40 + ...t-vm-attributes-property-not-on-sandbox.js | 25 + .../test/parallel/test-vm-codegen.js | 108 ++ .../parallel/test-vm-context-async-script.js | 42 + .../test-vm-context-property-forwarding.js | 72 + .../test-vm-create-and-run-in-context.js | 57 + .../test-vm-create-context-accessors.js | 56 + .../parallel/test-vm-create-context-arg.js | 47 + ...st-vm-create-context-circular-reference.js | 41 + .../test/parallel/test-vm-createcacheddata.js | 29 + .../test/parallel/test-vm-cross-context.js | 36 + .../test-vm-data-property-writable.js | 35 + .../parallel/test-vm-deleting-property.js | 22 + .../parallel/test-vm-function-declaration.js | 56 + .../parallel/test-vm-function-redefinition.js | 18 + .../test/parallel/test-vm-getters.js | 31 + .../parallel/test-vm-global-assignment.js | 22 + .../test-vm-global-define-property.js | 54 + .../test/parallel/test-vm-global-identity.js | 39 + .../test/parallel/test-vm-global-setter.js | 168 +++ .../test/parallel/test-vm-harmony-symbols.js | 44 + .../parallel/test-vm-indexed-properties.js | 24 + .../parallel/test-vm-inherited_properties.js | 45 + .../test/parallel/test-vm-is-context.js | 53 + .../test/parallel/test-vm-low-stack-space.js | 33 + .../test-vm-new-script-new-context.js | 114 ++ .../test/parallel/test-vm-not-strict.js | 44 + .../parallel/test-vm-options-validation.js | 101 ++ ...st-vm-parse-abort-on-uncaught-exception.js | 25 + .../parallel/test-vm-preserves-property.js | 32 + .../test-vm-property-not-on-sandbox.js | 44 + .../test/parallel/test-vm-proxies.js | 25 + .../test/parallel/test-vm-proxy-failure-CP.js | 22 + .../test-vm-script-throw-in-tostring.js | 21 + .../parallel/test-vm-set-property-proxy.js | 23 + .../test-vm-set-proto-null-on-globalthis.js | 20 + .../test/parallel/test-vm-source-map-url.js | 34 + .../test/parallel/test-vm-strict-mode.js | 21 + .../test/parallel/test-vm-symbols.js | 30 + .../test-vm-timeout-escape-promise-2.js | 45 + .../test-vm-timeout-escape-promise.js | 46 + .../test/parallel/test-vm-timeout.js | 88 ++ tests/unit_node/vm_test.ts | 2 +- 54 files changed, 3609 insertions(+), 266 deletions(-) create mode 100644 ext/node/polyfills/vm.js delete mode 100644 ext/node/polyfills/vm.ts create mode 100644 tests/node_compat/test/parallel/test-vm-access-process-env.js create mode 100644 tests/node_compat/test/parallel/test-vm-attributes-property-not-on-sandbox.js create mode 100644 tests/node_compat/test/parallel/test-vm-codegen.js create mode 100644 tests/node_compat/test/parallel/test-vm-context-async-script.js create mode 100644 tests/node_compat/test/parallel/test-vm-context-property-forwarding.js create mode 100644 tests/node_compat/test/parallel/test-vm-create-and-run-in-context.js create mode 100644 tests/node_compat/test/parallel/test-vm-create-context-accessors.js create mode 100644 tests/node_compat/test/parallel/test-vm-create-context-arg.js create mode 100644 tests/node_compat/test/parallel/test-vm-create-context-circular-reference.js create mode 100644 tests/node_compat/test/parallel/test-vm-createcacheddata.js create mode 100644 tests/node_compat/test/parallel/test-vm-cross-context.js create mode 100644 tests/node_compat/test/parallel/test-vm-data-property-writable.js create mode 100644 tests/node_compat/test/parallel/test-vm-deleting-property.js create mode 100644 tests/node_compat/test/parallel/test-vm-function-declaration.js create mode 100644 tests/node_compat/test/parallel/test-vm-function-redefinition.js create mode 100644 tests/node_compat/test/parallel/test-vm-getters.js create mode 100644 tests/node_compat/test/parallel/test-vm-global-assignment.js create mode 100644 tests/node_compat/test/parallel/test-vm-global-define-property.js create mode 100644 tests/node_compat/test/parallel/test-vm-global-identity.js create mode 100644 tests/node_compat/test/parallel/test-vm-global-setter.js create mode 100644 tests/node_compat/test/parallel/test-vm-harmony-symbols.js create mode 100644 tests/node_compat/test/parallel/test-vm-indexed-properties.js create mode 100644 tests/node_compat/test/parallel/test-vm-inherited_properties.js create mode 100644 tests/node_compat/test/parallel/test-vm-is-context.js create mode 100644 tests/node_compat/test/parallel/test-vm-low-stack-space.js create mode 100644 tests/node_compat/test/parallel/test-vm-new-script-new-context.js create mode 100644 tests/node_compat/test/parallel/test-vm-not-strict.js create mode 100644 tests/node_compat/test/parallel/test-vm-options-validation.js create mode 100644 tests/node_compat/test/parallel/test-vm-parse-abort-on-uncaught-exception.js create mode 100644 tests/node_compat/test/parallel/test-vm-preserves-property.js create mode 100644 tests/node_compat/test/parallel/test-vm-property-not-on-sandbox.js create mode 100644 tests/node_compat/test/parallel/test-vm-proxies.js create mode 100644 tests/node_compat/test/parallel/test-vm-proxy-failure-CP.js create mode 100644 tests/node_compat/test/parallel/test-vm-script-throw-in-tostring.js create mode 100644 tests/node_compat/test/parallel/test-vm-set-property-proxy.js create mode 100644 tests/node_compat/test/parallel/test-vm-set-proto-null-on-globalthis.js create mode 100644 tests/node_compat/test/parallel/test-vm-source-map-url.js create mode 100644 tests/node_compat/test/parallel/test-vm-strict-mode.js create mode 100644 tests/node_compat/test/parallel/test-vm-symbols.js create mode 100644 tests/node_compat/test/parallel/test-vm-timeout-escape-promise-2.js create mode 100644 tests/node_compat/test/parallel/test-vm-timeout-escape-promise.js create mode 100644 tests/node_compat/test/parallel/test-vm-timeout.js diff --git a/ext/node/lib.rs b/ext/node/lib.rs index a4a757996e..00070fae99 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -303,8 +303,10 @@ deno_core::extension!(deno_node, ops::vm::op_vm_create_script, ops::vm::op_vm_create_context, ops::vm::op_vm_script_run_in_context, - ops::vm::op_vm_script_run_in_this_context, ops::vm::op_vm_is_context, + ops::vm::op_vm_compile_function, + ops::vm::op_vm_script_get_source_map_url, + ops::vm::op_vm_script_create_cached_data, ops::idna::op_node_idna_domain_to_ascii, ops::idna::op_node_idna_domain_to_unicode, ops::idna::op_node_idna_punycode_to_ascii, @@ -620,7 +622,7 @@ deno_core::extension!(deno_node, "node:util" = "util.ts", "node:util/types" = "util/types.ts", "node:v8" = "v8.ts", - "node:vm" = "vm.ts", + "node:vm" = "vm.js", "node:worker_threads" = "worker_threads.ts", "node:zlib" = "zlib.ts", ], @@ -643,6 +645,11 @@ deno_core::extension!(deno_node, customizer = |ext: &mut deno_core::Extension| { let mut external_references = Vec::with_capacity(14); + vm::QUERY_MAP_FN.with(|query| { + external_references.push(ExternalReference { + named_query: *query, + }); + }); vm::GETTER_MAP_FN.with(|getter| { external_references.push(ExternalReference { named_getter: *getter, @@ -653,6 +660,11 @@ deno_core::extension!(deno_node, named_setter: *setter, }); }); + vm::DESCRIPTOR_MAP_FN.with(|descriptor| { + external_references.push(ExternalReference { + named_getter: *descriptor, + }); + }); vm::DELETER_MAP_FN.with(|deleter| { external_references.push(ExternalReference { named_deleter: *deleter, @@ -668,12 +680,12 @@ deno_core::extension!(deno_node, named_definer: *definer, }); }); - vm::DESCRIPTOR_MAP_FN.with(|descriptor| { + + vm::INDEXED_QUERY_MAP_FN.with(|query| { external_references.push(ExternalReference { - named_getter: *descriptor, + indexed_query: *query, }); }); - vm::INDEXED_GETTER_MAP_FN.with(|getter| { external_references.push(ExternalReference { indexed_getter: *getter, @@ -684,6 +696,11 @@ deno_core::extension!(deno_node, indexed_setter: *setter, }); }); + vm::INDEXED_DESCRIPTOR_MAP_FN.with(|descriptor| { + external_references.push(ExternalReference { + indexed_getter: *descriptor, + }); + }); vm::INDEXED_DELETER_MAP_FN.with(|deleter| { external_references.push(ExternalReference { indexed_deleter: *deleter, @@ -694,9 +711,9 @@ deno_core::extension!(deno_node, indexed_definer: *definer, }); }); - vm::INDEXED_DESCRIPTOR_MAP_FN.with(|descriptor| { + vm::INDEXED_ENUMERATOR_MAP_FN.with(|enumerator| { external_references.push(ExternalReference { - indexed_getter: *descriptor, + enumerator: *enumerator, }); }); diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index b51e23ac8a..d11cc7461b 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -14,7 +14,6 @@ pub mod require; pub mod util; pub mod v8; pub mod vm; -mod vm_internal; pub mod winerror; pub mod worker_threads; pub mod zlib; diff --git a/ext/node/ops/vm.rs b/ext/node/ops/vm.rs index d9a16eefff..e75e05651e 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; - -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; - -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; - -pub struct Script { - inner: i::ContextifyScript, +/// An unbounded script that can be run in a context. +pub struct ContextifyScript { + script: v8::TracedReference, } -impl deno_core::GarbageCollected for Script {} - -impl Script { - fn new( - scope: &mut v8::HandleScope, - source: v8::Local, - ) -> Result { - Ok(Self { - inner: i::ContextifyScript::new(scope, source)?, - }) +impl v8::cppgc::GarbageCollected for ContextifyScript { + fn trace(&self, visitor: &v8::cppgc::Visitor) { + visitor.trace(&self.script); } +} - fn run_in_this_context<'s>( - &self, - scope: &'s mut v8::HandleScope, - ) -> Result, AnyError> { - let context = scope.get_current_context(); - - 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)) - } - - fn run_in_context<'s>( - &self, +impl ContextifyScript { + #[allow(clippy::too_many_arguments)] + fn create<'s>( scope: &mut v8::HandleScope<'s>, - sandbox: v8::Local<'s, v8::Value>, - ) -> Result, 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"))?; + source: v8::Local<'s, v8::String>, + filename: v8::Local<'s, v8::Value>, + line_offset: i32, + column_offset: i32, + cached_data: Option, + produce_cached_data: bool, + parsing_context: Option>, + ) -> Option> { + 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() }; + 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 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 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_context<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + sandbox: Option>, + timeout: i64, + display_errors: bool, + break_on_sigint: bool, + ) -> Option> { + 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, + timeout: i64, + _display_errors: bool, + _break_on_sigint: bool, + microtask_queue: Option<&v8::MicrotaskQueue>, + ) -> Option> { 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::() + .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?)) } } +pub struct ContextifyContext { + microtask_queue: *mut v8::MicrotaskQueue, + context: v8::TracedReference, + sandbox: v8::TracedReference, +} + +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, + _source: v8::Local, +) -> bool { + match context.get_slot::() { + Some(b) => b.0, + None => true, + } +} + +impl ContextifyContext { + pub fn attach( + scope: &mut v8::HandleScope, + sandbox_obj: v8::Local, + _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::(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, + ) -> 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::(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, + ) -> 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>, + ) -> 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 { + // 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, + 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 + }; + + scope.escape(context) +} + +#[derive(Debug, Clone)] +struct SlotContextifyGlobalTemplate(v8::Global); + +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::(); + + 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::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 = 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::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, +) { + 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, +) { + 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::>(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::::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::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::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, 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, + produce_cached_data: bool, + parsing_context: Option>, +) -> Option> { + 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, 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, AnyError> { - script.run_in_this_context(scope) + #[cppgc] script: &ContextifyScript, + sandbox: Option>, + #[serde] timeout: i64, + display_errors: bool, + break_on_sigint: bool, +) -> Option> { + 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, + #[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; - - #[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); - - let isolate = &mut v8::Isolate::new(Default::default()); - - let scope = &mut v8::HandleScope::new(isolate); - let context = v8::Context::new(scope, Default::default()); - let scope = &mut v8::ContextScope::new(scope, context); - - let source = v8::String::new(scope, "1 + 2").unwrap(); - let script = Script::new(scope, source).unwrap(); - - let result = script.run_in_this_context(scope).unwrap(); - assert!(result.is_number()); - } +#[derive(serde::Serialize)] +struct CompileResult<'s> { + value: serde_v8::Value<'s>, + cached_data: Option>, + cached_data_rejected: bool, + cached_data_produced: bool, +} + +#[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, + produce_cached_data: bool, + parsing_context: Option>, + context_extensions: Option>, + params: Option>, +) -> Option> { + 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 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 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 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 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, + ¶ms, + &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() } diff --git a/ext/node/polyfills/assert.ts b/ext/node/polyfills/assert.ts index 00677e3f78..188c7a0c2f 100644 --- a/ext/node/polyfills/assert.ts +++ b/ext/node/polyfills/assert.ts @@ -746,6 +746,9 @@ function validateThrownError( message = error; error = undefined; } + if (error?.prototype !== undefined && e instanceof error) { + return true; + } if ( typeof error === "function" && (error === Error || ObjectPrototypeIsPrototypeOf(Error, error)) diff --git a/ext/node/polyfills/vm.js b/ext/node/polyfills/vm.js new file mode 100644 index 0000000000..bc1a250456 --- /dev/null +++ b/ext/node/polyfills/vm.js @@ -0,0 +1,359 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { Buffer } from "node:buffer"; +import { notImplemented } from "ext:deno_node/_utils.ts"; +import { + op_vm_compile_function, + op_vm_create_context, + op_vm_create_script, + op_vm_is_context, + op_vm_script_create_cached_data, + op_vm_script_get_source_map_url, + op_vm_script_run_in_context, +} from "ext:core/ops"; +import { + validateArray, + validateBoolean, + validateBuffer, + validateInt32, + validateObject, + validateOneOf, + validateString, + validateStringArray, + validateUint32, +} from "ext:deno_node/internal/validators.mjs"; +import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; + +import { primordials } from "ext:core/mod.js"; + +const { Symbol, ArrayPrototypeForEach } = primordials; + +const kParsingContext = Symbol("script parsing context"); + +export class Script { + #inner; + + constructor(code, options = {}) { + code = `${code}`; + if (typeof options === "string") { + options = { filename: options }; + } else { + validateObject(options, "options"); + } + + const { + filename = "evalmachine.", + lineOffset = 0, + columnOffset = 0, + cachedData, + produceCachedData = false, + // importModuleDynamically, + [kParsingContext]: parsingContext, + } = options; + + validateString(filename, "options.filename"); + validateInt32(lineOffset, "options.lineOffset"); + validateInt32(columnOffset, "options.columnOffset"); + if (cachedData !== undefined) { + validateBuffer(cachedData, "options.cachedData"); + } + validateBoolean(produceCachedData, "options.produceCachedData"); + + // const hostDefinedOptionId = + // getHostDefinedOptionId(importModuleDynamically, filename); + + const result = op_vm_create_script( + code, + filename, + lineOffset, + columnOffset, + cachedData, + produceCachedData, + parsingContext, + ); + this.#inner = result.value; + this.cachedDataProduced = result.cached_data_produced; + this.cachedDataRejected = result.cached_data_rejected; + this.cachedData = result.cached_data + ? Buffer.from(result.cached_data) + : undefined; + } + + #runInContext(contextifiedObject, options = {}) { + validateObject(options, "options"); + + let timeout = options.timeout; + if (timeout === undefined) { + timeout = -1; + } else { + validateUint32(timeout, "options.timeout", true); + } + + const { + displayErrors = true, + breakOnSigint = false, + } = options; + + validateBoolean(displayErrors, "options.displayErrors"); + validateBoolean(breakOnSigint, "options.breakOnSigint"); + + //if (breakOnSigint && process.listenerCount('SIGINT') > 0) { + // return sigintHandlersWrap(super.runInContext, this, args); + //} + + return op_vm_script_run_in_context( + this.#inner, + contextifiedObject, + timeout, + displayErrors, + breakOnSigint, + ); + } + + runInThisContext(options) { + return this.#runInContext(null, options); + } + + runInContext(contextifiedObject, options) { + validateContext(contextifiedObject); + return this.#runInContext(contextifiedObject, options); + } + + runInNewContext(contextObject, options) { + const context = createContext(contextObject, getContextOptions(options)); + return this.runInContext(context, options); + } + + get sourceMapURL() { + return op_vm_script_get_source_map_url(this.#inner); + } + + createCachedData() { + return Buffer.from(op_vm_script_create_cached_data(this.#inner)); + } +} + +function validateContext(contextifiedObject) { + if (!isContext(contextifiedObject)) { + throw new ERR_INVALID_ARG_TYPE( + "contextifiedObject", + "vm.Context", + contextifiedObject, + ); + } +} + +function getContextOptions(options) { + if (!options) { + return {}; + } + const contextOptions = { + name: options.contextName, + origin: options.contextOrigin, + codeGeneration: undefined, + microtaskMode: options.microtaskMode, + }; + if (contextOptions.name !== undefined) { + validateString(contextOptions.name, "options.contextName"); + } + if (contextOptions.origin !== undefined) { + validateString(contextOptions.origin, "options.contextOrigin"); + } + if (options.contextCodeGeneration !== undefined) { + validateObject( + options.contextCodeGeneration, + "options.contextCodeGeneration", + ); + const { strings, wasm } = options.contextCodeGeneration; + if (strings !== undefined) { + validateBoolean(strings, "options.contextCodeGeneration.strings"); + } + if (wasm !== undefined) { + validateBoolean(wasm, "options.contextCodeGeneration.wasm"); + } + contextOptions.codeGeneration = { strings, wasm }; + } + if (options.microtaskMode !== undefined) { + validateString(options.microtaskMode, "options.microtaskMode"); + } + return contextOptions; +} + +let defaultContextNameIndex = 1; +export function createContext(contextObject = {}, options = {}) { + if (isContext(contextObject)) { + return contextObject; + } + + validateObject(options, "options"); + + const { + name = `VM Context ${defaultContextNameIndex++}`, + origin, + codeGeneration, + microtaskMode, + // importModuleDynamically, + } = options; + + validateString(name, "options.name"); + if (origin !== undefined) { + validateString(origin, "options.origin"); + } + if (codeGeneration !== undefined) { + validateObject(codeGeneration, "options.codeGeneration"); + } + + let strings = true; + let wasm = true; + if (codeGeneration !== undefined) { + ({ strings = true, wasm = true } = codeGeneration); + validateBoolean(strings, "options.codeGeneration.strings"); + validateBoolean(wasm, "options.codeGeneration.wasm"); + } + + validateOneOf(microtaskMode, "options.microtaskMode", [ + "afterEvaluate", + undefined, + ]); + const microtaskQueue = microtaskMode === "afterEvaluate"; + + // const hostDefinedOptionId = + // getHostDefinedOptionId(importModuleDynamically, name); + + op_vm_create_context( + contextObject, + name, + origin, + strings, + wasm, + microtaskQueue, + ); + // Register the context scope callback after the context was initialized. + // registerImportModuleDynamically(contextObject, importModuleDynamically); + return contextObject; +} + +export function createScript(code, options) { + return new Script(code, options); +} + +export function runInContext(code, contextifiedObject, options) { + validateContext(contextifiedObject); + if (typeof options === "string") { + options = { + filename: options, + [kParsingContext]: contextifiedObject, + }; + } else { + options = { + ...options, + [kParsingContext]: contextifiedObject, + }; + } + return createScript(code, options) + .runInContext(contextifiedObject, options); +} + +export function runInNewContext(code, contextObject, options) { + if (typeof options === "string") { + options = { filename: options }; + } + contextObject = createContext(contextObject, getContextOptions(options)); + options = { ...options, [kParsingContext]: contextObject }; + return createScript(code, options).runInNewContext(contextObject, options); +} + +export function runInThisContext(code, options) { + if (typeof options === "string") { + options = { filename: options }; + } + return createScript(code, options).runInThisContext(options); +} + +export function isContext(object) { + validateObject(object, "object", { allowArray: true }); + return op_vm_is_context(object); +} + +export function compileFunction(code, params, options = {}) { + validateString(code, "code"); + if (params !== undefined) { + validateStringArray(params, "params"); + } + const { + filename = "", + columnOffset = 0, + lineOffset = 0, + cachedData = undefined, + produceCachedData = false, + parsingContext = undefined, + contextExtensions = [], + // importModuleDynamically, + } = options; + + validateString(filename, "options.filename"); + validateInt32(columnOffset, "options.columnOffset"); + validateInt32(lineOffset, "options.lineOffset"); + if (cachedData !== undefined) { + validateBuffer(cachedData, "options.cachedData"); + } + validateBoolean(produceCachedData, "options.produceCachedData"); + if (parsingContext !== undefined) { + if ( + typeof parsingContext !== "object" || + parsingContext === null || + !isContext(parsingContext) + ) { + throw new ERR_INVALID_ARG_TYPE( + "options.parsingContext", + "Context", + parsingContext, + ); + } + } + validateArray(contextExtensions, "options.contextExtensions"); + ArrayPrototypeForEach(contextExtensions, (extension, i) => { + const name = `options.contextExtensions[${i}]`; + validateObject(extension, name, { nullable: true }); + }); + + // const hostDefinedOptionId = + // getHostDefinedOptionId(importModuleDynamically, filename); + + const result = op_vm_compile_function( + code, + filename, + lineOffset, + columnOffset, + cachedData, + produceCachedData, + parsingContext, + contextExtensions, + params, + ); + + result.value.cachedDataProduced = result.cached_data_produced; + result.value.cachedDataRejected = result.cached_data_rejected; + result.value.cachedData = result.cached_data + ? Buffer.from(result.cached_data) + : undefined; + + return result.value; +} + +export function measureMemory(_options) { + notImplemented("measureMemory"); +} + +export default { + Script, + createContext, + createScript, + runInContext, + runInNewContext, + runInThisContext, + isContext, + compileFunction, + measureMemory, +}; diff --git a/ext/node/polyfills/vm.ts b/ext/node/polyfills/vm.ts deleted file mode 100644 index 3378e38862..0000000000 --- a/ext/node/polyfills/vm.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -// deno-lint-ignore-file no-explicit-any - -import { notImplemented } from "ext:deno_node/_utils.ts"; -import { - op_vm_create_context, - op_vm_create_script, - op_vm_is_context, - op_vm_script_run_in_context, - op_vm_script_run_in_this_context, -} from "ext:core/ops"; - -export class Script { - #inner; - - constructor(code: string, _options = {}) { - this.#inner = op_vm_create_script(code); - } - - runInThisContext(_options: any) { - return op_vm_script_run_in_this_context(this.#inner); - } - - runInContext(contextifiedObject: any, _options: any) { - return op_vm_script_run_in_context(this.#inner, contextifiedObject); - } - - runInNewContext(contextObject: any, options: any) { - const context = createContext(contextObject); - return this.runInContext(context, options); - } - - createCachedData() { - notImplemented("Script.prototype.createCachedData"); - } -} - -export function createContext(contextObject: any = {}, _options: any) { - if (isContext(contextObject)) { - return contextObject; - } - - op_vm_create_context(contextObject); - return contextObject; -} - -export function createScript(code: string, options: any) { - return new Script(code, options); -} - -export function runInContext( - code: string, - contextifiedObject: any, - _options: any, -) { - return createScript(code).runInContext(contextifiedObject); -} - -export function runInNewContext( - code: string, - contextObject: any, - options: any, -) { - if (options) { - console.warn("vm.runInNewContext options are currently not supported"); - } - return createScript(code).runInNewContext(contextObject); -} - -export function runInThisContext( - code: string, - options: any, -) { - return createScript(code, options).runInThisContext(options); -} - -export function isContext(maybeContext: any) { - return op_vm_is_context(maybeContext); -} - -export function compileFunction(_code: string, _params: any, _options: any) { - notImplemented("compileFunction"); -} - -export function measureMemory(_options: any) { - notImplemented("measureMemory"); -} - -export default { - Script, - createContext, - createScript, - runInContext, - runInNewContext, - runInThisContext, - isContext, - compileFunction, - measureMemory, -}; diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs index da66bff5eb..659356ae66 100644 --- a/runtime/snapshot.rs +++ b/runtime/snapshot.rs @@ -293,6 +293,7 @@ pub fn create_runtime_snapshot( scope, tmpl, deno_node::ContextInitMode::ForSnapshot, + std::ptr::null_mut(), ); assert_eq!(scope.add_context(ctx), deno_node::VM_CONTEXT_INDEX); })), diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index d4953075a2..72d8728122 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -669,8 +669,50 @@ "test-util-types-exists.js", "test-util-types.js", "test-util.js", + "test-vm-access-process-env.js", + "test-vm-attributes-property-not-on-sandbox.js", + "test-vm-codegen.js", + "test-vm-context-async-script.js", + "test-vm-context-property-forwarding.js", + "test-vm-create-and-run-in-context.js", + "test-vm-create-context-accessors.js", + "test-vm-create-context-arg.js", + "test-vm-create-context-circular-reference.js", + "test-vm-createcacheddata.js", + "test-vm-cross-context.js", + "test-vm-data-property-writable.js", + "test-vm-deleting-property.js", + "test-vm-function-declaration.js", + "test-vm-function-redefinition.js", + "test-vm-getters.js", + "test-vm-global-assignment.js", + "test-vm-global-define-property.js", + "test-vm-global-identity.js", + "test-vm-global-setter.js", + "test-vm-harmony-symbols.js", + "test-vm-indexed-properties.js", + "test-vm-inherited_properties.js", + "test-vm-is-context.js", + "test-vm-low-stack-space.js", + "test-vm-new-script-new-context.js", "test-vm-new-script-this-context.js", + "test-vm-not-strict.js", + "test-vm-options-validation.js", + "test-vm-parse-abort-on-uncaught-exception.js", + "test-vm-preserves-property.js", + "test-vm-property-not-on-sandbox.js", + "test-vm-proxies.js", + "test-vm-proxy-failure-CP.js", + "test-vm-script-throw-in-tostring.js", + "test-vm-set-property-proxy.js", + "test-vm-set-proto-null-on-globalthis.js", + "test-vm-source-map-url.js", "test-vm-static-this.js", + "test-vm-strict-mode.js", + "test-vm-symbols.js", + "test-vm-timeout-escape-promise-2.js", + "test-vm-timeout-escape-promise.js", + "test-vm-timeout.js", "test-webcrypto-sign-verify.js", "test-whatwg-encoding-custom-api-basics.js", "test-whatwg-encoding-custom-textdecoder-ignorebom.js", @@ -716,7 +758,9 @@ "test-tty-stdout-end.js" ], "pummel": [], - "sequential": ["test-child-process-exit.js"] + "sequential": [ + "test-child-process-exit.js" + ] }, "windowsIgnore": { "parallel": [ @@ -744,7 +788,8 @@ "test-net-server-listen-path.js", "test-net-socket-close-after-end.js", "test-util-inspect-long-running.js", - "test-util-inspect.js" + "test-util-inspect.js", + "test-vm-low-stack-space.js" ] }, "darwinIgnore": { diff --git a/tests/node_compat/runner/TODO.md b/tests/node_compat/runner/TODO.md index e491009818..1656729e26 100644 --- a/tests/node_compat/runner/TODO.md +++ b/tests/node_compat/runner/TODO.md @@ -2622,39 +2622,14 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-v8-version-tag.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-v8-version-tag.js) - [parallel/test-validators.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-validators.js) - [parallel/test-vfs.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vfs.js) -- [parallel/test-vm-access-process-env.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-access-process-env.js) - [parallel/test-vm-api-handles-getter-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-api-handles-getter-errors.js) -- [parallel/test-vm-attributes-property-not-on-sandbox.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-attributes-property-not-on-sandbox.js) - [parallel/test-vm-basic.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-basic.js) - [parallel/test-vm-cached-data.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-cached-data.js) -- [parallel/test-vm-codegen.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-codegen.js) -- [parallel/test-vm-context-async-script.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-context-async-script.js) -- [parallel/test-vm-context-property-forwarding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-context-property-forwarding.js) - [parallel/test-vm-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-context.js) -- [parallel/test-vm-create-and-run-in-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-create-and-run-in-context.js) -- [parallel/test-vm-create-context-accessors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-create-context-accessors.js) -- [parallel/test-vm-create-context-arg.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-create-context-arg.js) -- [parallel/test-vm-create-context-circular-reference.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-create-context-circular-reference.js) -- [parallel/test-vm-createcacheddata.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-createcacheddata.js) -- [parallel/test-vm-cross-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-cross-context.js) -- [parallel/test-vm-data-property-writable.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-data-property-writable.js) -- [parallel/test-vm-deleting-property.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-deleting-property.js) - [parallel/test-vm-dynamic-import-callback-missing-flag.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-dynamic-import-callback-missing-flag.js) -- [parallel/test-vm-function-declaration.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-function-declaration.js) -- [parallel/test-vm-function-redefinition.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-function-redefinition.js) -- [parallel/test-vm-getters.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-getters.js) -- [parallel/test-vm-global-assignment.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-global-assignment.js) -- [parallel/test-vm-global-define-property.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-global-define-property.js) - [parallel/test-vm-global-get-own.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-global-get-own.js) -- [parallel/test-vm-global-identity.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-global-identity.js) - [parallel/test-vm-global-non-writable-properties.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-global-non-writable-properties.js) - [parallel/test-vm-global-property-interceptors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-global-property-interceptors.js) -- [parallel/test-vm-global-setter.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-global-setter.js) -- [parallel/test-vm-harmony-symbols.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-harmony-symbols.js) -- [parallel/test-vm-indexed-properties.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-indexed-properties.js) -- [parallel/test-vm-inherited_properties.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-inherited_properties.js) -- [parallel/test-vm-is-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-is-context.js) -- [parallel/test-vm-low-stack-space.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-low-stack-space.js) - [parallel/test-vm-measure-memory-lazy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-measure-memory-lazy.js) - [parallel/test-vm-measure-memory-multi-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-measure-memory-multi-context.js) - [parallel/test-vm-measure-memory.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-measure-memory.js) @@ -2667,31 +2642,14 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-vm-module-link.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-module-link.js) - [parallel/test-vm-module-reevaluate.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-module-reevaluate.js) - [parallel/test-vm-module-synthetic.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-module-synthetic.js) -- [parallel/test-vm-new-script-new-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-new-script-new-context.js) - [parallel/test-vm-no-dynamic-import-callback.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-no-dynamic-import-callback.js) -- [parallel/test-vm-not-strict.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-not-strict.js) -- [parallel/test-vm-options-validation.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-options-validation.js) -- [parallel/test-vm-parse-abort-on-uncaught-exception.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-parse-abort-on-uncaught-exception.js) -- [parallel/test-vm-preserves-property.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-preserves-property.js) -- [parallel/test-vm-property-not-on-sandbox.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-property-not-on-sandbox.js) -- [parallel/test-vm-proxies.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-proxies.js) -- [parallel/test-vm-proxy-failure-CP.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-proxy-failure-CP.js) - [parallel/test-vm-run-in-new-context.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-run-in-new-context.js) -- [parallel/test-vm-script-throw-in-tostring.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-script-throw-in-tostring.js) -- [parallel/test-vm-set-property-proxy.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-set-property-proxy.js) -- [parallel/test-vm-set-proto-null-on-globalthis.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-set-proto-null-on-globalthis.js) - [parallel/test-vm-sigint-existing-handler.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-sigint-existing-handler.js) - [parallel/test-vm-sigint.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-sigint.js) -- [parallel/test-vm-source-map-url.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-source-map-url.js) - [parallel/test-vm-strict-assign.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-strict-assign.js) -- [parallel/test-vm-strict-mode.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-strict-mode.js) -- [parallel/test-vm-symbols.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-symbols.js) - [parallel/test-vm-syntax-error-message.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-syntax-error-message.js) - [parallel/test-vm-syntax-error-stderr.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-syntax-error-stderr.js) -- [parallel/test-vm-timeout-escape-promise-2.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-timeout-escape-promise-2.js) - [parallel/test-vm-timeout-escape-promise-module.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-timeout-escape-promise-module.js) -- [parallel/test-vm-timeout-escape-promise.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-timeout-escape-promise.js) -- [parallel/test-vm-timeout.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-vm-timeout.js) - [parallel/test-warn-sigprof.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-warn-sigprof.js) - [parallel/test-warn-stream-wrap.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-warn-stream-wrap.js) - [parallel/test-weakref.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-weakref.js) diff --git a/tests/node_compat/test.ts b/tests/node_compat/test.ts index c9ec1544de..b5c9514a33 100644 --- a/tests/node_compat/test.ts +++ b/tests/node_compat/test.ts @@ -45,6 +45,12 @@ const darwinIgnorePaths = new Set( const decoder = new TextDecoder(); let testSerialId = 0; +function parseFlags(source: string): string[] { + const line = /^\/\/ Flags: (.+)$/um.exec(source); + if (line == null) return []; + return line[1].split(" "); +} + async function runTest(t: Deno.TestContext, path: string): Promise { // If filter patterns are given and any pattern doesn't match // to the file path, then skip the case @@ -69,14 +75,23 @@ async function runTest(t: Deno.TestContext, path: string): Promise { const v8Flags = ["--stack-size=4000"]; const testSource = await Deno.readTextFile(testCase); const envVars: Record = {}; - // TODO(kt3k): Parse `Flags` directive correctly - if (testSource.includes("Flags: --expose_externalize_string")) { - v8Flags.push("--expose-externalize-string"); - // TODO(bartlomieju): disable verifying globals if that V8 flag is - // present. Even though we should be able to pass a list of globals - // that are allowed, it doesn't work, because the list is expected to - // contain actual JS objects, not strings :)). - envVars["NODE_TEST_KNOWN_GLOBALS"] = "0"; + const knownGlobals: string[] = []; + parseFlags(testSource).forEach((flag) => { + switch (flag) { + case "--expose_externalize_string": + v8Flags.push("--expose-externalize-string"); + knownGlobals.push("createExternalizableString"); + break; + case "--expose-gc": + v8Flags.push("--expose-gc"); + knownGlobals.push("gc"); + break; + default: + break; + } + }); + if (knownGlobals.length > 0) { + envVars["NODE_TEST_KNOWN_GLOBALS"] = knownGlobals.join(","); } // TODO(nathanwhit): once we match node's behavior on executing // `node:test` tests when we run a file, we can remove this diff --git a/tests/node_compat/test/common/index.js b/tests/node_compat/test/common/index.js index ebac56ac55..d2165aecd0 100644 --- a/tests/node_compat/test/common/index.js +++ b/tests/node_compat/test/common/index.js @@ -16,7 +16,6 @@ const path = require("path"); const util = require("util"); const tmpdir = require("./tmpdir"); - function platformTimeout(ms) { return ms; } @@ -90,7 +89,7 @@ function allowGlobals(...allowlist) { if (process.env.NODE_TEST_KNOWN_GLOBALS !== '0') { if (process.env.NODE_TEST_KNOWN_GLOBALS) { - const knownFromEnv = process.env.NODE_TEST_KNOWN_GLOBALS.split(','); + const knownFromEnv = process.env.NODE_TEST_KNOWN_GLOBALS.split(',').map((name) => global[name]); allowGlobals(...knownFromEnv); } @@ -445,13 +444,13 @@ const pwdCommand = isWindows ? function spawnPromisified(...args) { let stderr = ''; let stdout = ''; - + const child = spawn(...args); child.stderr.setEncoding('utf8'); child.stderr.on('data', (data) => { stderr += data; }); child.stdout.setEncoding('utf8'); child.stdout.on('data', (data) => { stdout += data; }); - + return new Promise((resolve, reject) => { child.on('close', (code, signal) => { resolve({ diff --git a/tests/node_compat/test/parallel/test-vm-access-process-env.js b/tests/node_compat/test/parallel/test-vm-access-process-env.js new file mode 100644 index 0000000000..95f555dac3 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-access-process-env.js @@ -0,0 +1,40 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Tests that node does neither crash nor throw an error when accessing +// process.env when inside a VM context. +// See https://github.com/nodejs/node-v0.x-archive/issues/7511. + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const context = vm.createContext({ process }); +const result = vm.runInContext('process.env["PATH"]', context); +assert.notStrictEqual(undefined, result); diff --git a/tests/node_compat/test/parallel/test-vm-attributes-property-not-on-sandbox.js b/tests/node_compat/test/parallel/test-vm-attributes-property-not-on-sandbox.js new file mode 100644 index 0000000000..940fd4e7fe --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-attributes-property-not-on-sandbox.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// Assert that accessor descriptors are not flattened on the sandbox. +// Issue: https://github.com/nodejs/node/issues/2734 +const sandbox = {}; +vm.createContext(sandbox); +const code = `Object.defineProperty( + this, + 'foo', + { get: function() {return 17} } + ); + var desc = Object.getOwnPropertyDescriptor(this, 'foo');`; + +vm.runInContext(code, sandbox); +assert.strictEqual(typeof sandbox.desc.get, 'function'); diff --git a/tests/node_compat/test/parallel/test-vm-codegen.js b/tests/node_compat/test/parallel/test-vm-codegen.js new file mode 100644 index 0000000000..fff9c287f4 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-codegen.js @@ -0,0 +1,108 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const { createContext, runInContext, runInNewContext } = require('vm'); + +const WASM_BYTES = Buffer.from( + [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]); + +{ + const ctx = createContext({ WASM_BYTES }); + const test = 'eval(""); new WebAssembly.Module(WASM_BYTES);'; + runInContext(test, ctx); + + runInNewContext(test, { WASM_BYTES }, { + contextCodeGeneration: undefined, + }); +} + +{ + const ctx = createContext({}, { + codeGeneration: { + strings: false, + }, + }); + + const EvalError = runInContext('EvalError', ctx); + assert.throws(() => { + runInContext('eval("x")', ctx); + }, EvalError); +} + +{ + const ctx = createContext({ WASM_BYTES }, { + codeGeneration: { + wasm: false, + }, + }); + + const CompileError = runInContext('WebAssembly.CompileError', ctx); + assert.throws(() => { + runInContext('new WebAssembly.Module(WASM_BYTES)', ctx); + }, CompileError); +} + +assert.throws(() => { + runInNewContext('eval("x")', {}, { + contextCodeGeneration: { + strings: false, + }, + }); +}, { + name: 'EvalError' +}); + +assert.throws(() => { + runInNewContext('new WebAssembly.Module(WASM_BYTES)', { WASM_BYTES }, { + contextCodeGeneration: { + wasm: false, + }, + }); +}, { + name: 'CompileError' +}); + +assert.throws(() => { + createContext({}, { + codeGeneration: { + strings: 0, + }, + }); +}, { + code: 'ERR_INVALID_ARG_TYPE', +}); + +assert.throws(() => { + runInNewContext('eval("x")', {}, { + contextCodeGeneration: { + wasm: 1, + }, + }); +}, { + code: 'ERR_INVALID_ARG_TYPE' +}); + +assert.throws(() => { + createContext({}, { + codeGeneration: 1, + }); +}, { + code: 'ERR_INVALID_ARG_TYPE', +}); + +assert.throws(() => { + createContext({}, { + codeGeneration: null, + }); +}, { + code: 'ERR_INVALID_ARG_TYPE', +}); diff --git a/tests/node_compat/test/parallel/test-vm-context-async-script.js b/tests/node_compat/test/parallel/test-vm-context-async-script.js new file mode 100644 index 0000000000..271567ccf1 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-context-async-script.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const sandbox = { setTimeout }; + +const ctx = vm.createContext(sandbox); + +vm.runInContext('setTimeout(function() { x = 3; }, 0);', ctx); +setTimeout(common.mustCall(() => { + assert.strictEqual(sandbox.x, 3); + assert.strictEqual(ctx.x, 3); +}), 1); diff --git a/tests/node_compat/test/parallel/test-vm-context-property-forwarding.js b/tests/node_compat/test/parallel/test-vm-context-property-forwarding.js new file mode 100644 index 0000000000..f507377718 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-context-property-forwarding.js @@ -0,0 +1,72 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const sandbox = { x: 3 }; + +const ctx = vm.createContext(sandbox); + +assert.strictEqual(vm.runInContext('x;', ctx), 3); +vm.runInContext('y = 4;', ctx); +assert.strictEqual(sandbox.y, 4); +assert.strictEqual(ctx.y, 4); + +// Test `IndexedPropertyGetterCallback` and `IndexedPropertyDeleterCallback` +const x = { get 1() { return 5; } }; +const pd_expected = Object.getOwnPropertyDescriptor(x, 1); +const ctx2 = vm.createContext(x); +const pd_actual = Object.getOwnPropertyDescriptor(ctx2, 1); + +assert.deepStrictEqual(pd_actual, pd_expected); +assert.strictEqual(ctx2[1], 5); +delete ctx2[1]; +assert.strictEqual(ctx2[1], undefined); + +// https://github.com/nodejs/node/issues/33806 +{ + const ctx = vm.createContext(); + + Object.defineProperty(ctx, 'prop', { + get() { + return undefined; + }, + set(val) { + throw new Error('test error'); + }, + }); + + assert.throws(() => { + vm.runInContext('prop = 42', ctx); + }, { + message: 'test error', + }); +} diff --git a/tests/node_compat/test/parallel/test-vm-create-and-run-in-context.js b/tests/node_compat/test/parallel/test-vm-create-and-run-in-context.js new file mode 100644 index 0000000000..0674c0b7b8 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-create-and-run-in-context.js @@ -0,0 +1,57 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Flags: --expose-gc +require('../common'); +const assert = require('assert'); + +const vm = require('vm'); + +// Run in a new empty context +let context = vm.createContext(); +let result = vm.runInContext('"passed";', context); +assert.strictEqual(result, 'passed'); + +// Create a new pre-populated context +context = vm.createContext({ 'foo': 'bar', 'thing': 'lala' }); +assert.strictEqual(context.foo, 'bar'); +assert.strictEqual(context.thing, 'lala'); + +// Test updating context +result = vm.runInContext('var foo = 3;', context); +assert.strictEqual(context.foo, 3); +assert.strictEqual(context.thing, 'lala'); + +// https://github.com/nodejs/node/issues/5768 +// Run in contextified sandbox without referencing the context +const sandbox = { x: 1 }; +vm.createContext(sandbox); +global.gc(); +vm.runInContext('x = 2', sandbox); +// Should not crash. diff --git a/tests/node_compat/test/parallel/test-vm-create-context-accessors.js b/tests/node_compat/test/parallel/test-vm-create-context-accessors.js new file mode 100644 index 0000000000..4b683d687b --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-create-context-accessors.js @@ -0,0 +1,56 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +let ctx = {}; + +Object.defineProperty(ctx, 'getter', { + get: function() { + return 'ok'; + } +}); + +let val; +Object.defineProperty(ctx, 'setter', { + set: function(_val) { + val = _val; + }, + get: function() { + return `ok=${val}`; + } +}); + +ctx = vm.createContext(ctx); + +const result = vm.runInContext('setter = "test";[getter,setter]', ctx); +assert.strictEqual(result[0], 'ok'); +assert.strictEqual(result[1], 'ok=test'); diff --git a/tests/node_compat/test/parallel/test-vm-create-context-arg.js b/tests/node_compat/test/parallel/test-vm-create-context-arg.js new file mode 100644 index 0000000000..6eb0f7cf91 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-create-context-arg.js @@ -0,0 +1,47 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +assert.throws(() => { + vm.createContext('string is not supported'); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' +}); + +// Should not throw. +vm.createContext({ a: 1 }); +vm.createContext([0, 1, 2, 3]); + +const sandbox = {}; +vm.createContext(sandbox); +vm.createContext(sandbox); diff --git a/tests/node_compat/test/parallel/test-vm-create-context-circular-reference.js b/tests/node_compat/test/parallel/test-vm-create-context-circular-reference.js new file mode 100644 index 0000000000..95056a3d96 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-create-context-circular-reference.js @@ -0,0 +1,41 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +let sbx = {}; +sbx.window = sbx; + +sbx = vm.createContext(sbx); + +sbx.test = 123; + +assert.strictEqual(sbx.window.window.window.window.window.test, 123); diff --git a/tests/node_compat/test/parallel/test-vm-createcacheddata.js b/tests/node_compat/test/parallel/test-vm-createcacheddata.js new file mode 100644 index 0000000000..0e786364d3 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-createcacheddata.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); + +const { Script } = require('vm'); +const assert = require('assert'); + +const source = 'function x() {} const y = x();'; + +const script = new Script(source); +let cachedData = script.createCachedData(); +assert(cachedData instanceof Buffer); + +assert(!new Script(source, { cachedData }).cachedDataRejected); + +script.runInNewContext(); + +for (let i = 0; i < 10; i += 1) { + cachedData = script.createCachedData(); + + assert(!new Script(source, { cachedData }).cachedDataRejected); +} diff --git a/tests/node_compat/test/parallel/test-vm-cross-context.js b/tests/node_compat/test/parallel/test-vm-cross-context.js new file mode 100644 index 0000000000..3a1f1678ec --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-cross-context.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); + +const vm = require('vm'); +const ctx = vm.createContext(global); + +// Should not throw. +vm.runInContext('!function() { var x = console.log; }()', ctx); diff --git a/tests/node_compat/test/parallel/test-vm-data-property-writable.js b/tests/node_compat/test/parallel/test-vm-data-property-writable.js new file mode 100644 index 0000000000..1ce7648018 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-data-property-writable.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +// Refs: https://github.com/nodejs/node/issues/10223 + +require('../common'); +const vm = require('vm'); +const assert = require('assert'); + +const context = vm.createContext({}); + +let code = ` + Object.defineProperty(this, 'foo', {value: 5}); + Object.getOwnPropertyDescriptor(this, 'foo'); +`; + +let desc = vm.runInContext(code, context); + +assert.strictEqual(desc.writable, false); + +// Check that interceptors work for symbols. +code = ` + const bar = Symbol('bar'); + Object.defineProperty(this, bar, {value: 6}); + Object.getOwnPropertyDescriptor(this, bar); +`; + +desc = vm.runInContext(code, context); + +assert.strictEqual(desc.value, 6); diff --git a/tests/node_compat/test/parallel/test-vm-deleting-property.js b/tests/node_compat/test/parallel/test-vm-deleting-property.js new file mode 100644 index 0000000000..df5ac859a7 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-deleting-property.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +// Refs: https://github.com/nodejs/node/issues/6287 + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const context = vm.createContext(); +const res = vm.runInContext(` + this.x = 'prop'; + delete this.x; + Object.getOwnPropertyDescriptor(this, 'x'); +`, context); + +assert.strictEqual(res, undefined); diff --git a/tests/node_compat/test/parallel/test-vm-function-declaration.js b/tests/node_compat/test/parallel/test-vm-function-declaration.js new file mode 100644 index 0000000000..209720c757 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-function-declaration.js @@ -0,0 +1,56 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const vm = require('vm'); +const o = vm.createContext({ console }); + +// Function declaration and expression should both be copied to the +// sandboxed context. +let code = 'let a = function() {};\n'; +code += 'function b(){}\n'; +code += 'var c = function() {};\n'; +code += 'var d = () => {};\n'; +code += 'let e = () => {};\n'; + +// Grab the global b function as the completion value, to ensure that +// we are getting the global function, and not some other thing +code += '(function(){return this})().b;\n'; + +const res = vm.runInContext(code, o, 'test'); +assert.strictEqual(typeof res, 'function'); +assert.strictEqual(res.name, 'b'); +assert.strictEqual(typeof o.a, 'undefined'); +assert.strictEqual(typeof o.b, 'function'); +assert.strictEqual(typeof o.c, 'function'); +assert.strictEqual(typeof o.d, 'function'); +assert.strictEqual(typeof o.e, 'undefined'); +assert.strictEqual(res, o.b); diff --git a/tests/node_compat/test/parallel/test-vm-function-redefinition.js b/tests/node_compat/test/parallel/test-vm-function-redefinition.js new file mode 100644 index 0000000000..7bc62ac0ed --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-function-redefinition.js @@ -0,0 +1,18 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +// Refs: https://github.com/nodejs/node/issues/548 +require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const context = vm.createContext(); + +vm.runInContext('function test() { return 0; }', context); +vm.runInContext('function test() { return 1; }', context); +const result = vm.runInContext('test()', context); +assert.strictEqual(result, 1); diff --git a/tests/node_compat/test/parallel/test-vm-getters.js b/tests/node_compat/test/parallel/test-vm-getters.js new file mode 100644 index 0000000000..b9c28014ad --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-getters.js @@ -0,0 +1,31 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +// Refs: https://github.com/nodejs/node/issues/2734 +require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const sandbox = {}; + +Object.defineProperty(sandbox, 'prop', { + get() { + return 'foo'; + } +}); + +const descriptor = Object.getOwnPropertyDescriptor(sandbox, 'prop'); +const context = vm.createContext(sandbox); +const code = 'Object.getOwnPropertyDescriptor(this, "prop");'; +const result = vm.runInContext(code, context); + +// Ref: https://github.com/nodejs/node/issues/11803 + +assert.deepStrictEqual(Object.keys(result), Object.keys(descriptor)); +for (const prop of Object.keys(result)) { + assert.strictEqual(result[prop], descriptor[prop]); +} diff --git a/tests/node_compat/test/parallel/test-vm-global-assignment.js b/tests/node_compat/test/parallel/test-vm-global-assignment.js new file mode 100644 index 0000000000..c8fc516d65 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-global-assignment.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +// Regression test for https://github.com/nodejs/node/issues/10806 + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const ctx = vm.createContext({ open() { } }); +const window = vm.runInContext('this', ctx); +const other = 123; + +assert.notStrictEqual(window.open, other); +window.open = other; +assert.strictEqual(window.open, other); +window.open = other; +assert.strictEqual(window.open, other); diff --git a/tests/node_compat/test/parallel/test-vm-global-define-property.js b/tests/node_compat/test/parallel/test-vm-global-define-property.js new file mode 100644 index 0000000000..28f5070c27 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-global-define-property.js @@ -0,0 +1,54 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const vm = require('vm'); + +const code = + 'Object.defineProperty(this, "f", {\n' + + ' get: function() { return x; },\n' + + ' set: function(k) { x = k; },\n' + + ' configurable: true,\n' + + ' enumerable: true\n' + + '});\n' + + 'g = f;\n' + + 'f;\n'; + +const x = {}; +const o = vm.createContext({ console, x }); + +const res = vm.runInContext(code, o, 'test'); + +assert(res); +assert.strictEqual(typeof res, 'object'); +assert.strictEqual(res, x); +assert.strictEqual(o.f, res); +assert.deepStrictEqual(Object.keys(o), ['console', 'x', 'f', 'g']); diff --git a/tests/node_compat/test/parallel/test-vm-global-identity.js b/tests/node_compat/test/parallel/test-vm-global-identity.js new file mode 100644 index 0000000000..5413ca94a2 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-global-identity.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const ctx = vm.createContext(); +ctx.window = ctx; + +const thisVal = vm.runInContext('this;', ctx); +const windowVal = vm.runInContext('window;', ctx); +assert.strictEqual(thisVal, windowVal); diff --git a/tests/node_compat/test/parallel/test-vm-global-setter.js b/tests/node_compat/test/parallel/test-vm-global-setter.js new file mode 100644 index 0000000000..8f1f862f36 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-global-setter.js @@ -0,0 +1,168 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const getSetSymbolReceivingFunction = Symbol('sym-1'); +const getSetSymbolReceivingNumber = Symbol('sym-2'); +const symbolReceivingNumber = Symbol('sym-3'); +const unknownSymbolReceivingNumber = Symbol('sym-4'); + +const window = createWindow(); + +const descriptor1 = Object.getOwnPropertyDescriptor( + window.globalProxy, + 'getSetPropReceivingFunction' +); +assert.strictEqual(typeof descriptor1.get, 'function'); +assert.strictEqual(typeof descriptor1.set, 'function'); +assert.strictEqual(descriptor1.configurable, true); + +const descriptor2 = Object.getOwnPropertyDescriptor( + window.globalProxy, + 'getSetPropReceivingNumber' +); +assert.strictEqual(typeof descriptor2.get, 'function'); +assert.strictEqual(typeof descriptor2.set, 'function'); +assert.strictEqual(descriptor2.configurable, true); + +const descriptor3 = Object.getOwnPropertyDescriptor( + window.globalProxy, + 'propReceivingNumber' +); +assert.strictEqual(descriptor3.value, 44); + +const descriptor4 = Object.getOwnPropertyDescriptor( + window.globalProxy, + 'unknownPropReceivingNumber' +); +assert.strictEqual(descriptor4, undefined); + +const descriptor5 = Object.getOwnPropertyDescriptor( + window.globalProxy, + getSetSymbolReceivingFunction +); +assert.strictEqual(typeof descriptor5.get, 'function'); +assert.strictEqual(typeof descriptor5.set, 'function'); +assert.strictEqual(descriptor5.configurable, true); + +const descriptor6 = Object.getOwnPropertyDescriptor( + window.globalProxy, + getSetSymbolReceivingNumber +); +assert.strictEqual(typeof descriptor6.get, 'function'); +assert.strictEqual(typeof descriptor6.set, 'function'); +assert.strictEqual(descriptor6.configurable, true); + +const descriptor7 = Object.getOwnPropertyDescriptor( + window.globalProxy, + symbolReceivingNumber +); +assert.strictEqual(descriptor7.value, 48); + +const descriptor8 = Object.getOwnPropertyDescriptor( + window.globalProxy, + unknownSymbolReceivingNumber +); +assert.strictEqual(descriptor8, undefined); + +const descriptor9 = Object.getOwnPropertyDescriptor( + window.globalProxy, + 'getSetPropThrowing' +); +assert.strictEqual(typeof descriptor9.get, 'function'); +assert.strictEqual(typeof descriptor9.set, 'function'); +assert.strictEqual(descriptor9.configurable, true); + +const descriptor10 = Object.getOwnPropertyDescriptor( + window.globalProxy, + 'nonWritableProp' +); +assert.strictEqual(descriptor10.value, 51); +assert.strictEqual(descriptor10.writable, false); + +// Regression test for GH-42962. This assignment should not throw. +window.globalProxy.getSetPropReceivingFunction = () => {}; +assert.strictEqual(window.globalProxy.getSetPropReceivingFunction, 42); + +window.globalProxy.getSetPropReceivingNumber = 143; +assert.strictEqual(window.globalProxy.getSetPropReceivingNumber, 43); + +window.globalProxy.propReceivingNumber = 144; +assert.strictEqual(window.globalProxy.propReceivingNumber, 144); + +window.globalProxy.unknownPropReceivingNumber = 145; +assert.strictEqual(window.globalProxy.unknownPropReceivingNumber, 145); + +window.globalProxy[getSetSymbolReceivingFunction] = () => {}; +assert.strictEqual(window.globalProxy[getSetSymbolReceivingFunction], 46); + +window.globalProxy[getSetSymbolReceivingNumber] = 147; +assert.strictEqual(window.globalProxy[getSetSymbolReceivingNumber], 47); + +window.globalProxy[symbolReceivingNumber] = 148; +assert.strictEqual(window.globalProxy[symbolReceivingNumber], 148); + +window.globalProxy[unknownSymbolReceivingNumber] = 149; +assert.strictEqual(window.globalProxy[unknownSymbolReceivingNumber], 149); + +assert.throws( + () => (window.globalProxy.getSetPropThrowing = 150), + new Error('setter called') +); +assert.strictEqual(window.globalProxy.getSetPropThrowing, 50); + +assert.throws( + () => (window.globalProxy.nonWritableProp = 151), + new TypeError('Cannot redefine property: nonWritableProp') +); +assert.strictEqual(window.globalProxy.nonWritableProp, 51); + +function createWindow() { + const obj = {}; + vm.createContext(obj); + Object.defineProperty(obj, 'getSetPropReceivingFunction', { + get: common.mustCall(() => 42), + set: common.mustCall(), + configurable: true, + }); + Object.defineProperty(obj, 'getSetPropReceivingNumber', { + get: common.mustCall(() => 43), + set: common.mustCall(), + configurable: true, + }); + obj.propReceivingNumber = 44; + Object.defineProperty(obj, getSetSymbolReceivingFunction, { + get: common.mustCall(() => 46), + set: common.mustCall(), + configurable: true, + }); + Object.defineProperty(obj, getSetSymbolReceivingNumber, { + get: common.mustCall(() => 47), + set: common.mustCall(), + configurable: true, + }); + obj[symbolReceivingNumber] = 48; + Object.defineProperty(obj, 'getSetPropThrowing', { + get: common.mustCall(() => 50), + set: common.mustCall(() => { + throw new Error('setter called'); + }), + configurable: true, + }); + Object.defineProperty(obj, 'nonWritableProp', { + value: 51, + writable: false, + }); + + obj.globalProxy = vm.runInContext('this', obj); + + return obj; +} diff --git a/tests/node_compat/test/parallel/test-vm-harmony-symbols.js b/tests/node_compat/test/parallel/test-vm-harmony-symbols.js new file mode 100644 index 0000000000..d4713a2e24 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-harmony-symbols.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// The sandbox should have its own Symbol constructor. +let sandbox = {}; +vm.runInNewContext('this.Symbol = Symbol', sandbox); +assert.strictEqual(typeof sandbox.Symbol, 'function'); +assert.notStrictEqual(sandbox.Symbol, Symbol); + +// Unless we copy the Symbol constructor explicitly, of course. +sandbox = { Symbol }; +vm.runInNewContext('this.Symbol = Symbol', sandbox); +assert.strictEqual(typeof sandbox.Symbol, 'function'); +assert.strictEqual(sandbox.Symbol, Symbol); diff --git a/tests/node_compat/test/parallel/test-vm-indexed-properties.js b/tests/node_compat/test/parallel/test-vm-indexed-properties.js new file mode 100644 index 0000000000..332905ff74 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-indexed-properties.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const code = `Object.defineProperty(this, 99, { + value: 20, + enumerable: true + });`; + + +const sandbox = {}; +const ctx = vm.createContext(sandbox); +vm.runInContext(code, ctx); + +assert.strictEqual(sandbox[99], 20); diff --git a/tests/node_compat/test/parallel/test-vm-inherited_properties.js b/tests/node_compat/test/parallel/test-vm-inherited_properties.js new file mode 100644 index 0000000000..4994a8ceab --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-inherited_properties.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); + +const vm = require('vm'); +const assert = require('assert'); + +let base = { + propBase: 1 +}; + +let sandbox = Object.create(base, { + propSandbox: { value: 3 } +}); + +const context = vm.createContext(sandbox); + +let result = vm.runInContext('Object.hasOwnProperty(this, "propBase");', + context); + +assert.strictEqual(result, false); + +// Ref: https://github.com/nodejs/node/issues/5350 +base = { __proto__: null }; +base.x = 1; +base.y = 2; + +sandbox = { __proto__: base }; +sandbox.z = 3; + +assert.deepStrictEqual(Object.keys(sandbox), ['z']); + +const code = 'x = 0; z = 4;'; +result = vm.runInNewContext(code, sandbox); +assert.strictEqual(result, 4); + +// Check that y is not an own property. +assert.deepStrictEqual(Object.keys(sandbox), ['z', 'x']); diff --git a/tests/node_compat/test/parallel/test-vm-is-context.js b/tests/node_compat/test/parallel/test-vm-is-context.js new file mode 100644 index 0000000000..fdbaa36d66 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-is-context.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +for (const valToTest of [ + 'string', null, undefined, 8.9, Symbol('sym'), true, +]) { + assert.throws(() => { + vm.isContext(valToTest); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +} + +assert.strictEqual(vm.isContext({}), false); +assert.strictEqual(vm.isContext([]), false); + +assert.strictEqual(vm.isContext(vm.createContext()), true); +assert.strictEqual(vm.isContext(vm.createContext([])), true); + +const sandbox = { foo: 'bar' }; +vm.createContext(sandbox); +assert.strictEqual(vm.isContext(sandbox), true); diff --git a/tests/node_compat/test/parallel/test-vm-low-stack-space.js b/tests/node_compat/test/parallel/test-vm-low-stack-space.js new file mode 100644 index 0000000000..d101377246 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-low-stack-space.js @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +function a() { + try { + return a(); + } catch { + // Throw an exception as near to the recursion-based RangeError as possible. + return vm.runInThisContext('() => 42')(); + } +} + +assert.strictEqual(a(), 42); + +function b() { + try { + return b(); + } catch { + // This writes a lot of noise to stderr, but it still works. + return vm.runInNewContext('() => 42')(); + } +} + +assert.strictEqual(b(), 42); diff --git a/tests/node_compat/test/parallel/test-vm-new-script-new-context.js b/tests/node_compat/test/parallel/test-vm-new-script-new-context.js new file mode 100644 index 0000000000..aada162629 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-new-script-new-context.js @@ -0,0 +1,114 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); + +const assert = require('assert'); + +const Script = require('vm').Script; + +{ + const script = new Script('\'passed\';'); + const result1 = script.runInNewContext(); + const result2 = script.runInNewContext(); + assert.strictEqual(result1, 'passed'); + assert.strictEqual(result2, 'passed'); +} + +{ + const script = new Script('throw new Error(\'test\');'); + assert.throws(() => { + script.runInNewContext(); + }, /^Error: test$/); +} + +{ + const script = new Script('foo.bar = 5;'); + assert.throws(() => { + script.runInNewContext(); + }, /^ReferenceError: foo is not defined$/); +} + +{ + global.hello = 5; + const script = new Script('hello = 2'); + script.runInNewContext(); + assert.strictEqual(global.hello, 5); + + // Cleanup + delete global.hello; +} + +{ + global.code = 'foo = 1;' + + 'bar = 2;' + + 'if (baz !== 3) throw new Error(\'test fail\');'; + global.foo = 2; + global.obj = { foo: 0, baz: 3 }; + const script = new Script(global.code); + /* eslint-disable no-unused-vars */ + const baz = script.runInNewContext(global.obj); + /* eslint-enable no-unused-vars */ + assert.strictEqual(global.obj.foo, 1); + assert.strictEqual(global.obj.bar, 2); + assert.strictEqual(global.foo, 2); + + // cleanup + delete global.code; + delete global.foo; + delete global.obj; +} + +{ + const script = new Script('f()'); + function changeFoo() { global.foo = 100; } + script.runInNewContext({ f: changeFoo }); + assert.strictEqual(global.foo, 100); + + // cleanup + delete global.foo; +} + +{ + const script = new Script('f.a = 2'); + const f = { a: 1 }; + script.runInNewContext({ f }); + assert.strictEqual(f.a, 2); + + assert.throws(() => { + script.runInNewContext(); + }, /^ReferenceError: f is not defined$/); +} + +{ + const script = new Script(''); + assert.throws(() => { + script.runInNewContext.call('\'hello\';'); + }, /^TypeError: this\.runInContext is not a function$/); +} diff --git a/tests/node_compat/test/parallel/test-vm-not-strict.js b/tests/node_compat/test/parallel/test-vm-not-strict.js new file mode 100644 index 0000000000..c5dee3a2c6 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-not-strict.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +/* eslint-disable strict, no-var, no-delete-var, no-undef, node-core/required-modules, node-core/require-common-first */ +// Importing common would break the execution. Indeed running `vm.runInThisContext` alters the global context +// when declaring new variables with `var`. The other rules (strict, no-var, no-delete-var) have been disabled +// in order to be able to test this specific not-strict case playing with `var` and `delete`. +// Related to bug report: https://github.com/nodejs/node/issues/43129 +var assert = require('assert'); +var vm = require('vm'); + +var data = []; +var a = 'direct'; +delete a; +data.push(a); + +var item2 = vm.runInThisContext(` +var unusedB = 1; +var data = []; +var b = "this"; +delete b; +data.push(b); +data[0] +`); +data.push(item2); + +vm.runInContext( + ` +var unusedC = 1; +var c = "new"; +delete c; +data.push(c); +`, + vm.createContext({ data: data }) +); + +assert.deepStrictEqual(data, ['direct', 'this', 'new']); + +assert.strictEqual(typeof unusedB, 'number'); // Declared within runInThisContext +assert.strictEqual(typeof unusedC, 'undefined'); // Declared within runInContext diff --git a/tests/node_compat/test/parallel/test-vm-options-validation.js b/tests/node_compat/test/parallel/test-vm-options-validation.js new file mode 100644 index 0000000000..d1b215ed70 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-options-validation.js @@ -0,0 +1,101 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const invalidArgType = { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' +}; + +const outOfRange = { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE' +}; + +assert.throws(() => { + new vm.Script('void 0', 42); +}, invalidArgType); + +[null, {}, [1], 'bad', true].forEach((value) => { + assert.throws(() => { + new vm.Script('void 0', { lineOffset: value }); + }, invalidArgType); + + assert.throws(() => { + new vm.Script('void 0', { columnOffset: value }); + }, invalidArgType); +}); + +[0.1, 2 ** 32].forEach((value) => { + assert.throws(() => { + new vm.Script('void 0', { lineOffset: value }); + }, outOfRange); + + assert.throws(() => { + new vm.Script('void 0', { columnOffset: value }); + }, outOfRange); +}); + +assert.throws(() => { + new vm.Script('void 0', { lineOffset: Number.MAX_SAFE_INTEGER }); +}, outOfRange); + +assert.throws(() => { + new vm.Script('void 0', { columnOffset: Number.MAX_SAFE_INTEGER }); +}, outOfRange); + +assert.throws(() => { + new vm.Script('void 0', { filename: 123 }); +}, invalidArgType); + +assert.throws(() => { + new vm.Script('void 0', { produceCachedData: 1 }); +}, invalidArgType); + +[[0], {}, true, 'bad', 42].forEach((value) => { + assert.throws(() => { + new vm.Script('void 0', { cachedData: value }); + }, invalidArgType); +}); + +{ + const script = new vm.Script('void 0'); + const sandbox = vm.createContext(); + + function assertErrors(options, errCheck) { + assert.throws(() => { + script.runInThisContext(options); + }, errCheck); + + assert.throws(() => { + script.runInContext(sandbox, options); + }, errCheck); + + assert.throws(() => { + script.runInNewContext({}, options); + }, errCheck); + } + + [null, 'bad', 42].forEach((value) => { + assertErrors(value, invalidArgType); + }); + [{}, [1], 'bad', null].forEach((value) => { + assertErrors({ timeout: value }, invalidArgType); + }); + [-1, 0, NaN].forEach((value) => { + assertErrors({ timeout: value }, outOfRange); + }); + [{}, [1], 'bad', 1, null].forEach((value) => { + assertErrors({ displayErrors: value }, invalidArgType); + assertErrors({ breakOnSigint: value }, invalidArgType); + }); +} diff --git a/tests/node_compat/test/parallel/test-vm-parse-abort-on-uncaught-exception.js b/tests/node_compat/test/parallel/test-vm-parse-abort-on-uncaught-exception.js new file mode 100644 index 0000000000..e8cae690af --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-parse-abort-on-uncaught-exception.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Flags: --abort-on-uncaught-exception +'use strict'; +require('../common'); +const vm = require('vm'); + +// Regression test for https://github.com/nodejs/node/issues/13258 + +try { + new vm.Script({ toString() { throw new Error('foo'); } }, {}); +} catch { + // Continue regardless of error. +} + +try { + new vm.Script('[', {}); +} catch { + // Continue regardless of error. +} diff --git a/tests/node_compat/test/parallel/test-vm-preserves-property.js b/tests/node_compat/test/parallel/test-vm-preserves-property.js new file mode 100644 index 0000000000..28f662a1a2 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-preserves-property.js @@ -0,0 +1,32 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const vm = require('vm'); + +const x = {}; +Object.defineProperty(x, 'prop', { + configurable: false, + enumerable: false, + writable: false, + value: 'val' +}); +const o = vm.createContext(x); + +const code = 'Object.getOwnPropertyDescriptor(this, "prop")'; +const res = vm.runInContext(code, o, 'test'); + +assert(res); +assert.strictEqual(typeof res, 'object'); +assert.strictEqual(res.value, 'val'); +assert.strictEqual(res.configurable, false); +assert.strictEqual(res.enumerable, false); +assert.strictEqual(res.writable, false); diff --git a/tests/node_compat/test/parallel/test-vm-property-not-on-sandbox.js b/tests/node_compat/test/parallel/test-vm-property-not-on-sandbox.js new file mode 100644 index 0000000000..360a5dabf8 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-property-not-on-sandbox.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// This, admittedly contrived, example tests an edge cases of the vm module. +// +// The GetterCallback explicitly checks the global_proxy() if a property is +// not found on the sandbox. In the following test, the explicit check +// inside the callback yields different results than deferring the +// check until after the callback. The check is deferred if the +// callback does not intercept, i.e., if args.GetReturnValue().Set() is +// not called. + +// Check that the GetterCallback explicitly calls GetRealNamedProperty() +// on the global proxy if the property is not found on the sandbox. +// +// foo is not defined on the sandbox until we call CopyProperties(). +// In the GetterCallback, we do not find the property on the sandbox and +// get the property from the global proxy. Since the return value is +// the sandbox, we replace it by +// the global_proxy to keep the correct identities. +// +// This test case is partially inspired by +// https://github.com/nodejs/node/issues/855 +const sandbox = { console }; +sandbox.document = { defaultView: sandbox }; +vm.createContext(sandbox); +const code = `Object.defineProperty( + this, + 'foo', + { get: function() {return document.defaultView} } + ); + var result = foo === this;`; + +vm.runInContext(code, sandbox); +assert.strictEqual(sandbox.result, true); diff --git a/tests/node_compat/test/parallel/test-vm-proxies.js b/tests/node_compat/test/parallel/test-vm-proxies.js new file mode 100644 index 0000000000..c485e0a62a --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-proxies.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// src/node_contextify.cc filters out the Proxy object from the parent +// context. Make sure that the new context has a Proxy object of its own. +let sandbox = {}; +vm.runInNewContext('this.Proxy = Proxy', sandbox); +assert.strictEqual(typeof sandbox.Proxy, 'function'); +assert.notStrictEqual(sandbox.Proxy, Proxy); + +// Unless we copy the Proxy object explicitly, of course. +sandbox = { Proxy }; +vm.runInNewContext('this.Proxy = Proxy', sandbox); +assert.strictEqual(typeof sandbox.Proxy, 'function'); +assert.strictEqual(sandbox.Proxy, Proxy); diff --git a/tests/node_compat/test/parallel/test-vm-proxy-failure-CP.js b/tests/node_compat/test/parallel/test-vm-proxy-failure-CP.js new file mode 100644 index 0000000000..2f503bd312 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-proxy-failure-CP.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +require('../common'); +const vm = require('vm'); + +// Check that we do not accidentally query attributes. +// Issue: https://github.com/nodejs/node/issues/11902 +const handler = { + getOwnPropertyDescriptor: (target, prop) => { + throw new Error('whoops'); + } +}; +const sandbox = new Proxy({ foo: 'bar' }, handler); +const context = vm.createContext(sandbox); + +vm.runInContext('', context); diff --git a/tests/node_compat/test/parallel/test-vm-script-throw-in-tostring.js b/tests/node_compat/test/parallel/test-vm-script-throw-in-tostring.js new file mode 100644 index 0000000000..c135730864 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-script-throw-in-tostring.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const vm = require('vm'); + +assert.throws(() => { + new vm.Script({ + toString() { + throw new Error(); + } + }); +}, Error); diff --git a/tests/node_compat/test/parallel/test-vm-set-property-proxy.js b/tests/node_compat/test/parallel/test-vm-set-property-proxy.js new file mode 100644 index 0000000000..61f80902c9 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-set-property-proxy.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// Regression test for https://github.com/nodejs/node/issues/34606 + +const handler = { + getOwnPropertyDescriptor: common.mustCallAtLeast(() => { + return {}; + }) +}; + +const proxy = new Proxy({}, handler); +assert.throws(() => vm.runInNewContext('p = 6', proxy), + /getOwnPropertyDescriptor/); diff --git a/tests/node_compat/test/parallel/test-vm-set-proto-null-on-globalthis.js b/tests/node_compat/test/parallel/test-vm-set-proto-null-on-globalthis.js new file mode 100644 index 0000000000..7798646680 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-set-proto-null-on-globalthis.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +require('../common'); + +// Setting __proto__ on vm context's globalThis should not cause a crash +// Regression test for https://github.com/nodejs/node/issues/47798 + +const vm = require('vm'); +const context = vm.createContext(); + +const contextGlobalThis = vm.runInContext('this', context); + +// Should not crash. +contextGlobalThis.__proto__ = null; // eslint-disable-line no-proto diff --git a/tests/node_compat/test/parallel/test-vm-source-map-url.js b/tests/node_compat/test/parallel/test-vm-source-map-url.js new file mode 100644 index 0000000000..fb91ff1fc5 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-source-map-url.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +function checkSourceMapUrl(source, expectedSourceMapURL) { + const script = new vm.Script(source); + assert.strictEqual(script.sourceMapURL, expectedSourceMapURL); +} + +// No magic comment +checkSourceMapUrl(` +function myFunc() {} +`, undefined); + +// Malformed magic comment +checkSourceMapUrl(` +function myFunc() {} +// sourceMappingURL=sourcemap.json +`, undefined); + +// Expected magic comment +checkSourceMapUrl(` +function myFunc() {} +//# sourceMappingURL=sourcemap.json +`, 'sourcemap.json'); diff --git a/tests/node_compat/test/parallel/test-vm-strict-mode.js b/tests/node_compat/test/parallel/test-vm-strict-mode.js new file mode 100644 index 0000000000..e759bd2c47 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-strict-mode.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +// https://github.com/nodejs/node/issues/12300 + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const ctx = vm.createContext({ x: 42 }); + +// This might look as if x has not been declared, but x is defined on the +// sandbox and the assignment should not throw. +vm.runInContext('"use strict"; x = 1', ctx); + +assert.strictEqual(ctx.x, 1); diff --git a/tests/node_compat/test/parallel/test-vm-symbols.js b/tests/node_compat/test/parallel/test-vm-symbols.js new file mode 100644 index 0000000000..fbaff6a829 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-symbols.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const vm = require('vm'); + +const symbol = Symbol(); + +function Document() { + this[symbol] = 'foo'; +} + +Document.prototype.getSymbolValue = function() { + return this[symbol]; +}; + +const context = new Document(); +vm.createContext(context); + +assert.strictEqual(context.getSymbolValue(), 'foo'); + +assert.strictEqual(vm.runInContext('this.getSymbolValue()', context), 'foo'); diff --git a/tests/node_compat/test/parallel/test-vm-timeout-escape-promise-2.js b/tests/node_compat/test/parallel/test-vm-timeout-escape-promise-2.js new file mode 100644 index 0000000000..19ba0a6821 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-timeout-escape-promise-2.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +// https://github.com/nodejs/node/issues/3020 +// Promises used to allow code to escape the timeout +// set for runInContext, runInNewContext, and runInThisContext. + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const NS_PER_MS = 1000000n; + +const hrtime = process.hrtime.bigint; + +function loop() { + const start = hrtime(); + while (1) { + const current = hrtime(); + const span = (current - start) / NS_PER_MS; + if (span >= 2000n) { + throw new Error( + `escaped timeout at ${span} milliseconds!`); + } + } +} + +assert.throws(() => { + vm.runInNewContext( + 'Promise.resolve().then(() => loop());', + { + hrtime, + loop + }, + { timeout: 10, microtaskMode: 'afterEvaluate' } + ); +}, { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 10ms' +}); diff --git a/tests/node_compat/test/parallel/test-vm-timeout-escape-promise.js b/tests/node_compat/test/parallel/test-vm-timeout-escape-promise.js new file mode 100644 index 0000000000..c2393a852a --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-timeout-escape-promise.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +// https://github.com/nodejs/node/issues/3020 +// Promises used to allow code to escape the timeout +// set for runInContext, runInNewContext, and runInThisContext. + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const NS_PER_MS = 1000000n; + +const hrtime = process.hrtime.bigint; + +function loop() { + const start = hrtime(); + while (1) { + const current = hrtime(); + const span = (current - start) / NS_PER_MS; + if (span >= 2000n) { + throw new Error( + `escaped timeout at ${span} milliseconds!`); + } + } +} + +assert.throws(() => { + vm.runInNewContext( + 'Promise.resolve().then(() => loop()); loop();', + { + hrtime, + loop + }, + { timeout: 5, microtaskMode: 'afterEvaluate' } + ); +}, { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 5ms' +}); diff --git a/tests/node_compat/test/parallel/test-vm-timeout.js b/tests/node_compat/test/parallel/test-vm-timeout.js new file mode 100644 index 0000000000..d345206404 --- /dev/null +++ b/tests/node_compat/test/parallel/test-vm-timeout.js @@ -0,0 +1,88 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// Timeout of 100ms executing endless loop +assert.throws( + function() { + vm.runInThisContext('while(true) {}', { timeout: 100 }); + }, + { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 100ms' + }); + +// Timeout of 1000ms, script finishes first +vm.runInThisContext('', { timeout: 1000 }); + +// Nested vm timeouts, inner timeout propagates out +assert.throws( + function() { + const context = { + log: console.log, + runInVM: function(timeout) { + vm.runInNewContext('while(true) {}', context, { timeout }); + } + }; + vm.runInNewContext('runInVM(10)', context, { timeout: 10000 }); + throw new Error('Test 5 failed'); + }, + { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 10ms' + }); + +// Nested vm timeouts, outer timeout is shorter and fires first. +assert.throws( + function() { + const context = { + runInVM: function(timeout) { + vm.runInNewContext('while(true) {}', context, { timeout }); + } + }; + vm.runInNewContext('runInVM(10000)', context, { timeout: 100 }); + throw new Error('Test 6 failed'); + }, + { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 100ms' + }); + +// Nested vm timeouts, inner script throws an error. +assert.throws(function() { + const context = { + runInVM: function(timeout) { + vm.runInNewContext('throw new Error(\'foobar\')', context, { timeout }); + } + }; + vm.runInNewContext('runInVM(10000)', context, { timeout: 100000 }); +}, /foobar/); diff --git a/tests/unit_node/vm_test.ts b/tests/unit_node/vm_test.ts index 4c370931de..85b9556637 100644 --- a/tests/unit_node/vm_test.ts +++ b/tests/unit_node/vm_test.ts @@ -128,7 +128,7 @@ Deno.test({ const obj = {}; assertEquals(isContext(obj), false); assertEquals(isContext(globalThis), false); - const sandbox = runInNewContext("{}"); + const sandbox = runInNewContext("({})"); assertEquals(isContext(sandbox), false); }, });