fix(ext/node): basic vm.runInNewContext implementation (#21527)

Simple implementation to support webpack (& Next.js):
8766092180/lib/javascript/JavascriptParser.js (L4329)
This commit is contained in:
Divy Srivastava 2023-12-11 12:38:45 +05:30 committed by GitHub
parent 0bee37a5e2
commit 02e138dca9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 7 deletions

View file

@ -82,6 +82,7 @@ util::unit_test_factory!(
tty_test,
util_test,
v8_test,
vm_test,
worker_threads_test,
zlib_test
]

View file

@ -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);
}
},
});

View file

@ -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,

View file

@ -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()
}
})
}

View file

@ -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(

View file

@ -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 {