Files
bun.sh/src/js/node/vm.ts

401 lines
11 KiB
TypeScript

// Hardcoded module "node:vm"
const { SafePromiseAllReturnArrayLike } = require("internal/primordials");
const { throwNotImplemented } = require("internal/shared");
const {
validateObject,
validateString,
validateUint32,
validateBoolean,
validateInt32,
validateBuffer,
validateFunction,
} = require("internal/validators");
const util = require("node:util");
const vm = $cpp("NodeVM.cpp", "Bun::createNodeVMBinding");
const ObjectFreeze = Object.freeze;
const ObjectDefineProperty = Object.defineProperty;
const ArrayPrototypeMap = Array.prototype.map;
const PromisePrototypeThen = Promise.prototype.then;
const PromiseResolve = Promise.resolve.bind(Promise);
const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const ObjectSetPrototypeOf = Object.setPrototypeOf;
const ObjectGetPrototypeOf = Object.getPrototypeOf;
const SymbolToStringTag = Symbol.toStringTag;
const kPerContextModuleId = Symbol("kPerContextModuleId");
const kNative = Symbol("kNative");
const kContext = Symbol("kContext");
const kLink = Symbol("kLink");
const kDependencySpecifiers = Symbol("kDependencySpecifiers");
const kNoError = Symbol("kNoError");
const kEmptyObject = Object.freeze(Object.create(null));
const {
Script,
Module: ModuleNative,
createContext,
isContext,
runInNewContext,
runInThisContext,
compileFunction,
isModuleNamespaceObject,
kUnlinked,
kLinked,
kEvaluated,
kErrored,
} = vm;
function runInContext(code, context, options) {
return new Script(code, options).runInContext(context, options);
}
function createScript(code, options) {
return new Script(code, options);
}
function measureMemory() {
throwNotImplemented("node:vm measureMemory");
}
let globalModuleId = 0;
const defaultModuleName = "vm:module";
class Module {
constructor(options) {
if (new.target === Module) {
throw new TypeError("Module is not a constructor");
}
const { context, sourceText, syntheticExportNames, syntheticEvaluationSteps } = options;
if (context !== undefined) {
validateObject(context, "context");
if (!isContext(context)) {
throw $ERR_INVALID_ARG_TYPE("options.context", "vm.Context", context);
}
}
let { identifier } = options;
if (identifier !== undefined) {
validateString(identifier, "options.identifier");
} else if (context === undefined) {
identifier = `${defaultModuleName}(${globalModuleId++})`;
} else if (context[kPerContextModuleId] !== undefined) {
const curId = context[kPerContextModuleId];
identifier = `${defaultModuleName}(${curId})`;
context[kPerContextModuleId] += 1;
} else {
identifier = `${defaultModuleName}(0)`;
ObjectDefineProperty(context, kPerContextModuleId, {
__proto__: null,
value: 1,
writable: true,
enumerable: false,
configurable: true,
});
}
let registry = { __proto__: null };
if (sourceText !== undefined) {
this[kNative] = new ModuleNative(
identifier,
context,
sourceText,
options.lineOffset,
options.columnOffset,
options.cachedData,
);
registry = {
__proto__: null,
initializeImportMeta: options.initializeImportMeta,
importModuleDynamically: options.importModuleDynamically
? importModuleDynamicallyWrap(options.importModuleDynamically)
: undefined,
};
// This will take precedence over the referrer as the object being
// passed into the callbacks.
registry.callbackReferrer = this;
// const { registerModule } = require("internal/modules/esm/utils");
// registerModule(this[kNative], registry);
} else {
$assert(syntheticEvaluationSteps);
this[kNative] = new ModuleNative(identifier, context, syntheticExportNames, syntheticEvaluationSteps);
}
this[kContext] = context;
}
get identifier() {
return this[kNative].identifier;
}
get context() {
return this[kContext];
}
get status() {
return this[kNative].getStatus();
}
get namespace() {
if (this[kNative].getStatusCode() < kLinked) {
throw $ERR_VM_MODULE_STATUS("must not be unlinked or linking");
}
return this[kNative].getNamespace();
}
get error() {
if (this[kNative].getStatusCode() !== kErrored) {
throw $ERR_VM_MODULE_STATUS("must be errored");
}
return this[kNative].getError();
}
async link(linker) {
if (this[kNative].getStatusCode() === kLinked) {
throw $ERR_VM_MODULE_ALREADY_LINKED();
}
if (this[kNative].getStatusCode() !== kUnlinked) {
throw $ERR_VM_MODULE_STATUS("must be unlinked");
}
await this[kLink](linker);
this[kNative].instantiate();
}
async evaluate(options = kEmptyObject) {
validateObject(options, "options");
let timeout = options.timeout;
if (timeout === undefined) {
timeout = -1;
} else {
validateUint32(timeout, "options.timeout", true);
}
const { breakOnSigint = false } = options;
validateBoolean(breakOnSigint, "options.breakOnSigint");
const status = this[kNative].getStatusCode();
if (status !== kLinked && status !== kEvaluated && status !== kErrored) {
throw $ERR_VM_MODULE_STATUS("must be one of linked, evaluated, or errored");
}
await this[kNative].evaluate(timeout, breakOnSigint);
}
[util.inspect.custom](depth, options) {
if (typeof depth === "number" && depth < 0) return this;
const constructor = getConstructorOf(this) || Module;
const o = { __proto__: { constructor } };
o.status = this.status;
o.identifier = this.identifier;
o.context = this.context;
ObjectSetPrototypeOf(o, ObjectGetPrototypeOf(this));
ObjectDefineProperty(o, SymbolToStringTag, {
__proto__: null,
value: constructor.name,
configurable: true,
});
return util.inspect(o, { ...options, customInspect: false });
}
}
class SourceTextModule extends Module {
#error: any = kNoError;
#statusOverride: any;
constructor(sourceText, options = kEmptyObject) {
validateString(sourceText, "sourceText");
validateObject(options, "options");
const {
lineOffset = 0,
columnOffset = 0,
initializeImportMeta,
importModuleDynamically,
context,
identifier,
cachedData,
} = options;
validateInt32(lineOffset, "options.lineOffset");
validateInt32(columnOffset, "options.columnOffset");
if (initializeImportMeta !== undefined) {
validateFunction(initializeImportMeta, "options.initializeImportMeta");
}
if (importModuleDynamically !== undefined) {
validateFunction(importModuleDynamically, "options.importModuleDynamically");
}
if (cachedData !== undefined) {
validateBuffer(cachedData, "options.cachedData");
}
super({
sourceText,
context,
identifier,
lineOffset,
columnOffset,
cachedData,
initializeImportMeta,
importModuleDynamically,
});
this[kDependencySpecifiers] = undefined;
}
async [kLink](linker) {
if (this[kNative].getStatusCode() >= kLinked) {
throw $ERR_VM_MODULE_ALREADY_LINKED();
}
this.#statusOverride = "linking";
const moduleRequests = this[kNative].createModuleRecord();
// Iterates the module requests and links with the linker.
// Specifiers should be aligned with the moduleRequests array in order.
const specifiers = Array(moduleRequests.length);
const modulePromises = Array(moduleRequests.length);
// Iterates with index to avoid calling into userspace with `Symbol.iterator`.
for (let idx = 0; idx < moduleRequests.length; idx++) {
const { specifier, attributes } = moduleRequests[idx];
const linkerResult = linker(specifier, this, {
attributes,
assert: attributes,
});
const modulePromise = PromisePrototypeThen.$call(PromiseResolve(linkerResult), async mod => {
if (!isModule(mod)) {
throw $ERR_VM_MODULE_NOT_MODULE();
}
if (mod.context !== this.context) {
throw $ERR_VM_MODULE_DIFFERENT_CONTEXT();
}
if (mod.status === "errored") {
throw $ERR_VM_MODULE_LINK_FAILURE(`request for '${specifier}' resolved to an errored mod`, mod.error);
}
if (mod.status === "unlinked") {
await mod[kLink](linker);
}
return mod[kNative];
});
modulePromises[idx] = modulePromise;
specifiers[idx] = specifier;
}
try {
const moduleNatives = await SafePromiseAllReturnArrayLike(modulePromises);
this[kNative].link(specifiers, moduleNatives, 0);
} catch (e) {
this.#error = e;
throw e;
} finally {
this.#statusOverride = undefined;
}
}
get dependencySpecifiers() {
this[kDependencySpecifiers] ??= ObjectFreeze(
ArrayPrototypeMap.$call(this[kNative].getModuleRequests(), request => request[0]),
);
return this[kDependencySpecifiers];
}
get status() {
if (this.#error !== kNoError) {
return "errored";
}
if (this.#statusOverride) {
return this.#statusOverride;
}
return super.status;
}
get error() {
if (this.#error !== kNoError) {
return this.#error;
}
return super.error;
}
createCachedData() {
const { status } = this;
if (status === "evaluating" || status === "evaluated" || status === "errored") {
throw $ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA();
}
return this[kNative].createCachedData();
}
}
class SyntheticModule {
constructor() {
throwNotImplemented("node:vm.SyntheticModule");
}
}
const constants = {
__proto__: null,
USE_MAIN_CONTEXT_DEFAULT_LOADER: Symbol("vm_dynamic_import_main_context_default"),
DONT_CONTEXTIFY: Symbol("vm_context_no_contextify"),
};
function isModule(object) {
return typeof object === "object" && object !== null && ObjectPrototypeHasOwnProperty.$call(object, kNative);
}
function importModuleDynamicallyWrap(importModuleDynamically) {
const importModuleDynamicallyWrapper = async (...args) => {
const m: any = importModuleDynamically.$apply(this, args);
if (isModuleNamespaceObject(m)) {
return m;
}
if (!isModule(m)) {
throw $ERR_VM_MODULE_NOT_MODULE();
}
if (m.status === "errored") {
throw m.error;
}
return m.namespace;
};
return importModuleDynamicallyWrapper;
}
function getConstructorOf(obj) {
while (obj) {
const descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor");
if (descriptor !== undefined && typeof descriptor.value === "function" && descriptor.value.name !== "") {
return descriptor.value;
}
obj = ObjectGetPrototypeOf(obj);
}
}
export default {
createContext,
runInContext,
runInNewContext,
runInThisContext,
isContext,
compileFunction,
measureMemory,
Script,
Module,
SourceTextModule,
SyntheticModule,
createScript,
constants: ObjectFreeze(constants),
};