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