diff --git a/cli/tests/integration/node_unit_tests.rs b/cli/tests/integration/node_unit_tests.rs index 9aad274e38..14847e9db4 100644 --- a/cli/tests/integration/node_unit_tests.rs +++ b/cli/tests/integration/node_unit_tests.rs @@ -82,6 +82,7 @@ util::unit_test_factory!( tty_test, util_test, v8_test, + vm_test, worker_threads_test, zlib_test ] diff --git a/cli/tests/unit_node/vm_test.ts b/cli/tests/unit_node/vm_test.ts new file mode 100644 index 0000000000..c43495e1d3 --- /dev/null +++ b/cli/tests/unit_node/vm_test.ts @@ -0,0 +1,57 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { runInNewContext } from "node:vm"; +import { + assertEquals, + assertThrows, +} from "../../../test_util/std/assert/mod.ts"; + +Deno.test({ + name: "vm runInNewContext", + fn() { + const two = runInNewContext("1 + 1"); + assertEquals(two, 2); + }, +}); + +Deno.test({ + name: "vm runInNewContext sandbox", + fn() { + assertThrows(() => runInNewContext("Deno")); + // deno-lint-ignore no-var + var a = 1; + assertThrows(() => runInNewContext("a + 1")); + + runInNewContext("a = 2"); + assertEquals(a, 1); + }, +}); + +// https://github.com/webpack/webpack/blob/87660921808566ef3b8796f8df61bd79fc026108/lib/javascript/JavascriptParser.js#L4329 +Deno.test({ + name: "vm runInNewContext webpack magic comments", + fn() { + const webpackCommentRegExp = new RegExp( + /(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/, + ); + const comments = [ + 'webpackChunkName: "test"', + 'webpackMode: "lazy"', + "webpackPrefetch: true", + "webpackPreload: true", + "webpackProvidedExports: true", + 'webpackChunkLoading: "require"', + 'webpackExports: ["default", "named"]', + ]; + + for (const comment of comments) { + const result = webpackCommentRegExp.test(comment); + assertEquals(result, true); + + const [[key, _value]]: [string, string][] = Object.entries( + runInNewContext(`(function(){return {${comment}};})()`), + ); + const expectedKey = comment.split(":")[0].trim(); + assertEquals(key, expectedKey); + } + }, +}); diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 547f1d60a1..56f4b0ee07 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -30,6 +30,7 @@ mod path; mod polyfill; mod resolution; +pub use ops::v8::VM_CONTEXT_INDEX; pub use package_json::PackageJson; pub use path::PathClean; pub use polyfill::is_builtin_node_module; @@ -243,6 +244,7 @@ deno_core::extension!(deno_node, ops::winerror::op_node_sys_to_uv_error, ops::v8::op_v8_cached_data_version_tag, ops::v8::op_v8_get_heap_statistics, + ops::v8::op_vm_run_in_new_context, ops::idna::op_node_idna_domain_to_ascii, ops::idna::op_node_idna_domain_to_unicode, ops::idna::op_node_idna_punycode_decode, diff --git a/ext/node/ops/v8.rs b/ext/node/ops/v8.rs index dbb84e9329..17af493589 100644 --- a/ext/node/ops/v8.rs +++ b/ext/node/ops/v8.rs @@ -1,4 +1,5 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_core::error::AnyError; use deno_core::op2; use deno_core::v8; @@ -30,3 +31,50 @@ pub fn op_v8_get_heap_statistics( buffer[12] = stats.used_global_handles_size() as f64; buffer[13] = stats.external_memory() as f64; } + +pub const VM_CONTEXT_INDEX: usize = 0; + +fn make_context<'a>( + scope: &mut v8::HandleScope<'a>, +) -> v8::Local<'a, v8::Context> { + let scope = &mut v8::EscapableHandleScope::new(scope); + let context = v8::Context::from_snapshot(scope, VM_CONTEXT_INDEX).unwrap(); + scope.escape(context) +} + +#[op2] +pub fn op_vm_run_in_new_context<'a>( + scope: &mut v8::HandleScope<'a>, + script: v8::Local, + ctx_val: v8::Local, +) -> Result, AnyError> { + let _ctx_obj = if ctx_val.is_undefined() || ctx_val.is_null() { + v8::Object::new(scope) + } else { + ctx_val.try_into()? + }; + + let ctx = make_context(scope); + + let scope = &mut v8::ContextScope::new(scope, ctx); + + let tc_scope = &mut v8::TryCatch::new(scope); + let script = match v8::Script::compile(tc_scope, script, None) { + Some(s) => s, + None => { + assert!(tc_scope.has_caught()); + tc_scope.rethrow(); + return Ok(v8::undefined(tc_scope).into()); + } + }; + + Ok(match script.run(tc_scope) { + Some(result) => result, + None => { + assert!(tc_scope.has_caught()); + tc_scope.rethrow(); + + v8::undefined(tc_scope).into() + } + }) +} diff --git a/ext/node/polyfills/vm.ts b/ext/node/polyfills/vm.ts index 39cd1ce360..45c67526ed 100644 --- a/ext/node/polyfills/vm.ts +++ b/ext/node/polyfills/vm.ts @@ -6,6 +6,7 @@ import { notImplemented } from "ext:deno_node/_utils.ts"; const { core } = globalThis.__bootstrap; +const ops = core.ops; export class Script { code: string; @@ -25,8 +26,13 @@ export class Script { notImplemented("Script.prototype.runInContext"); } - runInNewContext(_contextObject: any, _options: any) { - notImplemented("Script.prototype.runInNewContext"); + runInNewContext(contextObject: any, options: any) { + if (options) { + console.warn( + "Script.runInNewContext options are currently not supported", + ); + } + return ops.op_vm_run_in_new_context(this.code, contextObject); } createCachedData() { @@ -51,11 +57,14 @@ export function runInContext( } export function runInNewContext( - _code: string, - _contextObject: any, - _options: any, + code: string, + contextObject: any, + options: any, ) { - notImplemented("runInNewContext"); + if (options) { + console.warn("vm.runInNewContext options are currently not supported"); + } + return ops.op_vm_run_in_new_context(code, contextObject); } export function runInThisContext( diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs index c2e8b0df24..6a9fb4a2b7 100644 --- a/runtime/snapshot.rs +++ b/runtime/snapshot.rs @@ -7,6 +7,7 @@ use crate::shared::runtime; use deno_cache::SqliteBackedCache; use deno_core::error::AnyError; use deno_core::snapshot_util::*; +use deno_core::v8; use deno_core::Extension; use deno_http::DefaultHttpPropertyExtractor; use std::path::Path; @@ -261,7 +262,13 @@ pub fn create_runtime_snapshot( startup_snapshot: None, extensions, compression_cb: None, - with_runtime_cb: None, + with_runtime_cb: Some(Box::new(|rt| { + let isolate = rt.v8_isolate(); + let scope = &mut v8::HandleScope::new(isolate); + + let ctx = v8::Context::new(scope); + assert_eq!(scope.add_context(ctx), deno_node::VM_CONTEXT_INDEX); + })), skip_op_registration: false, }); for path in output.files_loaded_during_snapshot {