summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2023-12-11 12:38:45 +0530
committerGitHub <noreply@github.com>2023-12-11 07:08:45 +0000
commit02e138dca9c9fd79f47d352114b45b21dbb2b2ba (patch)
tree396fce46b544635ecec93a0c7592d570e48474cb
parent0bee37a5e24048cbf92c1b56efd0c65deaea1418 (diff)
fix(ext/node): basic vm.runInNewContext implementation (#21527)
Simple implementation to support webpack (& Next.js): https://github.com/webpack/webpack/blob/87660921808566ef3b8796f8df61bd79fc026108/lib/javascript/JavascriptParser.js#L4329
-rw-r--r--cli/tests/integration/node_unit_tests.rs1
-rw-r--r--cli/tests/unit_node/vm_test.ts57
-rw-r--r--ext/node/lib.rs2
-rw-r--r--ext/node/ops/v8.rs48
-rw-r--r--ext/node/polyfills/vm.ts21
-rw-r--r--runtime/snapshot.rs9
6 files changed, 131 insertions, 7 deletions
diff --git a/cli/tests/integration/node_unit_tests.rs b/cli/tests/integration/node_unit_tests.rs
index 9aad274e3..14847e9db 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 000000000..c43495e1d
--- /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 547f1d60a..56f4b0ee0 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 dbb84e932..17af49358 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<v8::String>,
+ ctx_val: v8::Local<v8::Value>,
+) -> Result<v8::Local<'a, v8::Value>, 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 39cd1ce36..45c67526e 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 c2e8b0df2..6a9fb4a2b 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 {