diff options
author | Gus Caplan <me@gus.host> | 2018-01-13 23:35:51 -0800 |
---|---|---|
committer | Timothy Gu <timothygu99@gmail.com> | 2018-01-30 17:00:57 -0800 |
commit | 0993fbe5b213d0fe746c3162bcda85f6c66bb552 (patch) | |
tree | f0a1ffef2ab24f1da963a23a43f3dd58eed5bba0 /lib | |
parent | 2033a9f4362ee46b0fbddf9caf9cf6238391561e (diff) | |
download | android-node-v8-0993fbe5b213d0fe746c3162bcda85f6c66bb552.tar.gz android-node-v8-0993fbe5b213d0fe746c3162bcda85f6c66bb552.tar.bz2 android-node-v8-0993fbe5b213d0fe746c3162bcda85f6c66bb552.zip |
vm: add modules
Adds vm.Module, which wraps around ModuleWrap to provide an interface
for developers to work with modules in a more reflective manner.
Co-authored-by: Timothy Gu <timothygu99@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/17560
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/internal/errors.js | 9 | ||||
-rw-r--r-- | lib/internal/vm/Module.js | 205 | ||||
-rw-r--r-- | lib/vm.js | 5 |
3 files changed, 218 insertions, 1 deletions
diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 6a52d55bb8..3530d63710 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -676,6 +676,15 @@ E('ERR_V8BREAKITERATOR', 'Full ICU data not installed. ' + 'See https://github.com/nodejs/node/wiki/Intl'); E('ERR_VALID_PERFORMANCE_ENTRY_TYPE', 'At least one valid performance entry type is required'); +E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked'); +E('ERR_VM_MODULE_DIFFERENT_CONTEXT', + 'Linked modules must use the same context'); +E('ERR_VM_MODULE_LINKING_ERRORED', + 'Linking has already failed for the provided module'); +E('ERR_VM_MODULE_NOT_LINKED', + 'Module must be linked before it can be instantiated'); +E('ERR_VM_MODULE_NOT_MODULE', 'Provided module is not an instance of Module'); +E('ERR_VM_MODULE_STATUS', 'Module status %s'); E('ERR_ZLIB_BINDING_CLOSED', 'zlib binding closed'); E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed'); diff --git a/lib/internal/vm/Module.js b/lib/internal/vm/Module.js new file mode 100644 index 0000000000..a8625ef76b --- /dev/null +++ b/lib/internal/vm/Module.js @@ -0,0 +1,205 @@ +'use strict'; + +const { emitExperimentalWarning } = require('internal/util'); +const { URL } = require('internal/url'); +const { kParsingContext, isContext } = process.binding('contextify'); +const errors = require('internal/errors'); +const { + getConstructorOf, + customInspectSymbol, +} = require('internal/util'); + +const { + ModuleWrap, + kUninstantiated, + kInstantiating, + kInstantiated, + kEvaluating, + kEvaluated, + kErrored, +} = internalBinding('module_wrap'); + +const STATUS_MAP = { + [kUninstantiated]: 'uninstantiated', + [kInstantiating]: 'instantiating', + [kInstantiated]: 'instantiated', + [kEvaluating]: 'evaluating', + [kEvaluated]: 'evaluated', + [kErrored]: 'errored', +}; + +let globalModuleId = 0; +const perContextModuleId = new WeakMap(); +const wrapMap = new WeakMap(); +const dependencyCacheMap = new WeakMap(); +const linkingStatusMap = new WeakMap(); + +class Module { + constructor(src, options = {}) { + emitExperimentalWarning('vm.Module'); + + if (typeof src !== 'string') + throw new errors.TypeError( + 'ERR_INVALID_ARG_TYPE', 'src', 'string', src); + if (typeof options !== 'object' || options === null) + throw new errors.TypeError( + 'ERR_INVALID_ARG_TYPE', 'options', 'object', options); + + let context; + if (options.context !== undefined) { + if (isContext(options.context)) { + context = options.context; + } else { + throw new errors.TypeError( + 'ERR_INVALID_ARG_TYPE', 'options.context', 'vm.Context'); + } + } + + let url = options.url; + if (url !== undefined) { + if (typeof url !== 'string') { + throw new errors.TypeError( + 'ERR_INVALID_ARG_TYPE', 'options.url', 'string', url); + } + url = new URL(url).href; + } else if (context === undefined) { + url = `vm:module(${globalModuleId++})`; + } else if (perContextModuleId.has(context)) { + const curId = perContextModuleId.get(context); + url = `vm:module(${curId})`; + perContextModuleId.set(context, curId + 1); + } else { + url = 'vm:module(0)'; + perContextModuleId.set(context, 1); + } + + const wrap = new ModuleWrap(src, url, { + [kParsingContext]: context, + lineOffset: options.lineOffset, + columnOffset: options.columnOffset + }); + + wrapMap.set(this, wrap); + linkingStatusMap.set(this, 'unlinked'); + + Object.defineProperties(this, { + url: { value: url, enumerable: true }, + context: { value: context, enumerable: true }, + }); + } + + get linkingStatus() { + return linkingStatusMap.get(this); + } + + get status() { + return STATUS_MAP[wrapMap.get(this).getStatus()]; + } + + get namespace() { + const wrap = wrapMap.get(this); + if (wrap.getStatus() < kInstantiated) + throw new errors.Error('ERR_VM_MODULE_STATUS', + 'must not be uninstantiated or instantiating'); + return wrap.namespace(); + } + + get dependencySpecifiers() { + let deps = dependencyCacheMap.get(this); + if (deps !== undefined) + return deps; + + deps = wrapMap.get(this).getStaticDependencySpecifiers(); + Object.freeze(deps); + dependencyCacheMap.set(this, deps); + return deps; + } + + get error() { + const wrap = wrapMap.get(this); + if (wrap.getStatus() !== kErrored) + throw new errors.Error('ERR_VM_MODULE_STATUS', 'must be errored'); + return wrap.getError(); + } + + async link(linker) { + if (typeof linker !== 'function') + throw new errors.TypeError( + 'ERR_INVALID_ARG_TYPE', 'linker', 'function', linker); + if (linkingStatusMap.get(this) !== 'unlinked') + throw new errors.Error('ERR_VM_MODULE_ALREADY_LINKED'); + const wrap = wrapMap.get(this); + if (wrap.getStatus() !== kUninstantiated) + throw new errors.Error('ERR_VM_MODULE_STATUS', 'must be uninstantiated'); + linkingStatusMap.set(this, 'linking'); + const promises = []; + wrap.link((specifier) => { + const p = (async () => { + const m = await linker(this, specifier); + if (!m || !wrapMap.has(m)) + throw new errors.Error('ERR_VM_MODULE_NOT_MODULE'); + if (m.context !== this.context) + throw new errors.Error('ERR_VM_MODULE_DIFFERENT_CONTEXT'); + const childLinkingStatus = linkingStatusMap.get(m); + if (childLinkingStatus === 'errored') + throw new errors.Error('ERR_VM_MODULE_LINKING_ERRORED'); + if (childLinkingStatus === 'unlinked') + await m.link(linker); + return wrapMap.get(m); + })(); + promises.push(p); + return p; + }); + try { + await Promise.all(promises); + linkingStatusMap.set(this, 'linked'); + } catch (err) { + linkingStatusMap.set(this, 'errored'); + throw err; + } + } + + instantiate() { + const wrap = wrapMap.get(this); + const status = wrap.getStatus(); + if (status === kInstantiating || status === kEvaluating) + throw new errors.Error( + 'ERR_VM_MODULE_STATUS', 'must not be instantiating or evaluating'); + if (linkingStatusMap.get(this) !== 'linked') + throw new errors.Error('ERR_VM_MODULE_NOT_LINKED'); + wrap.instantiate(); + } + + async evaluate(options) { + const wrap = wrapMap.get(this); + const status = wrap.getStatus(); + if (status !== kInstantiated && + status !== kEvaluated && + status !== kErrored) { + throw new errors.Error( + 'ERR_VM_MODULE_STATUS', + 'must be one of instantiated, evaluated, and errored'); + } + const result = wrap.evaluate(options); + return { result, __proto__: null }; + } + + [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.linkingStatus = this.linkingStatus; + o.url = this.url; + o.context = this.context; + return require('util').inspect(o, options); + } +} + +module.exports = { + Module +}; @@ -192,5 +192,8 @@ module.exports = { runInContext, runInNewContext, runInThisContext, - isContext + isContext, }; + +if (process.binding('config').experimentalVMModules) + module.exports.Module = require('internal/vm/Module').Module; |