Skip to content

Commit 0993fbe

Browse files
devsnekTimothyGu
andcommittedJan 31, 2018
vm: add modules
Adds vm.Module, which wraps around ModuleWrap to provide an interface for developers to work with modules in a more reflective manner. Co-authored-by: Timothy Gu <[email protected]> PR-URL: #17560 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]>
1 parent 2033a9f commit 0993fbe

16 files changed

+1281
-35
lines changed
 

‎doc/api/errors.md

+37
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,43 @@ entry types were found.
16391639

16401640
Superseded by `ERR_OUT_OF_RANGE`
16411641

1642+
<a id="ERR_VM_MODULE_ALREADY_LINKED"></a>
1643+
### ERR_VM_MODULE_ALREADY_LINKED
1644+
1645+
The module attempted to be linked is not eligible for linking, because of one of
1646+
the following reasons:
1647+
1648+
- It has already been linked (`linkingStatus` is `'linked'`)
1649+
- It is being linked (`linkingStatus` is `'linking'`)
1650+
- Linking has failed for this module (`linkingStatus` is `'errored'`)
1651+
1652+
<a id="ERR_VM_MODULE_DIFFERENT_CONTEXT"></a>
1653+
### ERR_VM_MODULE_DIFFERENT_CONTEXT
1654+
1655+
The module being returned from the linker function is from a different context
1656+
than the parent module. Linked modules must share the same context.
1657+
1658+
<a id="ERR_VM_MODULE_LINKING_ERRORED"></a>
1659+
### ERR_VM_MODULE_LINKING_ERRORED
1660+
1661+
The linker function returned a module for which linking has failed.
1662+
1663+
<a id="ERR_VM_MODULE_NOT_LINKED"></a>
1664+
### ERR_VM_MODULE_NOT_LINKED
1665+
1666+
The module must be successfully linked before instantiation.
1667+
1668+
<a id="ERR_VM_MODULE_NOT_MODULE"></a>
1669+
### ERR_VM_MODULE_NOT_MODULE
1670+
1671+
The fulfilled value of a linking promise is not a `vm.Module` object.
1672+
1673+
<a id="ERR_VM_MODULE_STATUS"></a>
1674+
### ERR_VM_MODULE_STATUS
1675+
1676+
The current module's status does not allow for this operation. The specific
1677+
meaning of the error depends on the specific function.
1678+
16421679
<a id="ERR_ZLIB_BINDING_CLOSED"></a>
16431680
### ERR_ZLIB_BINDING_CLOSED
16441681

‎doc/api/vm.md

+322
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,322 @@ console.log(x); // 1; y is not defined.
4343
*Note*: The vm module is not a security mechanism.
4444
**Do not use it to run untrusted code**.
4545

46+
## Class: vm.Module
47+
<!-- YAML
48+
added: REPLACEME
49+
-->
50+
51+
> Stability: 1 - Experimental
52+
53+
*This feature is only available with the `--experimental-vm-modules` command
54+
flag enabled.*
55+
56+
The `vm.Module` class provides a low-level interface for using ECMAScript
57+
modules in VM contexts. It is the counterpart of the `vm.Script` class that
58+
closely mirrors [Source Text Module Record][]s as defined in the ECMAScript
59+
specification.
60+
61+
Unlike `vm.Script` however, every `vm.Module` object is bound to a context from
62+
its creation. Operations on `vm.Module` objects are intrinsically asynchronous,
63+
in contrast with the synchronous nature of `vm.Script` objects. With the help
64+
of async functions, however, manipulating `vm.Module` objects is fairly
65+
straightforward.
66+
67+
Using a `vm.Module` object requires four distinct steps: creation/parsing,
68+
linking, instantiation, and evaluation. These four steps are illustrated in the
69+
following example.
70+
71+
*Note*: This implementation lies at a lower level than the [ECMAScript Module
72+
loader][]. There is also currently no way to interact with the Loader, though
73+
support is planned.
74+
75+
```js
76+
const vm = require('vm');
77+
78+
const contextifiedSandbox = vm.createContext({ secret: 42 });
79+
80+
(async () => {
81+
// Step 1
82+
//
83+
// Create a Module by constructing a new `vm.Module` object. This parses the
84+
// provided source text, throwing a `SyntaxError` if anything goes wrong. By
85+
// default, a Module is created in the top context. But here, we specify
86+
// `contextifiedSandbox` as the context this Module belongs to.
87+
//
88+
// Here, we attempt to obtain the default export from the module "foo", and
89+
// put it into local binding "secret".
90+
Has a conversation. Original line has a conversation.
91+
const bar = new vm.Module(`
92+
import s from 'foo';
93+
s;
94+
`, { context: contextifiedSandbox });
95+
96+
97+
// Step 2
98+
//
99+
// "Link" the imported dependencies of this Module to it.
100+
//
101+
// The provided linking callback (the "linker") accepts two arguments: the
102+
// parent module (`bar` in this case) and the string that is the specifier of
103+
// the imported module. The callback is expected to return a Module that
104+
// corresponds to the provided specifier, with certain requirements documented
105+
// in `module.link()`.
106+
//
107+
// If linking has not started for the returned Module, the same linker
108+
// callback will be called on the returned Module.
109+
//
110+
// Even top-level Modules without dependencies must be explicitly linked. The
111+
// callback provided would never be called, however.
112+
//
113+
// The link() method returns a Promise that will be resolved when all the
114+
// Promises returned by the linker resolve.
115+
//
116+
// Note: This is a contrived example in that the linker function creates a new
117+
// "foo" module every time it is called. In a full-fledged module system, a
118+
// cache would probably be used to avoid duplicated modules.
119+
120+
async function linker(referencingModule, specifier) {
121+
if (specifier === 'foo') {
122+
return new vm.Module(`
123+
// The "secret" variable refers to the global variable we added to
124+
// "contextifiedSandbox" when creating the context.
125+
export default secret;
126+
`, { context: referencingModule.context });
127+
128+
// Using `contextifiedSandbox` instead of `referencingModule.context`
129+
// here would work as well.
130+
}
131+
throw new Error(`Unable to resolve dependency: ${specifier}`);
132+
}
133+
await bar.link(linker);
134+
135+
136+
// Step 3
137+
//
138+
// Instantiate the top-level Module.
139+
//
140+
// Only the top-level Module needs to be explicitly instantiated; its
141+
// dependencies will be recursively instantiated by instantiate().
142+
143+
bar.instantiate();
144+
145+
146+
// Step 4
147+
//
148+
// Evaluate the Module. The evaluate() method returns a Promise with a single
149+
// property "result" that contains the result of the very last statement
150+
// executed in the Module. In the case of `bar`, it is `s;`, which refers to
151+
// the default export of the `foo` module, the `secret` we set in the
152+
// beginning to 42.
153+
154+
const { result } = await bar.evaluate();
155+
156+
console.log(result);
157+
// Prints 42.
158+
})();
159+
```
160+
161+
### Constructor: new vm.Module(code[, options])
162+
163+
* `code` {string} JavaScript Module code to parse
164+
* `options`
165+
* `url` {string} URL used in module resolution and stack traces. **Default**:
166+
`'vm:module(i)'` where `i` is a context-specific ascending index.
167+
* `context` {Object} The [contextified][] object as returned by the
168+
`vm.createContext()` method, to compile and evaluate this Module in.
169+
* `lineOffset` {integer} Specifies the line number offset that is displayed
170+
in stack traces produced by this Module.
171+
* `columnOffset` {integer} Spcifies the column number offset that is displayed
172+
in stack traces produced by this Module.
173+
174+
Creates a new ES `Module` object.
175+
176+
### module.dependencySpecifiers
177+
178+
* {string[]}
179+
180+
The specifiers of all dependencies of this module. The returned array is frozen
181+
to disallow any changes to it.
182+
183+
Corresponds to the [[RequestedModules]] field of [Source Text Module Record][]s
184+
in the ECMAScript specification.
185+
186+
### module.error
187+
188+
* {any}
189+
190+
If the `module.status` is `'errored'`, this property contains the exception thrown
191+
by the module during evaluation. If the status is anything else, accessing this
192+
property will result in a thrown exception.
193+
194+
*Note*: `undefined` cannot be used for cases where there is not a thrown
195+
exception due to possible ambiguity with `throw undefined;`.
196+
197+
Corresponds to the [[EvaluationError]] field of [Source Text Module Record][]s
198+
in the ECMAScript specification.
199+
200+
### module.linkingStatus
201+
202+
* {string}
203+
204+
The current linking status of `module`. It will be one of the following values:
205+
206+
- `'unlinked'`: `module.link()` has not yet been called.
207+
- `'linking'`: `module.link()` has been called, but not all Promises returned by
208+
the linker function have been resolved yet.
209+
- `'linked'`: `module.link()` has been called, and all its dependencies have
210+
been successfully linked.
211+
- `'errored'`: `module.link()` has been called, but at least one of its
212+
dependencies failed to link, either because the callback returned a Promise
213+
that is rejected, or because the Module the callback returned is invalid.
214+
215+
### module.namespace
216+
217+
* {Object}
218+
219+
The namespace object of the module. This is only available after instantiation
220+
(`module.instantiate()`) has completed.
221+
222+
Corresponds to the [GetModuleNamespace][] abstract operation in the ECMAScript
223+
specification.
224+
225+
### module.status
226+
227+
* {string}
228+
229+
The current status of the module. Will be one of:
230+
231+
- `'uninstantiated'`: The module is not instantiated. It may because of any of
232+
the following reasons:
233+
234+
- The module was just created.
235+
- `module.instantiate()` has been called on this module, but it failed for
236+
some reason.
237+
238+
This status does not convey any information regarding if `module.link()` has
239+
been called. See `module.linkingStatus` for that.
240+
241+
- `'instantiating'`: The module is currently being instantiated through a
242+
`module.instantiate()` call on itself or a parent module.
243+
244+
- `'instantiated'`: The module has been instantiated successfully, but
245+
`module.evaluate()` has not yet been called.
246+
247+
- `'evaluating'`: The module is being evaluated through a `module.evaluate()` on
248+
itself or a parent module.
249+
250+
- `'evaluated'`: The module has been successfully evaluated.
251+
252+
- `'errored'`: The module has been evaluated, but an exception was thrown.
253+
254+
Other than `'errored'`, this status string corresponds to the specification's
255+
[Source Text Module Record][]'s [[Status]] field. `'errored'` corresponds to
256+
`'evaluated'` in the specification, but with [[EvaluationError]] set to a value
257+
that is not `undefined`.
258+
259+
### module.url
260+
261+
* {string}
262+
263+
The URL of the current module, as set in the constructor.
264+
265+
### module.evaluate([options])
266+
267+
* `options` {Object}
268+
* `timeout` {number} Specifies the number of milliseconds to evaluate
269+
before terminating execution. If execution is interrupted, an [`Error`][]
270+
will be thrown.
271+
* `breakOnSigint` {boolean} If `true`, the execution will be terminated when
272+
`SIGINT` (Ctrl+C) is received. Existing handlers for the event that have
273+
been attached via `process.on("SIGINT")` will be disabled during script
274+
execution, but will continue to work after that. If execution is
275+
interrupted, an [`Error`][] will be thrown.
276+
* Returns: {Promise}
277+
278+
Evaluate the module.
279+
280+
This must be called after the module has been instantiated; otherwise it will
281+
throw an error. It could be called also when the module has already been
282+
evaluated, in which case it will do one of the following two things:
283+
284+
- return `undefined` if the initial evaluation ended in success (`module.status`
285+
is `'evaluated'`)
286+
- rethrow the same exception the initial evaluation threw if the initial
287+
evaluation ended in an error (`module.status` is `'errored'`)
288+
289+
This method cannot be called while the module is being evaluated
290+
(`module.status` is `'evaluating'`) to prevent infinite recursion.
291+
292+
Corresponds to the [Evaluate() concrete method][] field of [Source Text Module
293+
Record][]s in the ECMAScript specification.
294+
295+
### module.instantiate()
296+
297+
Instantiate the module. This must be called after linking has completed
298+
(`linkingStatus` is `'linked'`); otherwise it will throw an error. It may also
299+
throw an exception if one of the dependencies does not provide an export the
300+
parent module requires.
301+
302+
However, if this function succeeded, further calls to this function after the
303+
initial instantiation will be no-ops, to be consistent with the ECMAScript
304+
specification.
305+
306+
Unlike other methods operating on `Module`, this function completes
307+
synchronously and returns nothing.
308+
309+
Corresponds to the [Instantiate() concrete method][] field of [Source Text
310+
Module Record][]s in the ECMAScript specification.
311+
312+
### module.link(linker)
313+
314+
* `linker` {Function}
315+
* Returns: {Promise}
316+
317+
Link module dependencies. This method must be called before instantiation, and
318+
can only be called once per module.
319+
320+
Two parameters will be passed to the `linker` function:
321+
322+
- `referencingModule` The `Module` object `link()` is called on.
323+
- `specifier` The specifier of the requested module:
324+
325+
<!-- eslint-skip -->
326+
```js
327+
import foo from 'foo';
328+
// ^^^^^ the module specifier
329+
```
330+
331+
The function is expected to return a `Module` object or a `Promise` that
332+
eventually resolves to a `Module` object. The returned `Module` must satisfy the
333+
following two invariants:
334+
335+
- It must belong to the same context as the parent `Module`.
336+
- Its `linkingStatus` must not be `'errored'`.
337+
338+
If the returned `Module`'s `linkingStatus` is `'unlinked'`, this method will be
339+
recursively called on the returned `Module` with the same provided `linker`
340+
function.
341+
342+
`link()` returns a `Promise` that will either get resolved when all linking
343+
instances resolve to a valid `Module`, or rejected if the linker function either
344+
throws an exception or returns an invalid `Module`.
345+
346+
The linker function roughly corresponds to the implementation-defined
347+
[HostResolveImportedModule][] abstract operation in the ECMAScript
348+
specification, with a few key differences:
349+
350+
- The linker function is allowed to be asynchronous while
351+
[HostResolveImportedModule][] is synchronous.
352+
- The linker function is executed during linking, a Node.js-specific stage
353+
before instantiation, while [HostResolveImportedModule][] is called during
354+
instantiation.
355+
356+
The actual [HostResolveImportedModule][] implementation used during module
357+
instantiation is one that returns the modules linked during linking. Since at
358+
that point all modules would have been fully linked already, the
359+
[HostResolveImportedModule][] implementation is fully synchronous per
360+
specification.
361+
46362
## Class: vm.Script
47363
<!-- YAML
48364
added: v0.3.1
@@ -518,8 +834,14 @@ associating it with the `sandbox` object is what this document refers to as
518834
[`vm.createContext()`]: #vm_vm_createcontext_sandbox_options
519835
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
520836
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
837+
[GetModuleNamespace]: https://tc39.github.io/ecma262/#sec-getmodulenamespace
838+
[ECMAScript Module Loader]: esm.html#esm_ecmascript_modules
839+
[Evaluate() concrete method]: https://tc39.github.io/ecma262/#sec-moduleevaluation
840+
[HostResolveImportedModule]: https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule
841+
[Instantiate() concrete method]: https://tc39.github.io/ecma262/#sec-moduledeclarationinstantiation
521842
[V8 Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide#contexts
522843
[contextified]: #vm_what_does_it_mean_to_contextify_an_object
523844
[global object]: https://es5.github.io/#x15.1
524845
[indirect `eval()` call]: https://es5.github.io/#x10.4.2
525846
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin
847+
[Source Text Module Record]: https://tc39.github.io/ecma262/#sec-source-text-module-records

‎lib/internal/errors.js

+9
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,15 @@ E('ERR_V8BREAKITERATOR', 'Full ICU data not installed. ' +
676676
'See https://github.com/nodejs/node/wiki/Intl');
677677
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
678678
'At least one valid performance entry type is required');
679+
E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked');
680+
E('ERR_VM_MODULE_DIFFERENT_CONTEXT',
681+
'Linked modules must use the same context');
682+
E('ERR_VM_MODULE_LINKING_ERRORED',
683+
'Linking has already failed for the provided module');
684+
E('ERR_VM_MODULE_NOT_LINKED',
685+
'Module must be linked before it can be instantiated');
686+
E('ERR_VM_MODULE_NOT_MODULE', 'Provided module is not an instance of Module');
687+
E('ERR_VM_MODULE_STATUS', 'Module status %s');
679688
E('ERR_ZLIB_BINDING_CLOSED', 'zlib binding closed');
680689
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed');
681690

‎lib/internal/vm/Module.js

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
'use strict';
2+
3+
const { emitExperimentalWarning } = require('internal/util');
4+
const { URL } = require('internal/url');
5+
const { kParsingContext, isContext } = process.binding('contextify');
6+
const errors = require('internal/errors');
7+
const {
8+
getConstructorOf,
9+
customInspectSymbol,
10+
} = require('internal/util');
11+
12+
const {
13+
ModuleWrap,
14+
kUninstantiated,
15+
kInstantiating,
16+
kInstantiated,
17+
kEvaluating,
18+
kEvaluated,
19+
kErrored,
20+
} = internalBinding('module_wrap');
21+
22+
const STATUS_MAP = {
23+
[kUninstantiated]: 'uninstantiated',
24+
[kInstantiating]: 'instantiating',
25+
[kInstantiated]: 'instantiated',
26+
[kEvaluating]: 'evaluating',
27+
[kEvaluated]: 'evaluated',
28+
[kErrored]: 'errored',
29+
};
30+
31+
let globalModuleId = 0;
32+
const perContextModuleId = new WeakMap();
33+
const wrapMap = new WeakMap();
34+
const dependencyCacheMap = new WeakMap();
35+
const linkingStatusMap = new WeakMap();
36+
37+
class Module {
38+
constructor(src, options = {}) {
39+
emitExperimentalWarning('vm.Module');
40+
41+
if (typeof src !== 'string')
42+
throw new errors.TypeError(
43+
'ERR_INVALID_ARG_TYPE', 'src', 'string', src);
44+
if (typeof options !== 'object' || options === null)
45+
throw new errors.TypeError(
46+
'ERR_INVALID_ARG_TYPE', 'options', 'object', options);
47+
48+
let context;
49+
if (options.context !== undefined) {
50+
if (isContext(options.context)) {
51+
context = options.context;
52+
} else {
53+
throw new errors.TypeError(
54+
'ERR_INVALID_ARG_TYPE', 'options.context', 'vm.Context');
55+
}
56+
}
57+
58+
let url = options.url;
59+
if (url !== undefined) {
60+
if (typeof url !== 'string') {
61+
throw new errors.TypeError(
62+
'ERR_INVALID_ARG_TYPE', 'options.url', 'string', url);
63+
}
64+
url = new URL(url).href;
65+
} else if (context === undefined) {
66+
url = `vm:module(${globalModuleId++})`;
67+
} else if (perContextModuleId.has(context)) {
68+
const curId = perContextModuleId.get(context);
69+
url = `vm:module(${curId})`;
70+
perContextModuleId.set(context, curId + 1);
71+
} else {
72+
url = 'vm:module(0)';
73+
perContextModuleId.set(context, 1);
74+
}
75+
76+
const wrap = new ModuleWrap(src, url, {
77+
[kParsingContext]: context,
78+
lineOffset: options.lineOffset,
79+
columnOffset: options.columnOffset
80+
});
81+
82+
wrapMap.set(this, wrap);
83+
linkingStatusMap.set(this, 'unlinked');
84+
85+
Object.defineProperties(this, {
86+
url: { value: url, enumerable: true },
87+
context: { value: context, enumerable: true },
88+
});
89+
}
90+
91+
get linkingStatus() {
92+
return linkingStatusMap.get(this);
93+
}
94+
95+
get status() {
96+
return STATUS_MAP[wrapMap.get(this).getStatus()];
97+
}
98+
99+
get namespace() {
100+
const wrap = wrapMap.get(this);
101+
if (wrap.getStatus() < kInstantiated)
102+
throw new errors.Error('ERR_VM_MODULE_STATUS',
103+
'must not be uninstantiated or instantiating');
104+
return wrap.namespace();
105+
}
106+
107+
get dependencySpecifiers() {
108+
let deps = dependencyCacheMap.get(this);
109+
if (deps !== undefined)
110+
return deps;
111+
112+
deps = wrapMap.get(this).getStaticDependencySpecifiers();
113+
Object.freeze(deps);
114+
dependencyCacheMap.set(this, deps);
115+
return deps;
116+
}
117+
118+
get error() {
119+
const wrap = wrapMap.get(this);
120+
if (wrap.getStatus() !== kErrored)
121+
throw new errors.Error('ERR_VM_MODULE_STATUS', 'must be errored');
122+
return wrap.getError();
123+
}
124+
125+
async link(linker) {
126+
if (typeof linker !== 'function')
127+
throw new errors.TypeError(
128+
'ERR_INVALID_ARG_TYPE', 'linker', 'function', linker);
129+
if (linkingStatusMap.get(this) !== 'unlinked')
130+
throw new errors.Error('ERR_VM_MODULE_ALREADY_LINKED');
131+
const wrap = wrapMap.get(this);
132+
if (wrap.getStatus() !== kUninstantiated)
133+
throw new errors.Error('ERR_VM_MODULE_STATUS', 'must be uninstantiated');
134+
linkingStatusMap.set(this, 'linking');
135+
const promises = [];
136+
wrap.link((specifier) => {
137+
const p = (async () => {
138+
const m = await linker(this, specifier);
139+
if (!m || !wrapMap.has(m))
140+
throw new errors.Error('ERR_VM_MODULE_NOT_MODULE');
141+
if (m.context !== this.context)
142+
throw new errors.Error('ERR_VM_MODULE_DIFFERENT_CONTEXT');
143+
const childLinkingStatus = linkingStatusMap.get(m);
144+
if (childLinkingStatus === 'errored')
145+
throw new errors.Error('ERR_VM_MODULE_LINKING_ERRORED');
146+
if (childLinkingStatus === 'unlinked')
147+
await m.link(linker);
148+
return wrapMap.get(m);
149+
})();
150+
promises.push(p);
151+
return p;
152+
});
153+
try {
154+
await Promise.all(promises);
155+
linkingStatusMap.set(this, 'linked');
156+
} catch (err) {
157+
linkingStatusMap.set(this, 'errored');
158+
throw err;
159+
}
160+
}
161+
162+
instantiate() {
163+
const wrap = wrapMap.get(this);
164+
const status = wrap.getStatus();
165+
if (status === kInstantiating || status === kEvaluating)
166+
throw new errors.Error(
167+
'ERR_VM_MODULE_STATUS', 'must not be instantiating or evaluating');
168+
if (linkingStatusMap.get(this) !== 'linked')
169+
throw new errors.Error('ERR_VM_MODULE_NOT_LINKED');
170+
wrap.instantiate();
171+
}
172+
173+
async evaluate(options) {
174+
const wrap = wrapMap.get(this);
175+
const status = wrap.getStatus();
176+
if (status !== kInstantiated &&
177+
status !== kEvaluated &&
178+
status !== kErrored) {
179+
throw new errors.Error(
180+
'ERR_VM_MODULE_STATUS',
181+
'must be one of instantiated, evaluated, and errored');
182+
}
183+
const result = wrap.evaluate(options);
184+
return { result, __proto__: null };
185+
}
186+
187+
[customInspectSymbol](depth, options) {
188+
let ctor = getConstructorOf(this);
189+
ctor = ctor === null ? Module : ctor;
190+
191+
if (typeof depth === 'number' && depth < 0)
192+
return options.stylize(`[${ctor.name}]`, 'special');
193+
194+
const o = Object.create({ constructor: ctor });
195+
o.status = this.status;
196+
o.linkingStatus = this.linkingStatus;
197+
o.url = this.url;
198+
o.context = this.context;
199+
return require('util').inspect(o, options);
200+
}
201+
}
202+
203+
module.exports = {
204+
Module
205+
};

‎lib/vm.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -192,5 +192,8 @@ module.exports = {
192192
runInContext,
193193
runInNewContext,
194194
runInThisContext,
195-
isContext
195+
isContext,
196196
};
197+
198+
if (process.binding('config').experimentalVMModules)
199+
module.exports.Module = require('internal/vm/Module').Module;

‎node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
'lib/internal/v8.js',
139139
'lib/internal/v8_prof_polyfill.js',
140140
'lib/internal/v8_prof_processor.js',
141+
'lib/internal/vm/Module.js',
141142
'lib/internal/streams/lazy_transform.js',
142143
'lib/internal/streams/async_iterator.js',
143144
'lib/internal/streams/BufferList.js',

‎src/module_wrap.cc

+150-32
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
#include "node_url.h"
88
#include "util-inl.h"
99
#include "node_internals.h"
10+
#include "node_contextify.h"
11+
#include "node_watchdog.h"
1012

1113
namespace node {
1214
namespace loader {
1315

1416
using node::url::URL;
1517
using node::url::URL_FLAGS_FAILED;
18+
using v8::Array;
1619
using v8::Context;
1720
using v8::Function;
1821
using v8::FunctionCallbackInfo;
@@ -58,6 +61,7 @@ ModuleWrap::~ModuleWrap() {
5861
}
5962

6063
module_.Reset();
64+
context_.Reset();
6165
}
6266

6367
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
@@ -70,12 +74,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
7074
return;
7175
}
7276

73-
if (args.Length() != 2) {
74-
env->ThrowError("constructor must have exactly 2 arguments "
75-
"(string, string)");
76-
return;
77-
}
78-
7977
if (!args[0]->IsString()) {
8078
env->ThrowError("first argument is not a string");
8179
return;
@@ -90,20 +88,39 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
9088

9189
Local<String> url = args[1].As<String>();
9290

91+
Local<Object> that = args.This();
92+
93+
Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env);
94+
TryCatch try_catch(isolate);
95+
96+
Local<Value> options = args[2];
97+
MaybeLocal<Integer> line_offset = contextify::GetLineOffsetArg(env, options);
98+
MaybeLocal<Integer> column_offset =
99+
contextify::GetColumnOffsetArg(env, options);
100+
MaybeLocal<Context> maybe_context = contextify::GetContextArg(env, options);
101+
102+
103+
if (try_catch.HasCaught()) {
104+
no_abort_scope.Close();
105+
try_catch.ReThrow();
106+
return;
107+
}
108+
109+
Local<Context> context = maybe_context.FromMaybe(that->CreationContext());
93110
Local<Module> module;
94111

95112
// compile
96113
{
97114
ScriptOrigin origin(url,
98-
Integer::New(isolate, 0), // line offset
99-
Integer::New(isolate, 0), // column offset
115+
line_offset.ToLocalChecked(), // line offset
116+
column_offset.ToLocalChecked(), // column offset
100117
False(isolate), // is cross origin
101118
Local<Integer>(), // script id
102119
Local<Value>(), // source map URL
103120
False(isolate), // is opaque (?)
104121
False(isolate), // is WASM
105122
True(isolate)); // is ES6 module
106-
TryCatch try_catch(isolate);
123+
Context::Scope context_scope(context);
107124
ScriptCompiler::Source source(source_text, origin);
108125
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
109126
CHECK(try_catch.HasCaught());
@@ -116,15 +133,14 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
116133
}
117134
}
118135

119-
Local<Object> that = args.This();
120-
Local<Context> context = that->CreationContext();
121136
Local<String> url_str = FIXED_ONE_BYTE_STRING(isolate, "url");
122137

123138
if (!that->Set(context, url_str, url).FromMaybe(false)) {
124139
return;
125140
}
126141

127142
ModuleWrap* obj = new ModuleWrap(env, that, module, url);
143+
obj->context_.Reset(isolate, context);
128144

129145
env->module_map.emplace(module->GetIdentityHash(), obj);
130146
Wrap(that, obj);
@@ -141,15 +157,19 @@ void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
141157
return;
142158
}
143159

144-
Local<Function> resolver_arg = args[0].As<Function>();
145-
146160
Local<Object> that = args.This();
147-
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
148-
CHECK_NE(obj, nullptr);
149-
Local<Context> mod_context = that->CreationContext();
150-
if (obj->linked_) return;
161+
162+
ModuleWrap* obj;
163+
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
164+
165+
if (obj->linked_)
166+
return;
151167
obj->linked_ = true;
152-
Local<Module> module(obj->module_.Get(isolate));
168+
169+
Local<Function> resolver_arg = args[0].As<Function>();
170+
171+
Local<Context> mod_context = obj->context_.Get(isolate);
172+
Local<Module> module = obj->module_.Get(isolate);
153173

154174
// call the dependency resolve callbacks
155175
for (int i = 0; i < module->GetModuleRequestsLength(); i++) {
@@ -181,11 +201,9 @@ void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
181201
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
182202
Environment* env = Environment::GetCurrent(args);
183203
Isolate* isolate = args.GetIsolate();
184-
Local<Object> that = args.This();
185-
Local<Context> context = that->CreationContext();
186-
187-
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
188-
CHECK_NE(obj, nullptr);
204+
ModuleWrap* obj;
205+
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
206+
Local<Context> context = obj->context_.Get(isolate);
189207
Local<Module> module = obj->module_.Get(isolate);
190208
TryCatch try_catch(isolate);
191209
Maybe<bool> ok =
@@ -208,14 +226,60 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
208226
}
209227

210228
void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
229+
Environment* env = Environment::GetCurrent(args);
211230
Isolate* isolate = args.GetIsolate();
212-
Local<Object> that = args.This();
213-
Local<Context> context = that->CreationContext();
214-
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
215-
CHECK_NE(obj, nullptr);
216-
MaybeLocal<Value> result = obj->module_.Get(isolate)->Evaluate(context);
231+
ModuleWrap* obj;
232+
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
233+
Local<Context> context = obj->context_.Get(isolate);
234+
Local<Module> module = obj->module_.Get(isolate);
217235

218-
if (result.IsEmpty()) {
236+
Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env);
237+
TryCatch try_catch(isolate);
238+
Maybe<int64_t> maybe_timeout =
239+
contextify::GetTimeoutArg(env, args[0]);
240+
Maybe<bool> maybe_break_on_sigint =
241+
contextify::GetBreakOnSigintArg(env, args[0]);
242+
243+
if (try_catch.HasCaught()) {
244+
no_abort_scope.Close();
245+
try_catch.ReThrow();
246+
return;
247+
}
248+
249+
int64_t timeout = maybe_timeout.ToChecked();
250+
bool break_on_sigint = maybe_break_on_sigint.ToChecked();
251+
252+
bool timed_out = false;
253+
bool received_signal = false;
254+
MaybeLocal<Value> result;
255+
if (break_on_sigint && timeout != -1) {
256+
Watchdog wd(isolate, timeout, &timed_out);
257+
SigintWatchdog swd(isolate, &received_signal);
258+
result = module->Evaluate(context);
259+
} else if (break_on_sigint) {
260+
SigintWatchdog swd(isolate, &received_signal);
261+
result = module->Evaluate(context);
262+
} else if (timeout != -1) {
263+
Watchdog wd(isolate, timeout, &timed_out);
264+
result = module->Evaluate(context);
265+
} else {
266+
result = module->Evaluate(context);
267+
}
268+
269+
if (timed_out || received_signal) {
270+
// It is possible that execution was terminated by another timeout in
271+
// which this timeout is nested, so check whether one of the watchdogs
272+
// from this invocation is responsible for termination.
273+
if (timed_out) {
274+
env->ThrowError("Script execution timed out.");
275+
} else if (received_signal) {
276+
env->ThrowError("Script execution interrupted.");
277+
}
278+
env->isolate()->CancelTerminateExecution();
279+
}
280+
281+
if (try_catch.HasCaught()) {
282+
try_catch.ReThrow();
219283
return;
220284
}
221285

@@ -225,9 +289,8 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
225289
void ModuleWrap::Namespace(const FunctionCallbackInfo<Value>& args) {
226290
Environment* env = Environment::GetCurrent(args);
227291
Isolate* isolate = args.GetIsolate();
228-
Local<Object> that = args.This();
229-
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
230-
CHECK_NE(obj, nullptr);
292+
ModuleWrap* obj;
293+
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
231294

232295
Local<Module> module = obj->module_.Get(isolate);
233296

@@ -245,6 +308,44 @@ void ModuleWrap::Namespace(const FunctionCallbackInfo<Value>& args) {
245308
args.GetReturnValue().Set(result);
246309
}
247310

311+
void ModuleWrap::GetStatus(const FunctionCallbackInfo<Value>& args) {
312+
Isolate* isolate = args.GetIsolate();
313+
ModuleWrap* obj;
314+
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
315+
316+
Local<Module> module = obj->module_.Get(isolate);
317+
318+
args.GetReturnValue().Set(module->GetStatus());
319+
}
320+
321+
void ModuleWrap::GetStaticDependencySpecifiers(
322+
const FunctionCallbackInfo<Value>& args) {
323+
Environment* env = Environment::GetCurrent(args);
324+
ModuleWrap* obj;
325+
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
326+
327+
Local<Module> module = obj->module_.Get(env->isolate());
328+
329+
int count = module->GetModuleRequestsLength();
330+
331+
Local<Array> specifiers = Array::New(env->isolate(), count);
332+
333+
for (int i = 0; i < count; i++)
334+
specifiers->Set(env->context(), i, module->GetModuleRequest(i)).FromJust();
335+
336+
args.GetReturnValue().Set(specifiers);
337+
}
338+
339+
void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
340+
Isolate* isolate = args.GetIsolate();
341+
ModuleWrap* obj;
342+
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
343+
344+
Local<Module> module = obj->module_.Get(isolate);
345+
346+
args.GetReturnValue().Set(module->GetException());
347+
}
348+
248349
MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
249350
Local<String> specifier,
250351
Local<Module> referrer) {
@@ -636,12 +737,29 @@ void ModuleWrap::Initialize(Local<Object> target,
636737
env->SetProtoMethod(tpl, "instantiate", Instantiate);
637738
env->SetProtoMethod(tpl, "evaluate", Evaluate);
638739
env->SetProtoMethod(tpl, "namespace", Namespace);
740+
env->SetProtoMethod(tpl, "getStatus", GetStatus);
741+
env->SetProtoMethod(tpl, "getError", GetError);
742+
env->SetProtoMethod(tpl, "getStaticDependencySpecifiers",
743+
GetStaticDependencySpecifiers);
639744

640745
target->Set(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), tpl->GetFunction());
641746
env->SetMethod(target, "resolve", node::loader::ModuleWrap::Resolve);
642747
env->SetMethod(target,
643748
"setImportModuleDynamicallyCallback",
644749
node::loader::ModuleWrap::SetImportModuleDynamicallyCallback);
750+
751+
#define V(name) \
752+
target->Set(context, \
753+
FIXED_ONE_BYTE_STRING(env->isolate(), #name), \
754+
Integer::New(env->isolate(), Module::Status::name)) \
755+
.FromJust()
756+
V(kUninstantiated);
757+
V(kInstantiating);
758+
V(kInstantiated);
759+
V(kEvaluating);
760+
V(kEvaluated);
761+
V(kErrored);
762+
#undef V
645763
}
646764

647765
} // namespace loader

‎src/module_wrap.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ class ModuleWrap : public BaseObject {
3636
static void Instantiate(const v8::FunctionCallbackInfo<v8::Value>& args);
3737
static void Evaluate(const v8::FunctionCallbackInfo<v8::Value>& args);
3838
static void Namespace(const v8::FunctionCallbackInfo<v8::Value>& args);
39-
static void GetUrl(v8::Local<v8::String> property,
40-
const v8::PropertyCallbackInfo<v8::Value>& info);
39+
static void GetStatus(const v8::FunctionCallbackInfo<v8::Value>& args);
40+
static void GetError(const v8::FunctionCallbackInfo<v8::Value>& args);
41+
static void GetStaticDependencySpecifiers(
42+
const v8::FunctionCallbackInfo<v8::Value>& args);
43+
4144
static void Resolve(const v8::FunctionCallbackInfo<v8::Value>& args);
4245
static void SetImportModuleDynamicallyCallback(
4346
const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -50,6 +53,7 @@ class ModuleWrap : public BaseObject {
5053
v8::Persistent<v8::String> url_;
5154
bool linked_ = false;
5255
std::unordered_map<std::string, v8::Persistent<v8::Promise>> resolve_cache_;
56+
v8::Persistent<v8::Context> context_;
5357
};
5458

5559
} // namespace loader

‎src/node.cc

+10
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ bool config_preserve_symlinks = false;
240240
// that is used by lib/module.js
241241
bool config_experimental_modules = false;
242242

243+
// Set in node.cc by ParseArgs when --experimental-vm-modules is used.
244+
// Used in node_config.cc to set a constant on process.binding('config')
245+
// that is used by lib/vm.js
246+
bool config_experimental_vm_modules = false;
247+
243248
// Set in node.cc by ParseArgs when --loader is used.
244249
// Used in node_config.cc to set a constant on process.binding('config')
245250
// that is used by lib/internal/bootstrap_node.js
@@ -3424,6 +3429,8 @@ static void PrintHelp() {
34243429
" --preserve-symlinks preserve symbolic links when resolving\n"
34253430
" --experimental-modules experimental ES Module support\n"
34263431
" and caching modules\n"
3432+
" --experimental-vm-modules experimental ES Module support\n"
3433+
" in vm module\n"
34273434
#endif
34283435
"\n"
34293436
"Environment variables:\n"
@@ -3503,6 +3510,7 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env,
35033510
"--napi-modules",
35043511
"--expose-http2", // keep as a non-op through v9.x
35053512
"--experimental-modules",
3513+
"--experimental-vm-modules",
35063514
"--loader",
35073515
"--trace-warnings",
35083516
"--redirect-warnings",
@@ -3670,6 +3678,8 @@ static void ParseArgs(int* argc,
36703678
config_preserve_symlinks = true;
36713679
} else if (strcmp(arg, "--experimental-modules") == 0) {
36723680
config_experimental_modules = true;
3681+
} else if (strcmp(arg, "--experimental-vm-modules") == 0) {
3682+
config_experimental_vm_modules = true;
36733683
} else if (strcmp(arg, "--loader") == 0) {
36743684
const char* module = argv[index + 1];
36753685
if (!config_experimental_modules) {

‎src/node_config.cc

+3
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ static void InitConfig(Local<Object> target,
8282
}
8383
}
8484

85+
if (config_experimental_vm_modules)
86+
READONLY_BOOLEAN_PROPERTY("experimentalVMModules");
87+
8588
if (config_pending_deprecation)
8689
READONLY_BOOLEAN_PROPERTY("pendingDeprecation");
8790

‎src/node_internals.h

+5
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ extern bool config_preserve_symlinks;
172172
// that is used by lib/module.js
173173
extern bool config_experimental_modules;
174174

175+
// Set in node.cc by ParseArgs when --experimental-vm-modules is used.
176+
// Used in node_config.cc to set a constant on process.binding('config')
177+
// that is used by lib/vm.js
178+
extern bool config_experimental_vm_modules;
179+
175180
// Set in node.cc by ParseArgs when --loader is used.
176181
// Used in node_config.cc to set a constant on process.binding('config')
177182
// that is used by lib/internal/bootstrap_node.js

‎test/parallel/test-vm-module-basic.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
// Flags: --experimental-vm-modules
4+
5+
const common = require('../common');
6+
const assert = require('assert');
7+
const { Module, createContext } = require('vm');
8+
9+
common.crashOnUnhandledRejection();
10+
11+
(async function test1() {
12+
const context = createContext({
13+
foo: 'bar',
14+
baz: undefined,
15+
typeofProcess: undefined,
16+
});
17+
const m = new Module(
18+
'baz = foo; typeofProcess = typeof process; typeof Object;',
19+
{ context }
20+
);
21+
assert.strictEqual(m.status, 'uninstantiated');
22+
await m.link(common.mustNotCall());
23+
m.instantiate();
24+
assert.strictEqual(m.status, 'instantiated');
25+
const result = await m.evaluate();
26+
assert.strictEqual(m.status, 'evaluated');
27+
assert.strictEqual(Object.getPrototypeOf(result), null);
28+
assert.deepStrictEqual(context, {
29+
foo: 'bar',
30+
baz: 'bar',
31+
typeofProcess: 'undefined'
32+
});
33+
assert.strictEqual(result.result, 'function');
34+
}());
35+
36+
(async () => {
37+
const m = new Module(
38+
'global.vmResult = "foo"; Object.prototype.toString.call(process);'
39+
);
40+
await m.link(common.mustNotCall());
41+
m.instantiate();
42+
const { result } = await m.evaluate();
43+
assert.strictEqual(global.vmResult, 'foo');
44+
assert.strictEqual(result, '[object process]');
45+
delete global.vmResult;
46+
})();
47+
48+
(async () => {
49+
const m = new Module('while (true) {}');
50+
await m.link(common.mustNotCall());
51+
m.instantiate();
52+
await m.evaluate({ timeout: 500 })
53+
.then(() => assert(false), () => {});
54+
})();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
// Flags: --experimental-vm-modules --experimental-modules --harmony-dynamic-import
4+
5+
const common = require('../common');
6+
common.crashOnUnhandledRejection();
7+
8+
const assert = require('assert');
9+
const { Module, createContext } = require('vm');
10+
11+
const finished = common.mustCall();
12+
13+
(async function() {
14+
const m = new Module('import("foo")', { context: createContext() });
15+
await m.link(common.mustNotCall());
16+
m.instantiate();
17+
const { result } = await m.evaluate();
18+
let threw = false;
19+
try {
20+
await result;
21+
} catch (err) {
22+
threw = true;
23+
assert.strictEqual(err.message, 'import() called outside of main context');
24+
}
25+
assert(threw);
26+
finished();
27+
}());
+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
'use strict';
2+
3+
// Flags: --experimental-vm-modules
4+
5+
const common = require('../common');
6+
common.crashOnUnhandledRejection();
7+
8+
const assert = require('assert');
9+
10+
const { Module, createContext } = require('vm');
11+
12+
async function expectsRejection(fn, settings) {
13+
const validateError = common.expectsError(settings);
14+
// Retain async context.
15+
const storedError = new Error('Thrown from:');
16+
try {
17+
await fn();
18+
} catch (err) {
19+
try {
20+
validateError(err);
21+
} catch (validationError) {
22+
console.error(validationError);
23+
console.error('Original error:');
24+
console.error(err);
25+
throw storedError;
26+
}
27+
return;
28+
}
29+
assert.fail('Missing expected exception');
30+
}
31+
32+
async function createEmptyLinkedModule() {
33+
const m = new Module('');
34+
await m.link(common.mustNotCall());
35+
return m;
36+
}
37+
38+
async function checkArgType() {
39+
common.expectsError(() => {
40+
new Module();
41+
}, {
42+
code: 'ERR_INVALID_ARG_TYPE',
43+
type: TypeError
44+
});
45+
46+
for (const invalidOptions of [
47+
0, 1, null, true, 'str', () => {}, Symbol.iterator
48+
]) {
49+
common.expectsError(() => {
50+
new Module('', invalidOptions);
51+
}, {
52+
code: 'ERR_INVALID_ARG_TYPE',
53+
type: TypeError
54+
});
55+
}
56+
57+
for (const invalidLinker of [
58+
0, 1, undefined, null, true, 'str', {}, Symbol.iterator
59+
]) {
60+
await expectsRejection(async () => {
61+
const m = new Module('');
62+
await m.link(invalidLinker);
63+
}, {
64+
code: 'ERR_INVALID_ARG_TYPE',
65+
type: TypeError
66+
});
67+
}
68+
}
69+
70+
// Check methods/properties can only be used under a specific state.
71+
async function checkModuleState() {
72+
await expectsRejection(async () => {
73+
const m = new Module('');
74+
await m.link(common.mustNotCall());
75+
assert.strictEqual(m.linkingStatus, 'linked');
76+
await m.link(common.mustNotCall());
77+
}, {
78+
code: 'ERR_VM_MODULE_ALREADY_LINKED'
79+
});
80+
81+
await expectsRejection(async () => {
82+
const m = new Module('');
83+
m.link(common.mustNotCall());
84+
assert.strictEqual(m.linkingStatus, 'linking');
85+
await m.link(common.mustNotCall());
86+
}, {
87+
code: 'ERR_VM_MODULE_ALREADY_LINKED'
88+
});
89+
90+
common.expectsError(() => {
91+
const m = new Module('');
92+
m.instantiate();
93+
}, {
94+
code: 'ERR_VM_MODULE_NOT_LINKED'
95+
});
96+
97+
await expectsRejection(async () => {
98+
const m = new Module('import "foo";');
99+
try {
100+
await m.link(common.mustCall(() => ({})));
101+
} catch (err) {
102+
assert.strictEqual(m.linkingStatus, 'errored');
103+
m.instantiate();
104+
}
105+
assert.fail('Unreachable');
106+
}, {
107+
code: 'ERR_VM_MODULE_NOT_LINKED'
108+
});
109+
110+
{
111+
const m = new Module('import "foo";');
112+
await m.link(common.mustCall(async (module, specifier) => {
113+
assert.strictEqual(module, m);
114+
assert.strictEqual(specifier, 'foo');
115+
assert.strictEqual(m.linkingStatus, 'linking');
116+
common.expectsError(() => {
117+
m.instantiate();
118+
}, {
119+
code: 'ERR_VM_MODULE_NOT_LINKED'
120+
});
121+
return new Module('');
122+
}));
123+
m.instantiate();
124+
await m.evaluate();
125+
}
126+
127+
await expectsRejection(async () => {
128+
const m = new Module('');
129+
await m.evaluate();
130+
}, {
131+
code: 'ERR_VM_MODULE_STATUS',
132+
message: 'Module status must be one of instantiated, evaluated, and errored'
133+
});
134+
135+
await expectsRejection(async () => {
136+
const m = await createEmptyLinkedModule();
137+
await m.evaluate();
138+
}, {
139+
code: 'ERR_VM_MODULE_STATUS',
140+
message: 'Module status must be one of instantiated, evaluated, and errored'
141+
});
142+
143+
common.expectsError(() => {
144+
const m = new Module('');
145+
m.error;
146+
}, {
147+
code: 'ERR_VM_MODULE_STATUS',
148+
message: 'Module status must be errored'
149+
});
150+
151+
await expectsRejection(async () => {
152+
const m = await createEmptyLinkedModule();
153+
m.instantiate();
154+
await m.evaluate();
155+
m.error;
156+
}, {
157+
code: 'ERR_VM_MODULE_STATUS',
158+
message: 'Module status must be errored'
159+
});
160+
161+
common.expectsError(() => {
162+
const m = new Module('');
163+
m.namespace;
164+
}, {
165+
code: 'ERR_VM_MODULE_STATUS',
166+
message: 'Module status must not be uninstantiated or instantiating'
167+
});
168+
169+
await expectsRejection(async () => {
170+
const m = await createEmptyLinkedModule();
171+
m.namespace;
172+
}, {
173+
code: 'ERR_VM_MODULE_STATUS',
174+
message: 'Module status must not be uninstantiated or instantiating'
175+
});
176+
}
177+
178+
// Check link() fails when the returned module is not valid.
179+
async function checkLinking() {
180+
await expectsRejection(async () => {
181+
const m = new Module('import "foo";');
182+
try {
183+
await m.link(common.mustCall(() => ({})));
184+
} catch (err) {
185+
assert.strictEqual(m.linkingStatus, 'errored');
186+
throw err;
187+
}
188+
assert.fail('Unreachable');
189+
}, {
190+
code: 'ERR_VM_MODULE_NOT_MODULE'
191+
});
192+
193+
await expectsRejection(async () => {
194+
const c = createContext({ a: 1 });
195+
const foo = new Module('', { context: c });
196+
await foo.link(common.mustNotCall());
197+
const bar = new Module('import "foo";');
198+
try {
199+
await bar.link(common.mustCall(() => foo));
200+
} catch (err) {
201+
assert.strictEqual(bar.linkingStatus, 'errored');
202+
throw err;
203+
}
204+
assert.fail('Unreachable');
205+
}, {
206+
code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT'
207+
});
208+
209+
await expectsRejection(async () => {
210+
const erroredModule = new Module('import "foo";');
211+
try {
212+
await erroredModule.link(common.mustCall(() => ({})));
213+
} catch (err) {
214+
// ignored
215+
} finally {
216+
assert.strictEqual(erroredModule.linkingStatus, 'errored');
217+
}
218+
219+
const rootModule = new Module('import "errored";');
220+
await rootModule.link(common.mustCall(() => erroredModule));
221+
}, {
222+
code: 'ERR_VM_MODULE_LINKING_ERRORED'
223+
});
224+
}
225+
226+
// Check the JavaScript engine deals with exceptions correctly
227+
async function checkExecution() {
228+
await (async () => {
229+
const m = new Module('import { nonexistent } from "module";');
230+
await m.link(common.mustCall(() => new Module('')));
231+
232+
// There is no code for this exception since it is thrown by the JavaScript
233+
// engine.
234+
assert.throws(() => {
235+
m.instantiate();
236+
}, SyntaxError);
237+
})();
238+
239+
await (async () => {
240+
const m = new Module('throw new Error();');
241+
await m.link(common.mustNotCall());
242+
m.instantiate();
243+
const evaluatePromise = m.evaluate();
244+
await evaluatePromise.catch(() => {});
245+
assert.strictEqual(m.status, 'errored');
246+
try {
247+
await evaluatePromise;
248+
} catch (err) {
249+
assert.strictEqual(m.error, err);
250+
return;
251+
}
252+
assert.fail('Missing expected exception');
253+
})();
254+
}
255+
256+
const finished = common.mustCall();
257+
258+
(async function main() {
259+
await checkArgType();
260+
await checkModuleState();
261+
await checkLinking();
262+
await checkExecution();
263+
finished();
264+
})();

‎test/parallel/test-vm-module-link.js

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use strict';
2+
3+
// Flags: --experimental-vm-modules
4+
5+
const common = require('../common');
6+
common.crashOnUnhandledRejection();
7+
8+
const assert = require('assert');
9+
const { URL } = require('url');
10+
11+
const { Module } = require('vm');
12+
13+
async function simple() {
14+
const foo = new Module('export default 5;');
15+
await foo.link(common.mustNotCall());
16+
17+
const bar = new Module('import five from "foo"; five');
18+
19+
assert.deepStrictEqual(bar.dependencySpecifiers, ['foo']);
20+
21+
await bar.link(common.mustCall((module, specifier) => {
22+
assert.strictEqual(module, bar);
23+
assert.strictEqual(specifier, 'foo');
24+
return foo;
25+
}));
26+
27+
bar.instantiate();
28+
29+
assert.strictEqual((await bar.evaluate()).result, 5);
30+
}
31+
32+
async function depth() {
33+
const foo = new Module('export default 5');
34+
await foo.link(common.mustNotCall());
35+
36+
async function getProxy(parentName, parentModule) {
37+
const mod = new Module(`
38+
import ${parentName} from '${parentName}';
39+
export default ${parentName};
40+
`);
41+
await mod.link(common.mustCall((module, specifier) => {
42+
assert.strictEqual(module, mod);
43+
assert.strictEqual(specifier, parentName);
44+
return parentModule;
45+
}));
46+
return mod;
47+
}
48+
49+
const bar = await getProxy('foo', foo);
50+
const baz = await getProxy('bar', bar);
51+
const barz = await getProxy('baz', baz);
52+
53+
barz.instantiate();
54+
await barz.evaluate();
55+
56+
assert.strictEqual(barz.namespace.default, 5);
57+
}
58+
59+
async function circular() {
60+
const foo = new Module(`
61+
import getFoo from 'bar';
62+
export let foo = 42;
63+
export default getFoo();
64+
`);
65+
const bar = new Module(`
66+
import { foo } from 'foo';
67+
export default function getFoo() {
68+
return foo;
69+
}
70+
`);
71+
await foo.link(common.mustCall(async (fooModule, fooSpecifier) => {
72+
assert.strictEqual(fooModule, foo);
73+
assert.strictEqual(fooSpecifier, 'bar');
74+
await bar.link(common.mustCall((barModule, barSpecifier) => {
75+
assert.strictEqual(barModule, bar);
76+
assert.strictEqual(barSpecifier, 'foo');
77+
assert.strictEqual(foo.linkingStatus, 'linking');
78+
return foo;
79+
}));
80+
assert.strictEqual(bar.linkingStatus, 'linked');
81+
return bar;
82+
}));
83+
84+
foo.instantiate();
85+
await foo.evaluate();
86+
assert.strictEqual(foo.namespace.default, 42);
87+
}
88+
89+
async function circular2() {
90+
const sourceMap = {
91+
root: `
92+
import * as a from './a.mjs';
93+
import * as b from './b.mjs';
94+
if (!('fromA' in a))
95+
throw new Error();
96+
if (!('fromB' in a))
97+
throw new Error();
98+
if (!('fromA' in b))
99+
throw new Error();
100+
if (!('fromB' in b))
101+
throw new Error();
102+
`,
103+
'./a.mjs': `
104+
export * from './b.mjs';
105+
export var fromA;
106+
`,
107+
'./b.mjs': `
108+
export * from './a.mjs';
109+
export var fromB;
110+
`
111+
};
112+
const moduleMap = new Map();
113+
const rootModule = new Module(sourceMap.root, { url: 'vm:root' });
114+
async function link(referencingModule, specifier) {
115+
if (moduleMap.has(specifier)) {
116+
return moduleMap.get(specifier);
117+
}
118+
const mod = new Module(sourceMap[specifier], { url: new URL(specifier, 'file:///').href });
119+
moduleMap.set(specifier, mod);
120+
return mod;
121+
}
122+
await rootModule.link(link);
123+
rootModule.instantiate();
124+
await rootModule.evaluate();
125+
}
126+
127+
const finished = common.mustCall();
128+
129+
(async function main() {
130+
await simple();
131+
await depth();
132+
await circular();
133+
await circular2();
134+
finished();
135+
})();
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
// Flags: --experimental-vm-modules
4+
5+
const common = require('../common');
6+
common.crashOnUnhandledRejection();
7+
8+
const assert = require('assert');
9+
10+
const { Module } = require('vm');
11+
12+
const finished = common.mustCall();
13+
14+
(async function main() {
15+
{
16+
const m = new Module('1');
17+
await m.link(common.mustNotCall());
18+
m.instantiate();
19+
assert.strictEqual((await m.evaluate()).result, 1);
20+
assert.strictEqual((await m.evaluate()).result, undefined);
21+
assert.strictEqual((await m.evaluate()).result, undefined);
22+
}
23+
24+
{
25+
const m = new Module('throw new Error()');
26+
await m.link(common.mustNotCall());
27+
m.instantiate();
28+
29+
let threw = false;
30+
try {
31+
await m.evaluate();
32+
} catch (err) {
33+
assert(err instanceof Error);
34+
threw = true;
35+
}
36+
assert(threw);
37+
38+
threw = false;
39+
try {
40+
await m.evaluate();
41+
} catch (err) {
42+
assert(err instanceof Error);
43+
threw = true;
44+
}
45+
assert(threw);
46+
}
47+
48+
finished();
49+
})();

0 commit comments

Comments
 (0)
Please sign in to comment.