Skip to content

Commit

Permalink
fix(ext/node): basic vm.runInNewContext implementation (#21527)
Browse files Browse the repository at this point in the history
  • Loading branch information
littledivy authored Dec 11, 2023
1 parent 0bee37a commit 02e138d
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 7 deletions.
1 change: 1 addition & 0 deletions cli/tests/integration/node_unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ util::unit_test_factory!(
tty_test,
util_test,
v8_test,
vm_test,
worker_threads_test,
zlib_test
]
Expand Down
57 changes: 57 additions & 0 deletions cli/tests/unit_node/vm_test.ts
Original file line number Diff line number Diff line change
@@ -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);
}
},
});
2 changes: 2 additions & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
48 changes: 48 additions & 0 deletions ext/node/ops/v8.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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()
}
})
}
21 changes: 15 additions & 6 deletions ext/node/polyfills/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -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(
Expand Down
9 changes: 8 additions & 1 deletion runtime/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 02e138d

Please sign in to comment.