summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/vm.js
diff options
context:
space:
mode:
authorsnek <snek@deno.com>2024-08-06 14:52:53 +0200
committerGitHub <noreply@github.com>2024-08-06 12:52:53 +0000
commit897159dc6e1b2319cf2f5f09d8d6cecc0d3175fa (patch)
treecfe4a043d1fc102a4e051b99c7fcbef7b79bbb91 /ext/node/polyfills/vm.js
parentc0e9512b39a4ed3713d1fd9b28469d0edf68f578 (diff)
feat: vm rewrite (#24596)
rewrite vm implementation to increase compat. vm.Module+importModuleDynamically callbacks should be added in a followup.
Diffstat (limited to 'ext/node/polyfills/vm.js')
-rw-r--r--ext/node/polyfills/vm.js359
1 files changed, 359 insertions, 0 deletions
diff --git a/ext/node/polyfills/vm.js b/ext/node/polyfills/vm.js
new file mode 100644
index 000000000..bc1a25045
--- /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.<anonymous>",
+ 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,
+};