From f9caee986cae09cd4cb47e0a02a5b8672ab2c16d Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 4 Oct 2019 20:08:00 -0700 Subject: vm: add Synthetic modules - Refactor vm.SourceTextModule (again) - Add vm.Module abstract superclass - Add vm.SyntheticModule Refs: https://heycam.github.io/webidl/#synthetic-module-records PR-URL: https://github.com/nodejs/node/pull/29864 Reviewed-By: Guy Bedford --- lib/internal/vm/module.js | 409 ++++++++++++++++++++++++++++++++++ lib/internal/vm/source_text_module.js | 333 --------------------------- 2 files changed, 409 insertions(+), 333 deletions(-) create mode 100644 lib/internal/vm/module.js delete mode 100644 lib/internal/vm/source_text_module.js (limited to 'lib/internal/vm') diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js new file mode 100644 index 0000000000..169313c8dd --- /dev/null +++ b/lib/internal/vm/module.js @@ -0,0 +1,409 @@ +'use strict'; + +const { Object, Symbol, SafePromise } = primordials; + +const { isContext } = internalBinding('contextify'); +const { isModuleNamespaceObject } = require('internal/util/types'); +const { + getConstructorOf, + customInspectSymbol, + emitExperimentalWarning, +} = require('internal/util'); +const { + ERR_INVALID_ARG_TYPE, + ERR_VM_MODULE_ALREADY_LINKED, + ERR_VM_MODULE_DIFFERENT_CONTEXT, + ERR_VM_MODULE_LINKING_ERRORED, + ERR_VM_MODULE_NOT_MODULE, + ERR_VM_MODULE_STATUS, +} = require('internal/errors').codes; +const { + validateInt32, + validateUint32, + validateString, +} = require('internal/validators'); + +const binding = internalBinding('module_wrap'); +const { + ModuleWrap, + kUninstantiated, + kInstantiating, + kInstantiated, + kEvaluating, + kEvaluated, + kErrored, +} = binding; + +const STATUS_MAP = { + [kUninstantiated]: 'unlinked', + [kInstantiating]: 'linking', + [kInstantiated]: 'linked', + [kEvaluating]: 'evaluating', + [kEvaluated]: 'evaluated', + [kErrored]: 'errored', +}; + +let globalModuleId = 0; +const defaultModuleName = 'vm:module'; +const wrapToModuleMap = new WeakMap(); + +const kWrap = Symbol('kWrap'); +const kContext = Symbol('kContext'); +const kPerContextModuleId = Symbol('kPerContextModuleId'); +const kLink = Symbol('kLink'); + +class Module { + constructor(options) { + emitExperimentalWarning('VM Modules'); + + if (new.target === Module) { + // eslint-disable-next-line no-restricted-syntax + throw new TypeError('Module is not a constructor'); + } + + const { + context, + sourceText, + syntheticExportNames, + syntheticEvaluationSteps, + } = options; + + if (context !== undefined) { + if (typeof context !== 'object' || context === null) { + throw new ERR_INVALID_ARG_TYPE('options.context', 'Object', context); + } + if (!isContext(context)) { + throw new 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)`; + Object.defineProperty(context, kPerContextModuleId, { + value: 1, + writable: true, + enumerable: false, + configurable: true, + }); + } + + if (sourceText !== undefined) { + this[kWrap] = new ModuleWrap(identifier, context, sourceText, + options.lineOffset, options.columnOffset); + + binding.callbackMap.set(this[kWrap], { + initializeImportMeta: options.initializeImportMeta, + importModuleDynamically: options.importModuleDynamically ? + importModuleDynamicallyWrap(options.importModuleDynamically) : + undefined, + }); + } else if (syntheticEvaluationSteps) { + this[kWrap] = new ModuleWrap(identifier, context, + syntheticExportNames, + syntheticEvaluationSteps); + } else { + CHECK(false); + } + + wrapToModuleMap.set(this[kWrap], this); + + this[kContext] = context; + } + + get identifier() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + return this[kWrap].url; + } + + get context() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + return this[kContext]; + } + + get namespace() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (this[kWrap].getStatus() < kInstantiated) { + throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking'); + } + return this[kWrap].getNamespace(); + } + + get status() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + return STATUS_MAP[this[kWrap].getStatus()]; + } + + get error() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (this[kWrap].getStatus() !== kErrored) { + throw new ERR_VM_MODULE_STATUS('must be errored'); + } + return this[kWrap].getError(); + } + + async link(linker) { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (typeof linker !== 'function') { + throw new ERR_INVALID_ARG_TYPE('linker', 'function', linker); + } + if (this.status === 'linked') { + throw new ERR_VM_MODULE_ALREADY_LINKED(); + } + if (this.status !== 'unlinked') { + throw new ERR_VM_MODULE_STATUS('must be unlinked'); + } + await this[kLink](linker); + this[kWrap].instantiate(); + } + + async evaluate(options = {}) { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + + if (typeof options !== 'object' || options === null) { + throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); + } + + let timeout = options.timeout; + if (timeout === undefined) { + timeout = -1; + } else { + validateUint32(timeout, 'options.timeout', true); + } + const { breakOnSigint = false } = options; + if (typeof breakOnSigint !== 'boolean') { + throw new ERR_INVALID_ARG_TYPE('options.breakOnSigint', 'boolean', + breakOnSigint); + } + const status = this[kWrap].getStatus(); + if (status !== kInstantiated && + status !== kEvaluated && + status !== kErrored) { + throw new ERR_VM_MODULE_STATUS( + 'must be one of linked, evaluated, or errored' + ); + } + const result = this[kWrap].evaluate(timeout, breakOnSigint); + return { __proto__: null, result }; + } + + [customInspectSymbol](depth, options) { + let ctor = getConstructorOf(this); + ctor = ctor === null ? Module : ctor; + + if (typeof depth === 'number' && depth < 0) + return options.stylize(`[${ctor.name}]`, 'special'); + + const o = Object.create({ constructor: ctor }); + o.status = this.status; + o.identifier = this.identifier; + o.context = this.context; + return require('internal/util/inspect').inspect(o, options); + } +} + +const kDependencySpecifiers = Symbol('kDependencySpecifiers'); +const kNoError = Symbol('kNoError'); + +class SourceTextModule extends Module { + #error = kNoError; + #statusOverride; + + constructor(sourceText, options = {}) { + validateString(sourceText, 'sourceText'); + + if (typeof options !== 'object' || options === null) { + throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); + } + + const { + lineOffset = 0, + columnOffset = 0, + initializeImportMeta, + importModuleDynamically, + context, + identifier, + } = options; + + validateInt32(lineOffset, 'options.lineOffset'); + validateInt32(columnOffset, 'options.columnOffset'); + + if (initializeImportMeta !== undefined && + typeof initializeImportMeta !== 'function') { + throw new ERR_INVALID_ARG_TYPE( + 'options.initializeImportMeta', 'function', initializeImportMeta); + } + + if (importModuleDynamically !== undefined && + typeof importModuleDynamically !== 'function') { + throw new ERR_INVALID_ARG_TYPE( + 'options.importModuleDynamically', 'function', + importModuleDynamically); + } + + super({ + sourceText, + context, + identifier, + lineOffset, + columnOffset, + initializeImportMeta, + importModuleDynamically, + }); + + this[kLink] = async (linker) => { + this.#statusOverride = 'linking'; + + const promises = this[kWrap].link(async (identifier) => { + const module = await linker(identifier, this); + if (module[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (module.context !== this.context) { + throw new ERR_VM_MODULE_DIFFERENT_CONTEXT(); + } + if (module.status === 'errored') { + throw new ERR_VM_MODULE_LINKING_ERRORED(); + } + if (module.status === 'unlinked') { + await module[kLink](linker); + } + return module[kWrap]; + }); + + try { + if (promises !== undefined) { + await SafePromise.all(promises); + } + } catch (e) { + this.#error = e; + throw e; + } finally { + this.#statusOverride = undefined; + } + }; + + this[kDependencySpecifiers] = undefined; + } + + get dependencySpecifiers() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (this[kDependencySpecifiers] === undefined) { + this[kDependencySpecifiers] = this[kWrap].getStaticDependencySpecifiers(); + } + return this[kDependencySpecifiers]; + } + + get status() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (this.#error !== kNoError) { + return 'errored'; + } + if (this.#statusOverride) { + return this.#statusOverride; + } + return super.status; + } + + get error() { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (this.#error !== kNoError) { + return this.#error; + } + return super.error; + } +} + +class SyntheticModule extends Module { + constructor(exportNames, evaluateCallback, options = {}) { + if (!Array.isArray(exportNames) || + exportNames.some((e) => typeof e !== 'string')) { + throw new ERR_INVALID_ARG_TYPE('exportNames', 'Array of strings', + exportNames); + } + if (typeof evaluateCallback !== 'function') { + throw new ERR_INVALID_ARG_TYPE('evaluateCallback', 'function', + evaluateCallback); + } + + if (typeof options !== 'object' || options === null) { + throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); + } + + const { context, identifier } = options; + + super({ + syntheticExportNames: exportNames, + syntheticEvaluationSteps: evaluateCallback, + context, + identifier, + }); + + this[kLink] = () => this[kWrap].link(() => { CHECK(false); }); + } + + setExport(name, value) { + if (this[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + validateString(name, 'name'); + if (this[kWrap].getStatus() < kInstantiated) { + throw new ERR_VM_MODULE_STATUS('must be linked'); + } + this[kWrap].setExport(name, value); + } +} + +function importModuleDynamicallyWrap(importModuleDynamically) { + const importModuleDynamicallyWrapper = async (...args) => { + const m = await importModuleDynamically(...args); + if (isModuleNamespaceObject(m)) { + return m; + } + if (!m || m[kWrap] === undefined) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (m.status === 'errored') { + throw m.error; + } + return m.namespace; + }; + return importModuleDynamicallyWrapper; +} + +module.exports = { + Module, + SourceTextModule, + SyntheticModule, + importModuleDynamicallyWrap, + getModuleFromWrap: (wrap) => wrapToModuleMap.get(wrap), +}; diff --git a/lib/internal/vm/source_text_module.js b/lib/internal/vm/source_text_module.js deleted file mode 100644 index 61af2e7a29..0000000000 --- a/lib/internal/vm/source_text_module.js +++ /dev/null @@ -1,333 +0,0 @@ -'use strict'; - -const { Object, SafePromise } = primordials; - -const { isModuleNamespaceObject } = require('internal/util/types'); -const { isContext } = internalBinding('contextify'); -const { - ERR_INVALID_ARG_TYPE, - ERR_VM_MODULE_ALREADY_LINKED, - ERR_VM_MODULE_DIFFERENT_CONTEXT, - ERR_VM_MODULE_LINKING_ERRORED, - ERR_VM_MODULE_NOT_MODULE, - ERR_VM_MODULE_STATUS, -} = require('internal/errors').codes; -const { - getConstructorOf, - customInspectSymbol, - emitExperimentalWarning -} = require('internal/util'); -const { - validateInt32, - validateUint32, - validateString, -} = require('internal/validators'); - -const binding = internalBinding('module_wrap'); -const { - ModuleWrap, - kUninstantiated, - kInstantiating, - kInstantiated, - kEvaluating, - kEvaluated, - kErrored, -} = binding; - -const STATUS_MAP = { - [kUninstantiated]: 'unlinked', - [kInstantiating]: 'linking', - [kInstantiated]: 'linked', - [kEvaluating]: 'evaluating', - [kEvaluated]: 'evaluated', - [kErrored]: 'errored', -}; - -let globalModuleId = 0; -const defaultModuleName = 'vm:module'; -const perContextModuleId = new WeakMap(); -const wrapToModuleMap = new WeakMap(); - -const kNoError = Symbol('kNoError'); - -class SourceTextModule { - #wrap; - #identifier; - #context; - #dependencySpecifiers; - #statusOverride; - #error = kNoError; - - constructor(source, options = {}) { - emitExperimentalWarning('vm.SourceTextModule'); - - validateString(source, 'source'); - if (typeof options !== 'object' || options === null) - throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); - - const { - context, - lineOffset = 0, - columnOffset = 0, - initializeImportMeta, - importModuleDynamically, - } = options; - - if (context !== undefined) { - if (typeof context !== 'object' || context === null) { - throw new ERR_INVALID_ARG_TYPE('options.context', 'Object', context); - } - if (!isContext(context)) { - throw new ERR_INVALID_ARG_TYPE('options.context', 'vm.Context', - context); - } - } - - validateInt32(lineOffset, 'options.lineOffset'); - validateInt32(columnOffset, 'options.columnOffset'); - - if (initializeImportMeta !== undefined && - typeof initializeImportMeta !== 'function') { - throw new ERR_INVALID_ARG_TYPE( - 'options.initializeImportMeta', 'function', initializeImportMeta); - } - - if (importModuleDynamically !== undefined && - typeof importModuleDynamically !== 'function') { - throw new ERR_INVALID_ARG_TYPE( - 'options.importModuleDynamically', 'function', importModuleDynamically); - } - - let { identifier } = options; - if (identifier !== undefined) { - validateString(identifier, 'options.identifier'); - } else if (context === undefined) { - identifier = `${defaultModuleName}(${globalModuleId++})`; - } else if (perContextModuleId.has(context)) { - const curId = perContextModuleId.get(context); - identifier = `${defaultModuleName}(${curId})`; - perContextModuleId.set(context, curId + 1); - } else { - identifier = `${defaultModuleName}(0)`; - perContextModuleId.set(context, 1); - } - - this.#wrap = new ModuleWrap( - source, identifier, context, - lineOffset, columnOffset, - ); - wrapToModuleMap.set(this.#wrap, this); - this.#identifier = identifier; - this.#context = context; - - binding.callbackMap.set(this.#wrap, { - initializeImportMeta, - importModuleDynamically: importModuleDynamically ? - importModuleDynamicallyWrap(importModuleDynamically) : - undefined, - }); - } - - get status() { - try { - this.#error; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - if (this.#error !== kNoError) { - return 'errored'; - } - if (this.#statusOverride) { - return this.#statusOverride; - } - return STATUS_MAP[this.#wrap.getStatus()]; - } - - get identifier() { - try { - return this.#identifier; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - } - - get context() { - try { - return this.#context; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - } - - get namespace() { - try { - this.#wrap; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - if (this.#wrap.getStatus() < kInstantiated) { - throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking'); - } - return this.#wrap.getNamespace(); - } - - get dependencySpecifiers() { - try { - this.#dependencySpecifiers; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - if (this.#dependencySpecifiers === undefined) { - this.#dependencySpecifiers = this.#wrap.getStaticDependencySpecifiers(); - } - return this.#dependencySpecifiers; - } - - get error() { - try { - this.#error; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - if (this.#error !== kNoError) { - return this.#error; - } - if (this.#wrap.getStatus() !== kErrored) { - throw new ERR_VM_MODULE_STATUS('must be errored'); - } - return this.#wrap.getError(); - } - - async link(linker) { - try { - this.#link; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - - if (typeof linker !== 'function') { - throw new ERR_INVALID_ARG_TYPE('linker', 'function', linker); - } - if (this.status !== 'unlinked') { - throw new ERR_VM_MODULE_ALREADY_LINKED(); - } - - await this.#link(linker); - - this.#wrap.instantiate(); - } - - #link = async function(linker) { - this.#statusOverride = 'linking'; - - const promises = this.#wrap.link(async (identifier) => { - const module = await linker(identifier, this); - try { - module.#wrap; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - if (module.context !== this.context) { - throw new ERR_VM_MODULE_DIFFERENT_CONTEXT(); - } - if (module.status === 'errored') { - throw new ERR_VM_MODULE_LINKING_ERRORED(); - } - if (module.status === 'unlinked') { - await module.#link(linker); - } - return module.#wrap; - }); - - try { - if (promises !== undefined) { - await SafePromise.all(promises); - } - } catch (e) { - this.#error = e; - throw e; - } finally { - this.#statusOverride = undefined; - } - }; - - - async evaluate(options = {}) { - try { - this.#wrap; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - - if (typeof options !== 'object' || options === null) { - throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); - } - - let timeout = options.timeout; - if (timeout === undefined) { - timeout = -1; - } else { - validateUint32(timeout, 'options.timeout', true); - } - const { breakOnSigint = false } = options; - if (typeof breakOnSigint !== 'boolean') { - throw new ERR_INVALID_ARG_TYPE('options.breakOnSigint', 'boolean', - breakOnSigint); - } - const status = this.#wrap.getStatus(); - if (status !== kInstantiated && - status !== kEvaluated && - status !== kErrored) { - throw new ERR_VM_MODULE_STATUS( - 'must be one of linked, evaluated, or errored' - ); - } - const result = this.#wrap.evaluate(timeout, breakOnSigint); - return { __proto__: null, result }; - } - - static importModuleDynamicallyWrap(importModuleDynamically) { - // Named declaration for function name - const importModuleDynamicallyWrapper = async (...args) => { - const m = await importModuleDynamically(...args); - if (isModuleNamespaceObject(m)) { - return m; - } - try { - m.#wrap; - } catch { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - if (m.status === 'errored') { - throw m.error; - } - return m.namespace; - }; - return importModuleDynamicallyWrapper; - } - - [customInspectSymbol](depth, options) { - let ctor = getConstructorOf(this); - ctor = ctor === null ? SourceTextModule : ctor; - - if (typeof depth === 'number' && depth < 0) - return options.stylize(`[${ctor.name}]`, 'special'); - - const o = Object.create({ constructor: ctor }); - o.status = this.status; - o.identifier = this.identifier; - o.context = this.context; - return require('internal/util/inspect').inspect(o, options); - } -} - -// Declared as static to allow access to #wrap -const importModuleDynamicallyWrap = - SourceTextModule.importModuleDynamicallyWrap; -delete SourceTextModule.importModuleDynamicallyWrap; - -module.exports = { - SourceTextModule, - wrapToModuleMap, - importModuleDynamicallyWrap, -}; -- cgit v1.2.3