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