diff options
author | Gus Caplan <me@gus.host> | 2019-10-04 20:08:00 -0700 |
---|---|---|
committer | Rich Trott <rtrott@gmail.com> | 2019-10-14 14:25:38 -0700 |
commit | f9caee986cae09cd4cb47e0a02a5b8672ab2c16d (patch) | |
tree | 5f5171ac3a976dfe81ee0667bc2f49afc1921ec1 /lib/internal/vm/module.js | |
parent | 7991b57cfdba96ddcd6553c8233cd6392e16a42a (diff) | |
download | android-node-v8-f9caee986cae09cd4cb47e0a02a5b8672ab2c16d.tar.gz android-node-v8-f9caee986cae09cd4cb47e0a02a5b8672ab2c16d.tar.bz2 android-node-v8-f9caee986cae09cd4cb47e0a02a5b8672ab2c16d.zip |
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 <guybedford@gmail.com>
Diffstat (limited to 'lib/internal/vm/module.js')
-rw-r--r-- | lib/internal/vm/module.js | 409 |
1 files changed, 409 insertions, 0 deletions
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), +}; |