• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const assert = require('internal/assert');
4const {
5  ArrayIsArray,
6  ArrayPrototypeForEach,
7  ArrayPrototypeIndexOf,
8  ArrayPrototypeSome,
9  ObjectCreate,
10  ObjectDefineProperty,
11  ObjectGetPrototypeOf,
12  ObjectSetPrototypeOf,
13  ReflectApply,
14  SafePromiseAllReturnVoid,
15  Symbol,
16  SymbolToStringTag,
17  TypeError,
18} = primordials;
19
20const { isContext } = internalBinding('contextify');
21const {
22  isModuleNamespaceObject,
23} = require('internal/util/types');
24const {
25  customInspectSymbol,
26  emitExperimentalWarning,
27  getConstructorOf,
28  kEmptyObject,
29} = require('internal/util');
30const {
31  ERR_INVALID_ARG_TYPE,
32  ERR_INVALID_ARG_VALUE,
33  ERR_VM_MODULE_ALREADY_LINKED,
34  ERR_VM_MODULE_DIFFERENT_CONTEXT,
35  ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA,
36  ERR_VM_MODULE_LINK_FAILURE,
37  ERR_VM_MODULE_NOT_MODULE,
38  ERR_VM_MODULE_STATUS,
39} = require('internal/errors').codes;
40const {
41  validateBoolean,
42  validateBuffer,
43  validateFunction,
44  validateInt32,
45  validateObject,
46  validateUint32,
47  validateString,
48} = require('internal/validators');
49
50const binding = internalBinding('module_wrap');
51const {
52  ModuleWrap,
53  kUninstantiated,
54  kInstantiating,
55  kInstantiated,
56  kEvaluating,
57  kEvaluated,
58  kErrored,
59} = binding;
60
61const STATUS_MAP = {
62  [kUninstantiated]: 'unlinked',
63  [kInstantiating]: 'linking',
64  [kInstantiated]: 'linked',
65  [kEvaluating]: 'evaluating',
66  [kEvaluated]: 'evaluated',
67  [kErrored]: 'errored',
68};
69
70let globalModuleId = 0;
71const defaultModuleName = 'vm:module';
72
73const kWrap = Symbol('kWrap');
74const kContext = Symbol('kContext');
75const kPerContextModuleId = Symbol('kPerContextModuleId');
76const kLink = Symbol('kLink');
77
78class Module {
79  constructor(options) {
80    emitExperimentalWarning('VM Modules');
81
82    if (new.target === Module) {
83      // eslint-disable-next-line no-restricted-syntax
84      throw new TypeError('Module is not a constructor');
85    }
86
87    const {
88      context,
89      sourceText,
90      syntheticExportNames,
91      syntheticEvaluationSteps,
92    } = options;
93
94    if (context !== undefined) {
95      validateObject(context, 'context');
96      if (!isContext(context)) {
97        throw new ERR_INVALID_ARG_TYPE('options.context', 'vm.Context',
98                                       context);
99      }
100    }
101
102    let { identifier } = options;
103    if (identifier !== undefined) {
104      validateString(identifier, 'options.identifier');
105    } else if (context === undefined) {
106      identifier = `${defaultModuleName}(${globalModuleId++})`;
107    } else if (context[kPerContextModuleId] !== undefined) {
108      const curId = context[kPerContextModuleId];
109      identifier = `${defaultModuleName}(${curId})`;
110      context[kPerContextModuleId] += 1;
111    } else {
112      identifier = `${defaultModuleName}(0)`;
113      ObjectDefineProperty(context, kPerContextModuleId, {
114        __proto__: null,
115        value: 1,
116        writable: true,
117        enumerable: false,
118        configurable: true,
119      });
120    }
121
122    let registry = { __proto__: null };
123    if (sourceText !== undefined) {
124      this[kWrap] = new ModuleWrap(identifier, context, sourceText,
125                                   options.lineOffset, options.columnOffset,
126                                   options.cachedData);
127      registry = {
128        __proto__: null,
129        initializeImportMeta: options.initializeImportMeta,
130        importModuleDynamically: options.importModuleDynamically ?
131          importModuleDynamicallyWrap(options.importModuleDynamically) :
132          undefined,
133      };
134    } else {
135      assert(syntheticEvaluationSteps);
136      this[kWrap] = new ModuleWrap(identifier, context,
137                                   syntheticExportNames,
138                                   syntheticEvaluationSteps);
139    }
140
141    // This will take precedence over the referrer as the object being
142    // passed into the callbacks.
143    registry.callbackReferrer = this;
144    const { registerModule } = require('internal/modules/esm/utils');
145    registerModule(this[kWrap], registry);
146
147    this[kContext] = context;
148  }
149
150  get identifier() {
151    if (this[kWrap] === undefined) {
152      throw new ERR_VM_MODULE_NOT_MODULE();
153    }
154    return this[kWrap].url;
155  }
156
157  get context() {
158    if (this[kWrap] === undefined) {
159      throw new ERR_VM_MODULE_NOT_MODULE();
160    }
161    return this[kContext];
162  }
163
164  get namespace() {
165    if (this[kWrap] === undefined) {
166      throw new ERR_VM_MODULE_NOT_MODULE();
167    }
168    if (this[kWrap].getStatus() < kInstantiated) {
169      throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking');
170    }
171    return this[kWrap].getNamespace();
172  }
173
174  get status() {
175    if (this[kWrap] === undefined) {
176      throw new ERR_VM_MODULE_NOT_MODULE();
177    }
178    return STATUS_MAP[this[kWrap].getStatus()];
179  }
180
181  get error() {
182    if (this[kWrap] === undefined) {
183      throw new ERR_VM_MODULE_NOT_MODULE();
184    }
185    if (this[kWrap].getStatus() !== kErrored) {
186      throw new ERR_VM_MODULE_STATUS('must be errored');
187    }
188    return this[kWrap].getError();
189  }
190
191  async link(linker) {
192    if (this[kWrap] === undefined) {
193      throw new ERR_VM_MODULE_NOT_MODULE();
194    }
195    validateFunction(linker, 'linker');
196    if (this.status === 'linked') {
197      throw new ERR_VM_MODULE_ALREADY_LINKED();
198    }
199    if (this.status !== 'unlinked') {
200      throw new ERR_VM_MODULE_STATUS('must be unlinked');
201    }
202    await this[kLink](linker);
203    this[kWrap].instantiate();
204  }
205
206  async evaluate(options = kEmptyObject) {
207    if (this[kWrap] === undefined) {
208      throw new ERR_VM_MODULE_NOT_MODULE();
209    }
210
211    validateObject(options, 'options');
212
213    let timeout = options.timeout;
214    if (timeout === undefined) {
215      timeout = -1;
216    } else {
217      validateUint32(timeout, 'options.timeout', true);
218    }
219    const { breakOnSigint = false } = options;
220    validateBoolean(breakOnSigint, 'options.breakOnSigint');
221    const status = this[kWrap].getStatus();
222    if (status !== kInstantiated &&
223        status !== kEvaluated &&
224        status !== kErrored) {
225      throw new ERR_VM_MODULE_STATUS(
226        'must be one of linked, evaluated, or errored',
227      );
228    }
229    await this[kWrap].evaluate(timeout, breakOnSigint);
230  }
231
232  [customInspectSymbol](depth, options) {
233    if (this[kWrap] === undefined) {
234      throw new ERR_VM_MODULE_NOT_MODULE();
235    }
236    if (typeof depth === 'number' && depth < 0)
237      return this;
238
239    const constructor = getConstructorOf(this) || Module;
240    const o = ObjectCreate({ constructor });
241    o.status = this.status;
242    o.identifier = this.identifier;
243    o.context = this.context;
244
245    ObjectSetPrototypeOf(o, ObjectGetPrototypeOf(this));
246    ObjectDefineProperty(o, SymbolToStringTag, {
247      __proto__: null,
248      value: constructor.name,
249      configurable: true,
250    });
251
252    // Lazy to avoid circular dependency
253    const { inspect } = require('internal/util/inspect');
254    return inspect(o, { ...options, customInspect: false });
255  }
256}
257
258const kDependencySpecifiers = Symbol('kDependencySpecifiers');
259const kNoError = Symbol('kNoError');
260
261class SourceTextModule extends Module {
262  #error = kNoError;
263  #statusOverride;
264
265  constructor(sourceText, options = kEmptyObject) {
266    validateString(sourceText, 'sourceText');
267    validateObject(options, 'options');
268
269    const {
270      lineOffset = 0,
271      columnOffset = 0,
272      initializeImportMeta,
273      importModuleDynamically,
274      context,
275      identifier,
276      cachedData,
277    } = options;
278
279    validateInt32(lineOffset, 'options.lineOffset');
280    validateInt32(columnOffset, 'options.columnOffset');
281
282    if (initializeImportMeta !== undefined) {
283      validateFunction(initializeImportMeta, 'options.initializeImportMeta');
284    }
285
286    if (importModuleDynamically !== undefined) {
287      validateFunction(importModuleDynamically, 'options.importModuleDynamically');
288    }
289
290    if (cachedData !== undefined) {
291      validateBuffer(cachedData, 'options.cachedData');
292    }
293
294    super({
295      sourceText,
296      context,
297      identifier,
298      lineOffset,
299      columnOffset,
300      cachedData,
301      initializeImportMeta,
302      importModuleDynamically,
303    });
304
305    this[kLink] = async (linker) => {
306      this.#statusOverride = 'linking';
307
308      const promises = this[kWrap].link(async (identifier, attributes) => {
309        const module = await linker(identifier, this, { attributes, assert: attributes });
310        if (module[kWrap] === undefined) {
311          throw new ERR_VM_MODULE_NOT_MODULE();
312        }
313        if (module.context !== this.context) {
314          throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();
315        }
316        if (module.status === 'errored') {
317          throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${identifier}' resolved to an errored module`, module.error);
318        }
319        if (module.status === 'unlinked') {
320          await module[kLink](linker);
321        }
322        return module[kWrap];
323      });
324
325      try {
326        if (promises !== undefined) {
327          await SafePromiseAllReturnVoid(promises);
328        }
329      } catch (e) {
330        this.#error = e;
331        throw e;
332      } finally {
333        this.#statusOverride = undefined;
334      }
335    };
336
337    this[kDependencySpecifiers] = undefined;
338  }
339
340  get dependencySpecifiers() {
341    if (this[kWrap] === undefined) {
342      throw new ERR_VM_MODULE_NOT_MODULE();
343    }
344    if (this[kDependencySpecifiers] === undefined) {
345      this[kDependencySpecifiers] = this[kWrap].getStaticDependencySpecifiers();
346    }
347    return this[kDependencySpecifiers];
348  }
349
350  get status() {
351    if (this[kWrap] === undefined) {
352      throw new ERR_VM_MODULE_NOT_MODULE();
353    }
354    if (this.#error !== kNoError) {
355      return 'errored';
356    }
357    if (this.#statusOverride) {
358      return this.#statusOverride;
359    }
360    return super.status;
361  }
362
363  get error() {
364    if (this[kWrap] === undefined) {
365      throw new ERR_VM_MODULE_NOT_MODULE();
366    }
367    if (this.#error !== kNoError) {
368      return this.#error;
369    }
370    return super.error;
371  }
372
373  createCachedData() {
374    const { status } = this;
375    if (status === 'evaluating' ||
376        status === 'evaluated' ||
377        status === 'errored') {
378      throw new ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA();
379    }
380    return this[kWrap].createCachedData();
381  }
382}
383
384class SyntheticModule extends Module {
385  constructor(exportNames, evaluateCallback, options = kEmptyObject) {
386    if (!ArrayIsArray(exportNames) ||
387      ArrayPrototypeSome(exportNames, (e) => typeof e !== 'string')) {
388      throw new ERR_INVALID_ARG_TYPE('exportNames',
389                                     'Array of unique strings',
390                                     exportNames);
391    } else {
392      ArrayPrototypeForEach(exportNames, (name, i) => {
393        if (ArrayPrototypeIndexOf(exportNames, name, i + 1) !== -1) {
394          throw new ERR_INVALID_ARG_VALUE(`exportNames.${name}`,
395                                          name,
396                                          'is duplicated');
397        }
398      });
399    }
400    validateFunction(evaluateCallback, 'evaluateCallback');
401
402    validateObject(options, 'options');
403
404    const { context, identifier } = options;
405
406    super({
407      syntheticExportNames: exportNames,
408      syntheticEvaluationSteps: evaluateCallback,
409      context,
410      identifier,
411    });
412
413    this[kLink] = () => this[kWrap].link(() => {
414      assert.fail('link callback should not be called');
415    });
416  }
417
418  setExport(name, value) {
419    if (this[kWrap] === undefined) {
420      throw new ERR_VM_MODULE_NOT_MODULE();
421    }
422    validateString(name, 'name');
423    if (this[kWrap].getStatus() < kInstantiated) {
424      throw new ERR_VM_MODULE_STATUS('must be linked');
425    }
426    this[kWrap].setExport(name, value);
427  }
428}
429
430function importModuleDynamicallyWrap(importModuleDynamically) {
431  const importModuleDynamicallyWrapper = async (...args) => {
432    const m = await ReflectApply(importModuleDynamically, this, args);
433    if (isModuleNamespaceObject(m)) {
434      return m;
435    }
436    if (!m || m[kWrap] === undefined) {
437      throw new ERR_VM_MODULE_NOT_MODULE();
438    }
439    if (m.status === 'errored') {
440      throw m.error;
441    }
442    return m.namespace;
443  };
444  return importModuleDynamicallyWrapper;
445}
446
447module.exports = {
448  Module,
449  SourceTextModule,
450  SyntheticModule,
451  importModuleDynamicallyWrap,
452};
453