summaryrefslogtreecommitdiff
path: root/lib/internal/vm/module.js
diff options
context:
space:
mode:
authorGus Caplan <me@gus.host>2019-10-04 20:08:00 -0700
committerRich Trott <rtrott@gmail.com>2019-10-14 14:25:38 -0700
commitf9caee986cae09cd4cb47e0a02a5b8672ab2c16d (patch)
tree5f5171ac3a976dfe81ee0667bc2f49afc1921ec1 /lib/internal/vm/module.js
parent7991b57cfdba96ddcd6553c8233cd6392e16a42a (diff)
downloadandroid-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.js409
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),
+};