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