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 <timothygu99@gmail.com> PR-URL: https://github.com/nodejs/node/pull/17560 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
This commit is contained in:
parent
2033a9f436
commit
0993fbe5b2
@ -1639,6 +1639,43 @@ entry types were found.
|
|||||||
|
|
||||||
Superseded by `ERR_OUT_OF_RANGE`
|
Superseded by `ERR_OUT_OF_RANGE`
|
||||||
|
|
||||||
|
<a id="ERR_VM_MODULE_ALREADY_LINKED"></a>
|
||||||
|
### ERR_VM_MODULE_ALREADY_LINKED
|
||||||
|
|
||||||
|
The module attempted to be linked is not eligible for linking, because of one of
|
||||||
|
the following reasons:
|
||||||
|
|
||||||
|
- It has already been linked (`linkingStatus` is `'linked'`)
|
||||||
|
- It is being linked (`linkingStatus` is `'linking'`)
|
||||||
|
- Linking has failed for this module (`linkingStatus` is `'errored'`)
|
||||||
|
|
||||||
|
<a id="ERR_VM_MODULE_DIFFERENT_CONTEXT"></a>
|
||||||
|
### ERR_VM_MODULE_DIFFERENT_CONTEXT
|
||||||
|
|
||||||
|
The module being returned from the linker function is from a different context
|
||||||
|
than the parent module. Linked modules must share the same context.
|
||||||
|
|
||||||
|
<a id="ERR_VM_MODULE_LINKING_ERRORED"></a>
|
||||||
|
### ERR_VM_MODULE_LINKING_ERRORED
|
||||||
|
|
||||||
|
The linker function returned a module for which linking has failed.
|
||||||
|
|
||||||
|
<a id="ERR_VM_MODULE_NOT_LINKED"></a>
|
||||||
|
### ERR_VM_MODULE_NOT_LINKED
|
||||||
|
|
||||||
|
The module must be successfully linked before instantiation.
|
||||||
|
|
||||||
|
<a id="ERR_VM_MODULE_NOT_MODULE"></a>
|
||||||
|
### ERR_VM_MODULE_NOT_MODULE
|
||||||
|
|
||||||
|
The fulfilled value of a linking promise is not a `vm.Module` object.
|
||||||
|
|
||||||
|
<a id="ERR_VM_MODULE_STATUS"></a>
|
||||||
|
### ERR_VM_MODULE_STATUS
|
||||||
|
|
||||||
|
The current module's status does not allow for this operation. The specific
|
||||||
|
meaning of the error depends on the specific function.
|
||||||
|
|
||||||
<a id="ERR_ZLIB_BINDING_CLOSED"></a>
|
<a id="ERR_ZLIB_BINDING_CLOSED"></a>
|
||||||
### ERR_ZLIB_BINDING_CLOSED
|
### ERR_ZLIB_BINDING_CLOSED
|
||||||
|
|
||||||
|
322
doc/api/vm.md
322
doc/api/vm.md
@ -43,6 +43,322 @@ console.log(x); // 1; y is not defined.
|
|||||||
*Note*: The vm module is not a security mechanism.
|
*Note*: The vm module is not a security mechanism.
|
||||||
**Do not use it to run untrusted code**.
|
**Do not use it to run untrusted code**.
|
||||||
|
|
||||||
|
## Class: vm.Module
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1 - Experimental
|
||||||
|
|
||||||
|
*This feature is only available with the `--experimental-vm-modules` command
|
||||||
|
flag enabled.*
|
||||||
|
|
||||||
|
The `vm.Module` class provides a low-level interface for using ECMAScript
|
||||||
|
modules in VM contexts. It is the counterpart of the `vm.Script` class that
|
||||||
|
closely mirrors [Source Text Module Record][]s as defined in the ECMAScript
|
||||||
|
specification.
|
||||||
|
|
||||||
|
Unlike `vm.Script` however, every `vm.Module` object is bound to a context from
|
||||||
|
its creation. Operations on `vm.Module` objects are intrinsically asynchronous,
|
||||||
|
in contrast with the synchronous nature of `vm.Script` objects. With the help
|
||||||
|
of async functions, however, manipulating `vm.Module` objects is fairly
|
||||||
|
straightforward.
|
||||||
|
|
||||||
|
Using a `vm.Module` object requires four distinct steps: creation/parsing,
|
||||||
|
linking, instantiation, and evaluation. These four steps are illustrated in the
|
||||||
|
following example.
|
||||||
|
|
||||||
|
*Note*: This implementation lies at a lower level than the [ECMAScript Module
|
||||||
|
loader][]. There is also currently no way to interact with the Loader, though
|
||||||
|
support is planned.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
const contextifiedSandbox = vm.createContext({ secret: 42 });
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Step 1
|
||||||
|
//
|
||||||
|
// Create a Module by constructing a new `vm.Module` object. This parses the
|
||||||
|
// provided source text, throwing a `SyntaxError` if anything goes wrong. By
|
||||||
|
// default, a Module is created in the top context. But here, we specify
|
||||||
|
// `contextifiedSandbox` as the context this Module belongs to.
|
||||||
|
//
|
||||||
|
// Here, we attempt to obtain the default export from the module "foo", and
|
||||||
|
// put it into local binding "secret".
|
||||||
|
|
||||||
|
const bar = new vm.Module(`
|
||||||
|
import s from 'foo';
|
||||||
|
s;
|
||||||
|
`, { context: contextifiedSandbox });
|
||||||
|
|
||||||
|
|
||||||
|
// Step 2
|
||||||
|
//
|
||||||
|
// "Link" the imported dependencies of this Module to it.
|
||||||
|
//
|
||||||
|
// The provided linking callback (the "linker") accepts two arguments: the
|
||||||
|
// parent module (`bar` in this case) and the string that is the specifier of
|
||||||
|
// the imported module. The callback is expected to return a Module that
|
||||||
|
// corresponds to the provided specifier, with certain requirements documented
|
||||||
|
// in `module.link()`.
|
||||||
|
//
|
||||||
|
// If linking has not started for the returned Module, the same linker
|
||||||
|
// callback will be called on the returned Module.
|
||||||
|
//
|
||||||
|
// Even top-level Modules without dependencies must be explicitly linked. The
|
||||||
|
// callback provided would never be called, however.
|
||||||
|
//
|
||||||
|
// The link() method returns a Promise that will be resolved when all the
|
||||||
|
// Promises returned by the linker resolve.
|
||||||
|
//
|
||||||
|
// Note: This is a contrived example in that the linker function creates a new
|
||||||
|
// "foo" module every time it is called. In a full-fledged module system, a
|
||||||
|
// cache would probably be used to avoid duplicated modules.
|
||||||
|
|
||||||
|
async function linker(referencingModule, specifier) {
|
||||||
|
if (specifier === 'foo') {
|
||||||
|
return new vm.Module(`
|
||||||
|
// The "secret" variable refers to the global variable we added to
|
||||||
|
// "contextifiedSandbox" when creating the context.
|
||||||
|
export default secret;
|
||||||
|
`, { context: referencingModule.context });
|
||||||
|
|
||||||
|
// Using `contextifiedSandbox` instead of `referencingModule.context`
|
||||||
|
// here would work as well.
|
||||||
|
}
|
||||||
|
throw new Error(`Unable to resolve dependency: ${specifier}`);
|
||||||
|
}
|
||||||
|
await bar.link(linker);
|
||||||
|
|
||||||
|
|
||||||
|
// Step 3
|
||||||
|
//
|
||||||
|
// Instantiate the top-level Module.
|
||||||
|
//
|
||||||
|
// Only the top-level Module needs to be explicitly instantiated; its
|
||||||
|
// dependencies will be recursively instantiated by instantiate().
|
||||||
|
|
||||||
|
bar.instantiate();
|
||||||
|
|
||||||
|
|
||||||
|
// Step 4
|
||||||
|
//
|
||||||
|
// Evaluate the Module. The evaluate() method returns a Promise with a single
|
||||||
|
// property "result" that contains the result of the very last statement
|
||||||
|
// executed in the Module. In the case of `bar`, it is `s;`, which refers to
|
||||||
|
// the default export of the `foo` module, the `secret` we set in the
|
||||||
|
// beginning to 42.
|
||||||
|
|
||||||
|
const { result } = await bar.evaluate();
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
// Prints 42.
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Constructor: new vm.Module(code[, options])
|
||||||
|
|
||||||
|
* `code` {string} JavaScript Module code to parse
|
||||||
|
* `options`
|
||||||
|
* `url` {string} URL used in module resolution and stack traces. **Default**:
|
||||||
|
`'vm:module(i)'` where `i` is a context-specific ascending index.
|
||||||
|
* `context` {Object} The [contextified][] object as returned by the
|
||||||
|
`vm.createContext()` method, to compile and evaluate this Module in.
|
||||||
|
* `lineOffset` {integer} Specifies the line number offset that is displayed
|
||||||
|
in stack traces produced by this Module.
|
||||||
|
* `columnOffset` {integer} Spcifies the column number offset that is displayed
|
||||||
|
in stack traces produced by this Module.
|
||||||
|
|
||||||
|
Creates a new ES `Module` object.
|
||||||
|
|
||||||
|
### module.dependencySpecifiers
|
||||||
|
|
||||||
|
* {string[]}
|
||||||
|
|
||||||
|
The specifiers of all dependencies of this module. The returned array is frozen
|
||||||
|
to disallow any changes to it.
|
||||||
|
|
||||||
|
Corresponds to the [[RequestedModules]] field of [Source Text Module Record][]s
|
||||||
|
in the ECMAScript specification.
|
||||||
|
|
||||||
|
### module.error
|
||||||
|
|
||||||
|
* {any}
|
||||||
|
|
||||||
|
If the `module.status` is `'errored'`, this property contains the exception thrown
|
||||||
|
by the module during evaluation. If the status is anything else, accessing this
|
||||||
|
property will result in a thrown exception.
|
||||||
|
|
||||||
|
*Note*: `undefined` cannot be used for cases where there is not a thrown
|
||||||
|
exception due to possible ambiguity with `throw undefined;`.
|
||||||
|
|
||||||
|
Corresponds to the [[EvaluationError]] field of [Source Text Module Record][]s
|
||||||
|
in the ECMAScript specification.
|
||||||
|
|
||||||
|
### module.linkingStatus
|
||||||
|
|
||||||
|
* {string}
|
||||||
|
|
||||||
|
The current linking status of `module`. It will be one of the following values:
|
||||||
|
|
||||||
|
- `'unlinked'`: `module.link()` has not yet been called.
|
||||||
|
- `'linking'`: `module.link()` has been called, but not all Promises returned by
|
||||||
|
the linker function have been resolved yet.
|
||||||
|
- `'linked'`: `module.link()` has been called, and all its dependencies have
|
||||||
|
been successfully linked.
|
||||||
|
- `'errored'`: `module.link()` has been called, but at least one of its
|
||||||
|
dependencies failed to link, either because the callback returned a Promise
|
||||||
|
that is rejected, or because the Module the callback returned is invalid.
|
||||||
|
|
||||||
|
### module.namespace
|
||||||
|
|
||||||
|
* {Object}
|
||||||
|
|
||||||
|
The namespace object of the module. This is only available after instantiation
|
||||||
|
(`module.instantiate()`) has completed.
|
||||||
|
|
||||||
|
Corresponds to the [GetModuleNamespace][] abstract operation in the ECMAScript
|
||||||
|
specification.
|
||||||
|
|
||||||
|
### module.status
|
||||||
|
|
||||||
|
* {string}
|
||||||
|
|
||||||
|
The current status of the module. Will be one of:
|
||||||
|
|
||||||
|
- `'uninstantiated'`: The module is not instantiated. It may because of any of
|
||||||
|
the following reasons:
|
||||||
|
|
||||||
|
- The module was just created.
|
||||||
|
- `module.instantiate()` has been called on this module, but it failed for
|
||||||
|
some reason.
|
||||||
|
|
||||||
|
This status does not convey any information regarding if `module.link()` has
|
||||||
|
been called. See `module.linkingStatus` for that.
|
||||||
|
|
||||||
|
- `'instantiating'`: The module is currently being instantiated through a
|
||||||
|
`module.instantiate()` call on itself or a parent module.
|
||||||
|
|
||||||
|
- `'instantiated'`: The module has been instantiated successfully, but
|
||||||
|
`module.evaluate()` has not yet been called.
|
||||||
|
|
||||||
|
- `'evaluating'`: The module is being evaluated through a `module.evaluate()` on
|
||||||
|
itself or a parent module.
|
||||||
|
|
||||||
|
- `'evaluated'`: The module has been successfully evaluated.
|
||||||
|
|
||||||
|
- `'errored'`: The module has been evaluated, but an exception was thrown.
|
||||||
|
|
||||||
|
Other than `'errored'`, this status string corresponds to the specification's
|
||||||
|
[Source Text Module Record][]'s [[Status]] field. `'errored'` corresponds to
|
||||||
|
`'evaluated'` in the specification, but with [[EvaluationError]] set to a value
|
||||||
|
that is not `undefined`.
|
||||||
|
|
||||||
|
### module.url
|
||||||
|
|
||||||
|
* {string}
|
||||||
|
|
||||||
|
The URL of the current module, as set in the constructor.
|
||||||
|
|
||||||
|
### module.evaluate([options])
|
||||||
|
|
||||||
|
* `options` {Object}
|
||||||
|
* `timeout` {number} Specifies the number of milliseconds to evaluate
|
||||||
|
before terminating execution. If execution is interrupted, an [`Error`][]
|
||||||
|
will be thrown.
|
||||||
|
* `breakOnSigint` {boolean} If `true`, the execution will be terminated when
|
||||||
|
`SIGINT` (Ctrl+C) is received. Existing handlers for the event that have
|
||||||
|
been attached via `process.on("SIGINT")` will be disabled during script
|
||||||
|
execution, but will continue to work after that. If execution is
|
||||||
|
interrupted, an [`Error`][] will be thrown.
|
||||||
|
* Returns: {Promise}
|
||||||
|
|
||||||
|
Evaluate the module.
|
||||||
|
|
||||||
|
This must be called after the module has been instantiated; otherwise it will
|
||||||
|
throw an error. It could be called also when the module has already been
|
||||||
|
evaluated, in which case it will do one of the following two things:
|
||||||
|
|
||||||
|
- return `undefined` if the initial evaluation ended in success (`module.status`
|
||||||
|
is `'evaluated'`)
|
||||||
|
- rethrow the same exception the initial evaluation threw if the initial
|
||||||
|
evaluation ended in an error (`module.status` is `'errored'`)
|
||||||
|
|
||||||
|
This method cannot be called while the module is being evaluated
|
||||||
|
(`module.status` is `'evaluating'`) to prevent infinite recursion.
|
||||||
|
|
||||||
|
Corresponds to the [Evaluate() concrete method][] field of [Source Text Module
|
||||||
|
Record][]s in the ECMAScript specification.
|
||||||
|
|
||||||
|
### module.instantiate()
|
||||||
|
|
||||||
|
Instantiate the module. This must be called after linking has completed
|
||||||
|
(`linkingStatus` is `'linked'`); otherwise it will throw an error. It may also
|
||||||
|
throw an exception if one of the dependencies does not provide an export the
|
||||||
|
parent module requires.
|
||||||
|
|
||||||
|
However, if this function succeeded, further calls to this function after the
|
||||||
|
initial instantiation will be no-ops, to be consistent with the ECMAScript
|
||||||
|
specification.
|
||||||
|
|
||||||
|
Unlike other methods operating on `Module`, this function completes
|
||||||
|
synchronously and returns nothing.
|
||||||
|
|
||||||
|
Corresponds to the [Instantiate() concrete method][] field of [Source Text
|
||||||
|
Module Record][]s in the ECMAScript specification.
|
||||||
|
|
||||||
|
### module.link(linker)
|
||||||
|
|
||||||
|
* `linker` {Function}
|
||||||
|
* Returns: {Promise}
|
||||||
|
|
||||||
|
Link module dependencies. This method must be called before instantiation, and
|
||||||
|
can only be called once per module.
|
||||||
|
|
||||||
|
Two parameters will be passed to the `linker` function:
|
||||||
|
|
||||||
|
- `referencingModule` The `Module` object `link()` is called on.
|
||||||
|
- `specifier` The specifier of the requested module:
|
||||||
|
|
||||||
|
<!-- eslint-skip -->
|
||||||
|
```js
|
||||||
|
import foo from 'foo';
|
||||||
|
// ^^^^^ the module specifier
|
||||||
|
```
|
||||||
|
|
||||||
|
The function is expected to return a `Module` object or a `Promise` that
|
||||||
|
eventually resolves to a `Module` object. The returned `Module` must satisfy the
|
||||||
|
following two invariants:
|
||||||
|
|
||||||
|
- It must belong to the same context as the parent `Module`.
|
||||||
|
- Its `linkingStatus` must not be `'errored'`.
|
||||||
|
|
||||||
|
If the returned `Module`'s `linkingStatus` is `'unlinked'`, this method will be
|
||||||
|
recursively called on the returned `Module` with the same provided `linker`
|
||||||
|
function.
|
||||||
|
|
||||||
|
`link()` returns a `Promise` that will either get resolved when all linking
|
||||||
|
instances resolve to a valid `Module`, or rejected if the linker function either
|
||||||
|
throws an exception or returns an invalid `Module`.
|
||||||
|
|
||||||
|
The linker function roughly corresponds to the implementation-defined
|
||||||
|
[HostResolveImportedModule][] abstract operation in the ECMAScript
|
||||||
|
specification, with a few key differences:
|
||||||
|
|
||||||
|
- The linker function is allowed to be asynchronous while
|
||||||
|
[HostResolveImportedModule][] is synchronous.
|
||||||
|
- The linker function is executed during linking, a Node.js-specific stage
|
||||||
|
before instantiation, while [HostResolveImportedModule][] is called during
|
||||||
|
instantiation.
|
||||||
|
|
||||||
|
The actual [HostResolveImportedModule][] implementation used during module
|
||||||
|
instantiation is one that returns the modules linked during linking. Since at
|
||||||
|
that point all modules would have been fully linked already, the
|
||||||
|
[HostResolveImportedModule][] implementation is fully synchronous per
|
||||||
|
specification.
|
||||||
|
|
||||||
## Class: vm.Script
|
## Class: vm.Script
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.3.1
|
added: v0.3.1
|
||||||
@ -518,8 +834,14 @@ associating it with the `sandbox` object is what this document refers to as
|
|||||||
[`vm.createContext()`]: #vm_vm_createcontext_sandbox_options
|
[`vm.createContext()`]: #vm_vm_createcontext_sandbox_options
|
||||||
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
|
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
|
||||||
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
|
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
|
||||||
|
[GetModuleNamespace]: https://tc39.github.io/ecma262/#sec-getmodulenamespace
|
||||||
|
[ECMAScript Module Loader]: esm.html#esm_ecmascript_modules
|
||||||
|
[Evaluate() concrete method]: https://tc39.github.io/ecma262/#sec-moduleevaluation
|
||||||
|
[HostResolveImportedModule]: https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule
|
||||||
|
[Instantiate() concrete method]: https://tc39.github.io/ecma262/#sec-moduledeclarationinstantiation
|
||||||
[V8 Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide#contexts
|
[V8 Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide#contexts
|
||||||
[contextified]: #vm_what_does_it_mean_to_contextify_an_object
|
[contextified]: #vm_what_does_it_mean_to_contextify_an_object
|
||||||
[global object]: https://es5.github.io/#x15.1
|
[global object]: https://es5.github.io/#x15.1
|
||||||
[indirect `eval()` call]: https://es5.github.io/#x10.4.2
|
[indirect `eval()` call]: https://es5.github.io/#x10.4.2
|
||||||
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin
|
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin
|
||||||
|
[Source Text Module Record]: https://tc39.github.io/ecma262/#sec-source-text-module-records
|
||||||
|
@ -676,6 +676,15 @@ E('ERR_V8BREAKITERATOR', 'Full ICU data not installed. ' +
|
|||||||
'See https://github.com/nodejs/node/wiki/Intl');
|
'See https://github.com/nodejs/node/wiki/Intl');
|
||||||
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
|
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
|
||||||
'At least one valid performance entry type is required');
|
'At least one valid performance entry type is required');
|
||||||
|
E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked');
|
||||||
|
E('ERR_VM_MODULE_DIFFERENT_CONTEXT',
|
||||||
|
'Linked modules must use the same context');
|
||||||
|
E('ERR_VM_MODULE_LINKING_ERRORED',
|
||||||
|
'Linking has already failed for the provided module');
|
||||||
|
E('ERR_VM_MODULE_NOT_LINKED',
|
||||||
|
'Module must be linked before it can be instantiated');
|
||||||
|
E('ERR_VM_MODULE_NOT_MODULE', 'Provided module is not an instance of Module');
|
||||||
|
E('ERR_VM_MODULE_STATUS', 'Module status %s');
|
||||||
E('ERR_ZLIB_BINDING_CLOSED', 'zlib binding closed');
|
E('ERR_ZLIB_BINDING_CLOSED', 'zlib binding closed');
|
||||||
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed');
|
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed');
|
||||||
|
|
||||||
|
205
lib/internal/vm/Module.js
Normal file
205
lib/internal/vm/Module.js
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { emitExperimentalWarning } = require('internal/util');
|
||||||
|
const { URL } = require('internal/url');
|
||||||
|
const { kParsingContext, isContext } = process.binding('contextify');
|
||||||
|
const errors = require('internal/errors');
|
||||||
|
const {
|
||||||
|
getConstructorOf,
|
||||||
|
customInspectSymbol,
|
||||||
|
} = require('internal/util');
|
||||||
|
|
||||||
|
const {
|
||||||
|
ModuleWrap,
|
||||||
|
kUninstantiated,
|
||||||
|
kInstantiating,
|
||||||
|
kInstantiated,
|
||||||
|
kEvaluating,
|
||||||
|
kEvaluated,
|
||||||
|
kErrored,
|
||||||
|
} = internalBinding('module_wrap');
|
||||||
|
|
||||||
|
const STATUS_MAP = {
|
||||||
|
[kUninstantiated]: 'uninstantiated',
|
||||||
|
[kInstantiating]: 'instantiating',
|
||||||
|
[kInstantiated]: 'instantiated',
|
||||||
|
[kEvaluating]: 'evaluating',
|
||||||
|
[kEvaluated]: 'evaluated',
|
||||||
|
[kErrored]: 'errored',
|
||||||
|
};
|
||||||
|
|
||||||
|
let globalModuleId = 0;
|
||||||
|
const perContextModuleId = new WeakMap();
|
||||||
|
const wrapMap = new WeakMap();
|
||||||
|
const dependencyCacheMap = new WeakMap();
|
||||||
|
const linkingStatusMap = new WeakMap();
|
||||||
|
|
||||||
|
class Module {
|
||||||
|
constructor(src, options = {}) {
|
||||||
|
emitExperimentalWarning('vm.Module');
|
||||||
|
|
||||||
|
if (typeof src !== 'string')
|
||||||
|
throw new errors.TypeError(
|
||||||
|
'ERR_INVALID_ARG_TYPE', 'src', 'string', src);
|
||||||
|
if (typeof options !== 'object' || options === null)
|
||||||
|
throw new errors.TypeError(
|
||||||
|
'ERR_INVALID_ARG_TYPE', 'options', 'object', options);
|
||||||
|
|
||||||
|
let context;
|
||||||
|
if (options.context !== undefined) {
|
||||||
|
if (isContext(options.context)) {
|
||||||
|
context = options.context;
|
||||||
|
} else {
|
||||||
|
throw new errors.TypeError(
|
||||||
|
'ERR_INVALID_ARG_TYPE', 'options.context', 'vm.Context');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = options.url;
|
||||||
|
if (url !== undefined) {
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
throw new errors.TypeError(
|
||||||
|
'ERR_INVALID_ARG_TYPE', 'options.url', 'string', url);
|
||||||
|
}
|
||||||
|
url = new URL(url).href;
|
||||||
|
} else if (context === undefined) {
|
||||||
|
url = `vm:module(${globalModuleId++})`;
|
||||||
|
} else if (perContextModuleId.has(context)) {
|
||||||
|
const curId = perContextModuleId.get(context);
|
||||||
|
url = `vm:module(${curId})`;
|
||||||
|
perContextModuleId.set(context, curId + 1);
|
||||||
|
} else {
|
||||||
|
url = 'vm:module(0)';
|
||||||
|
perContextModuleId.set(context, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrap = new ModuleWrap(src, url, {
|
||||||
|
[kParsingContext]: context,
|
||||||
|
lineOffset: options.lineOffset,
|
||||||
|
columnOffset: options.columnOffset
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapMap.set(this, wrap);
|
||||||
|
linkingStatusMap.set(this, 'unlinked');
|
||||||
|
|
||||||
|
Object.defineProperties(this, {
|
||||||
|
url: { value: url, enumerable: true },
|
||||||
|
context: { value: context, enumerable: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get linkingStatus() {
|
||||||
|
return linkingStatusMap.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return STATUS_MAP[wrapMap.get(this).getStatus()];
|
||||||
|
}
|
||||||
|
|
||||||
|
get namespace() {
|
||||||
|
const wrap = wrapMap.get(this);
|
||||||
|
if (wrap.getStatus() < kInstantiated)
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_STATUS',
|
||||||
|
'must not be uninstantiated or instantiating');
|
||||||
|
return wrap.namespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
get dependencySpecifiers() {
|
||||||
|
let deps = dependencyCacheMap.get(this);
|
||||||
|
if (deps !== undefined)
|
||||||
|
return deps;
|
||||||
|
|
||||||
|
deps = wrapMap.get(this).getStaticDependencySpecifiers();
|
||||||
|
Object.freeze(deps);
|
||||||
|
dependencyCacheMap.set(this, deps);
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
get error() {
|
||||||
|
const wrap = wrapMap.get(this);
|
||||||
|
if (wrap.getStatus() !== kErrored)
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_STATUS', 'must be errored');
|
||||||
|
return wrap.getError();
|
||||||
|
}
|
||||||
|
|
||||||
|
async link(linker) {
|
||||||
|
if (typeof linker !== 'function')
|
||||||
|
throw new errors.TypeError(
|
||||||
|
'ERR_INVALID_ARG_TYPE', 'linker', 'function', linker);
|
||||||
|
if (linkingStatusMap.get(this) !== 'unlinked')
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_ALREADY_LINKED');
|
||||||
|
const wrap = wrapMap.get(this);
|
||||||
|
if (wrap.getStatus() !== kUninstantiated)
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_STATUS', 'must be uninstantiated');
|
||||||
|
linkingStatusMap.set(this, 'linking');
|
||||||
|
const promises = [];
|
||||||
|
wrap.link((specifier) => {
|
||||||
|
const p = (async () => {
|
||||||
|
const m = await linker(this, specifier);
|
||||||
|
if (!m || !wrapMap.has(m))
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_NOT_MODULE');
|
||||||
|
if (m.context !== this.context)
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_DIFFERENT_CONTEXT');
|
||||||
|
const childLinkingStatus = linkingStatusMap.get(m);
|
||||||
|
if (childLinkingStatus === 'errored')
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_LINKING_ERRORED');
|
||||||
|
if (childLinkingStatus === 'unlinked')
|
||||||
|
await m.link(linker);
|
||||||
|
return wrapMap.get(m);
|
||||||
|
})();
|
||||||
|
promises.push(p);
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await Promise.all(promises);
|
||||||
|
linkingStatusMap.set(this, 'linked');
|
||||||
|
} catch (err) {
|
||||||
|
linkingStatusMap.set(this, 'errored');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiate() {
|
||||||
|
const wrap = wrapMap.get(this);
|
||||||
|
const status = wrap.getStatus();
|
||||||
|
if (status === kInstantiating || status === kEvaluating)
|
||||||
|
throw new errors.Error(
|
||||||
|
'ERR_VM_MODULE_STATUS', 'must not be instantiating or evaluating');
|
||||||
|
if (linkingStatusMap.get(this) !== 'linked')
|
||||||
|
throw new errors.Error('ERR_VM_MODULE_NOT_LINKED');
|
||||||
|
wrap.instantiate();
|
||||||
|
}
|
||||||
|
|
||||||
|
async evaluate(options) {
|
||||||
|
const wrap = wrapMap.get(this);
|
||||||
|
const status = wrap.getStatus();
|
||||||
|
if (status !== kInstantiated &&
|
||||||
|
status !== kEvaluated &&
|
||||||
|
status !== kErrored) {
|
||||||
|
throw new errors.Error(
|
||||||
|
'ERR_VM_MODULE_STATUS',
|
||||||
|
'must be one of instantiated, evaluated, and errored');
|
||||||
|
}
|
||||||
|
const result = wrap.evaluate(options);
|
||||||
|
return { result, __proto__: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
[customInspectSymbol](depth, options) {
|
||||||
|
let ctor = getConstructorOf(this);
|
||||||
|
ctor = ctor === null ? Module : ctor;
|
||||||
|
|
||||||
|
if (typeof depth === 'number' && depth < 0)
|
||||||
|
return options.stylize(`[${ctor.name}]`, 'special');
|
||||||
|
|
||||||
|
const o = Object.create({ constructor: ctor });
|
||||||
|
o.status = this.status;
|
||||||
|
o.linkingStatus = this.linkingStatus;
|
||||||
|
o.url = this.url;
|
||||||
|
o.context = this.context;
|
||||||
|
return require('util').inspect(o, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Module
|
||||||
|
};
|
@ -192,5 +192,8 @@ module.exports = {
|
|||||||
runInContext,
|
runInContext,
|
||||||
runInNewContext,
|
runInNewContext,
|
||||||
runInThisContext,
|
runInThisContext,
|
||||||
isContext
|
isContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.binding('config').experimentalVMModules)
|
||||||
|
module.exports.Module = require('internal/vm/Module').Module;
|
||||||
|
1
node.gyp
1
node.gyp
@ -138,6 +138,7 @@
|
|||||||
'lib/internal/v8.js',
|
'lib/internal/v8.js',
|
||||||
'lib/internal/v8_prof_polyfill.js',
|
'lib/internal/v8_prof_polyfill.js',
|
||||||
'lib/internal/v8_prof_processor.js',
|
'lib/internal/v8_prof_processor.js',
|
||||||
|
'lib/internal/vm/Module.js',
|
||||||
'lib/internal/streams/lazy_transform.js',
|
'lib/internal/streams/lazy_transform.js',
|
||||||
'lib/internal/streams/async_iterator.js',
|
'lib/internal/streams/async_iterator.js',
|
||||||
'lib/internal/streams/BufferList.js',
|
'lib/internal/streams/BufferList.js',
|
||||||
|
@ -7,12 +7,15 @@
|
|||||||
#include "node_url.h"
|
#include "node_url.h"
|
||||||
#include "util-inl.h"
|
#include "util-inl.h"
|
||||||
#include "node_internals.h"
|
#include "node_internals.h"
|
||||||
|
#include "node_contextify.h"
|
||||||
|
#include "node_watchdog.h"
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
namespace loader {
|
namespace loader {
|
||||||
|
|
||||||
using node::url::URL;
|
using node::url::URL;
|
||||||
using node::url::URL_FLAGS_FAILED;
|
using node::url::URL_FLAGS_FAILED;
|
||||||
|
using v8::Array;
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
using v8::FunctionCallbackInfo;
|
using v8::FunctionCallbackInfo;
|
||||||
@ -58,6 +61,7 @@ ModuleWrap::~ModuleWrap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module_.Reset();
|
module_.Reset();
|
||||||
|
context_.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||||
@ -70,12 +74,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Length() != 2) {
|
|
||||||
env->ThrowError("constructor must have exactly 2 arguments "
|
|
||||||
"(string, string)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args[0]->IsString()) {
|
if (!args[0]->IsString()) {
|
||||||
env->ThrowError("first argument is not a string");
|
env->ThrowError("first argument is not a string");
|
||||||
return;
|
return;
|
||||||
@ -90,20 +88,39 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
Local<String> url = args[1].As<String>();
|
Local<String> url = args[1].As<String>();
|
||||||
|
|
||||||
|
Local<Object> that = args.This();
|
||||||
|
|
||||||
|
Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env);
|
||||||
|
TryCatch try_catch(isolate);
|
||||||
|
|
||||||
|
Local<Value> options = args[2];
|
||||||
|
MaybeLocal<Integer> line_offset = contextify::GetLineOffsetArg(env, options);
|
||||||
|
MaybeLocal<Integer> column_offset =
|
||||||
|
contextify::GetColumnOffsetArg(env, options);
|
||||||
|
MaybeLocal<Context> maybe_context = contextify::GetContextArg(env, options);
|
||||||
|
|
||||||
|
|
||||||
|
if (try_catch.HasCaught()) {
|
||||||
|
no_abort_scope.Close();
|
||||||
|
try_catch.ReThrow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Context> context = maybe_context.FromMaybe(that->CreationContext());
|
||||||
Local<Module> module;
|
Local<Module> module;
|
||||||
|
|
||||||
// compile
|
// compile
|
||||||
{
|
{
|
||||||
ScriptOrigin origin(url,
|
ScriptOrigin origin(url,
|
||||||
Integer::New(isolate, 0), // line offset
|
line_offset.ToLocalChecked(), // line offset
|
||||||
Integer::New(isolate, 0), // column offset
|
column_offset.ToLocalChecked(), // column offset
|
||||||
False(isolate), // is cross origin
|
False(isolate), // is cross origin
|
||||||
Local<Integer>(), // script id
|
Local<Integer>(), // script id
|
||||||
Local<Value>(), // source map URL
|
Local<Value>(), // source map URL
|
||||||
False(isolate), // is opaque (?)
|
False(isolate), // is opaque (?)
|
||||||
False(isolate), // is WASM
|
False(isolate), // is WASM
|
||||||
True(isolate)); // is ES6 module
|
True(isolate)); // is ES6 module
|
||||||
TryCatch try_catch(isolate);
|
Context::Scope context_scope(context);
|
||||||
ScriptCompiler::Source source(source_text, origin);
|
ScriptCompiler::Source source(source_text, origin);
|
||||||
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
|
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
|
||||||
CHECK(try_catch.HasCaught());
|
CHECK(try_catch.HasCaught());
|
||||||
@ -116,8 +133,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Local<Object> that = args.This();
|
|
||||||
Local<Context> context = that->CreationContext();
|
|
||||||
Local<String> url_str = FIXED_ONE_BYTE_STRING(isolate, "url");
|
Local<String> url_str = FIXED_ONE_BYTE_STRING(isolate, "url");
|
||||||
|
|
||||||
if (!that->Set(context, url_str, url).FromMaybe(false)) {
|
if (!that->Set(context, url_str, url).FromMaybe(false)) {
|
||||||
@ -125,6 +140,7 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModuleWrap* obj = new ModuleWrap(env, that, module, url);
|
ModuleWrap* obj = new ModuleWrap(env, that, module, url);
|
||||||
|
obj->context_.Reset(isolate, context);
|
||||||
|
|
||||||
env->module_map.emplace(module->GetIdentityHash(), obj);
|
env->module_map.emplace(module->GetIdentityHash(), obj);
|
||||||
Wrap(that, obj);
|
Wrap(that, obj);
|
||||||
@ -141,15 +157,19 @@ void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Local<Object> that = args.This();
|
||||||
|
|
||||||
|
ModuleWrap* obj;
|
||||||
|
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
|
||||||
|
|
||||||
|
if (obj->linked_)
|
||||||
|
return;
|
||||||
|
obj->linked_ = true;
|
||||||
|
|
||||||
Local<Function> resolver_arg = args[0].As<Function>();
|
Local<Function> resolver_arg = args[0].As<Function>();
|
||||||
|
|
||||||
Local<Object> that = args.This();
|
Local<Context> mod_context = obj->context_.Get(isolate);
|
||||||
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
|
Local<Module> module = obj->module_.Get(isolate);
|
||||||
CHECK_NE(obj, nullptr);
|
|
||||||
Local<Context> mod_context = that->CreationContext();
|
|
||||||
if (obj->linked_) return;
|
|
||||||
obj->linked_ = true;
|
|
||||||
Local<Module> module(obj->module_.Get(isolate));
|
|
||||||
|
|
||||||
// call the dependency resolve callbacks
|
// call the dependency resolve callbacks
|
||||||
for (int i = 0; i < module->GetModuleRequestsLength(); i++) {
|
for (int i = 0; i < module->GetModuleRequestsLength(); i++) {
|
||||||
@ -181,11 +201,9 @@ void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
|
|||||||
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
|
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
Isolate* isolate = args.GetIsolate();
|
Isolate* isolate = args.GetIsolate();
|
||||||
Local<Object> that = args.This();
|
ModuleWrap* obj;
|
||||||
Local<Context> context = that->CreationContext();
|
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||||
|
Local<Context> context = obj->context_.Get(isolate);
|
||||||
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
|
|
||||||
CHECK_NE(obj, nullptr);
|
|
||||||
Local<Module> module = obj->module_.Get(isolate);
|
Local<Module> module = obj->module_.Get(isolate);
|
||||||
TryCatch try_catch(isolate);
|
TryCatch try_catch(isolate);
|
||||||
Maybe<bool> ok =
|
Maybe<bool> ok =
|
||||||
@ -208,14 +226,60 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
|
void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
Isolate* isolate = args.GetIsolate();
|
Isolate* isolate = args.GetIsolate();
|
||||||
Local<Object> that = args.This();
|
ModuleWrap* obj;
|
||||||
Local<Context> context = that->CreationContext();
|
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||||
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
|
Local<Context> context = obj->context_.Get(isolate);
|
||||||
CHECK_NE(obj, nullptr);
|
Local<Module> module = obj->module_.Get(isolate);
|
||||||
MaybeLocal<Value> result = obj->module_.Get(isolate)->Evaluate(context);
|
|
||||||
|
|
||||||
if (result.IsEmpty()) {
|
Environment::ShouldNotAbortOnUncaughtScope no_abort_scope(env);
|
||||||
|
TryCatch try_catch(isolate);
|
||||||
|
Maybe<int64_t> maybe_timeout =
|
||||||
|
contextify::GetTimeoutArg(env, args[0]);
|
||||||
|
Maybe<bool> maybe_break_on_sigint =
|
||||||
|
contextify::GetBreakOnSigintArg(env, args[0]);
|
||||||
|
|
||||||
|
if (try_catch.HasCaught()) {
|
||||||
|
no_abort_scope.Close();
|
||||||
|
try_catch.ReThrow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t timeout = maybe_timeout.ToChecked();
|
||||||
|
bool break_on_sigint = maybe_break_on_sigint.ToChecked();
|
||||||
|
|
||||||
|
bool timed_out = false;
|
||||||
|
bool received_signal = false;
|
||||||
|
MaybeLocal<Value> result;
|
||||||
|
if (break_on_sigint && timeout != -1) {
|
||||||
|
Watchdog wd(isolate, timeout, &timed_out);
|
||||||
|
SigintWatchdog swd(isolate, &received_signal);
|
||||||
|
result = module->Evaluate(context);
|
||||||
|
} else if (break_on_sigint) {
|
||||||
|
SigintWatchdog swd(isolate, &received_signal);
|
||||||
|
result = module->Evaluate(context);
|
||||||
|
} else if (timeout != -1) {
|
||||||
|
Watchdog wd(isolate, timeout, &timed_out);
|
||||||
|
result = module->Evaluate(context);
|
||||||
|
} else {
|
||||||
|
result = module->Evaluate(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timed_out || received_signal) {
|
||||||
|
// It is possible that execution was terminated by another timeout in
|
||||||
|
// which this timeout is nested, so check whether one of the watchdogs
|
||||||
|
// from this invocation is responsible for termination.
|
||||||
|
if (timed_out) {
|
||||||
|
env->ThrowError("Script execution timed out.");
|
||||||
|
} else if (received_signal) {
|
||||||
|
env->ThrowError("Script execution interrupted.");
|
||||||
|
}
|
||||||
|
env->isolate()->CancelTerminateExecution();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try_catch.HasCaught()) {
|
||||||
|
try_catch.ReThrow();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,9 +289,8 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
|
|||||||
void ModuleWrap::Namespace(const FunctionCallbackInfo<Value>& args) {
|
void ModuleWrap::Namespace(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
Isolate* isolate = args.GetIsolate();
|
Isolate* isolate = args.GetIsolate();
|
||||||
Local<Object> that = args.This();
|
ModuleWrap* obj;
|
||||||
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
|
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||||
CHECK_NE(obj, nullptr);
|
|
||||||
|
|
||||||
Local<Module> module = obj->module_.Get(isolate);
|
Local<Module> module = obj->module_.Get(isolate);
|
||||||
|
|
||||||
@ -245,6 +308,44 @@ void ModuleWrap::Namespace(const FunctionCallbackInfo<Value>& args) {
|
|||||||
args.GetReturnValue().Set(result);
|
args.GetReturnValue().Set(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::GetStatus(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
ModuleWrap* obj;
|
||||||
|
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||||
|
|
||||||
|
Local<Module> module = obj->module_.Get(isolate);
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(module->GetStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::GetStaticDependencySpecifiers(
|
||||||
|
const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
ModuleWrap* obj;
|
||||||
|
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||||
|
|
||||||
|
Local<Module> module = obj->module_.Get(env->isolate());
|
||||||
|
|
||||||
|
int count = module->GetModuleRequestsLength();
|
||||||
|
|
||||||
|
Local<Array> specifiers = Array::New(env->isolate(), count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
specifiers->Set(env->context(), i, module->GetModuleRequest(i)).FromJust();
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(specifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
ModuleWrap* obj;
|
||||||
|
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||||
|
|
||||||
|
Local<Module> module = obj->module_.Get(isolate);
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(module->GetException());
|
||||||
|
}
|
||||||
|
|
||||||
MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
|
MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
|
||||||
Local<String> specifier,
|
Local<String> specifier,
|
||||||
Local<Module> referrer) {
|
Local<Module> referrer) {
|
||||||
@ -636,12 +737,29 @@ void ModuleWrap::Initialize(Local<Object> target,
|
|||||||
env->SetProtoMethod(tpl, "instantiate", Instantiate);
|
env->SetProtoMethod(tpl, "instantiate", Instantiate);
|
||||||
env->SetProtoMethod(tpl, "evaluate", Evaluate);
|
env->SetProtoMethod(tpl, "evaluate", Evaluate);
|
||||||
env->SetProtoMethod(tpl, "namespace", Namespace);
|
env->SetProtoMethod(tpl, "namespace", Namespace);
|
||||||
|
env->SetProtoMethod(tpl, "getStatus", GetStatus);
|
||||||
|
env->SetProtoMethod(tpl, "getError", GetError);
|
||||||
|
env->SetProtoMethod(tpl, "getStaticDependencySpecifiers",
|
||||||
|
GetStaticDependencySpecifiers);
|
||||||
|
|
||||||
target->Set(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), tpl->GetFunction());
|
target->Set(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), tpl->GetFunction());
|
||||||
env->SetMethod(target, "resolve", node::loader::ModuleWrap::Resolve);
|
env->SetMethod(target, "resolve", node::loader::ModuleWrap::Resolve);
|
||||||
env->SetMethod(target,
|
env->SetMethod(target,
|
||||||
"setImportModuleDynamicallyCallback",
|
"setImportModuleDynamicallyCallback",
|
||||||
node::loader::ModuleWrap::SetImportModuleDynamicallyCallback);
|
node::loader::ModuleWrap::SetImportModuleDynamicallyCallback);
|
||||||
|
|
||||||
|
#define V(name) \
|
||||||
|
target->Set(context, \
|
||||||
|
FIXED_ONE_BYTE_STRING(env->isolate(), #name), \
|
||||||
|
Integer::New(env->isolate(), Module::Status::name)) \
|
||||||
|
.FromJust()
|
||||||
|
V(kUninstantiated);
|
||||||
|
V(kInstantiating);
|
||||||
|
V(kInstantiated);
|
||||||
|
V(kEvaluating);
|
||||||
|
V(kEvaluated);
|
||||||
|
V(kErrored);
|
||||||
|
#undef V
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace loader
|
} // namespace loader
|
||||||
|
@ -36,8 +36,11 @@ class ModuleWrap : public BaseObject {
|
|||||||
static void Instantiate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Instantiate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void Evaluate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Evaluate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void Namespace(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Namespace(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void GetUrl(v8::Local<v8::String> property,
|
static void GetStatus(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
static void GetError(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void GetStaticDependencySpecifiers(
|
||||||
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
static void Resolve(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void Resolve(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
static void SetImportModuleDynamicallyCallback(
|
static void SetImportModuleDynamicallyCallback(
|
||||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
@ -50,6 +53,7 @@ class ModuleWrap : public BaseObject {
|
|||||||
v8::Persistent<v8::String> url_;
|
v8::Persistent<v8::String> url_;
|
||||||
bool linked_ = false;
|
bool linked_ = false;
|
||||||
std::unordered_map<std::string, v8::Persistent<v8::Promise>> resolve_cache_;
|
std::unordered_map<std::string, v8::Persistent<v8::Promise>> resolve_cache_;
|
||||||
|
v8::Persistent<v8::Context> context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace loader
|
} // namespace loader
|
||||||
|
10
src/node.cc
10
src/node.cc
@ -240,6 +240,11 @@ bool config_preserve_symlinks = false;
|
|||||||
// that is used by lib/module.js
|
// that is used by lib/module.js
|
||||||
bool config_experimental_modules = false;
|
bool config_experimental_modules = false;
|
||||||
|
|
||||||
|
// Set in node.cc by ParseArgs when --experimental-vm-modules is used.
|
||||||
|
// Used in node_config.cc to set a constant on process.binding('config')
|
||||||
|
// that is used by lib/vm.js
|
||||||
|
bool config_experimental_vm_modules = false;
|
||||||
|
|
||||||
// Set in node.cc by ParseArgs when --loader is used.
|
// Set in node.cc by ParseArgs when --loader is used.
|
||||||
// Used in node_config.cc to set a constant on process.binding('config')
|
// Used in node_config.cc to set a constant on process.binding('config')
|
||||||
// that is used by lib/internal/bootstrap_node.js
|
// that is used by lib/internal/bootstrap_node.js
|
||||||
@ -3424,6 +3429,8 @@ static void PrintHelp() {
|
|||||||
" --preserve-symlinks preserve symbolic links when resolving\n"
|
" --preserve-symlinks preserve symbolic links when resolving\n"
|
||||||
" --experimental-modules experimental ES Module support\n"
|
" --experimental-modules experimental ES Module support\n"
|
||||||
" and caching modules\n"
|
" and caching modules\n"
|
||||||
|
" --experimental-vm-modules experimental ES Module support\n"
|
||||||
|
" in vm module\n"
|
||||||
#endif
|
#endif
|
||||||
"\n"
|
"\n"
|
||||||
"Environment variables:\n"
|
"Environment variables:\n"
|
||||||
@ -3503,6 +3510,7 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env,
|
|||||||
"--napi-modules",
|
"--napi-modules",
|
||||||
"--expose-http2", // keep as a non-op through v9.x
|
"--expose-http2", // keep as a non-op through v9.x
|
||||||
"--experimental-modules",
|
"--experimental-modules",
|
||||||
|
"--experimental-vm-modules",
|
||||||
"--loader",
|
"--loader",
|
||||||
"--trace-warnings",
|
"--trace-warnings",
|
||||||
"--redirect-warnings",
|
"--redirect-warnings",
|
||||||
@ -3670,6 +3678,8 @@ static void ParseArgs(int* argc,
|
|||||||
config_preserve_symlinks = true;
|
config_preserve_symlinks = true;
|
||||||
} else if (strcmp(arg, "--experimental-modules") == 0) {
|
} else if (strcmp(arg, "--experimental-modules") == 0) {
|
||||||
config_experimental_modules = true;
|
config_experimental_modules = true;
|
||||||
|
} else if (strcmp(arg, "--experimental-vm-modules") == 0) {
|
||||||
|
config_experimental_vm_modules = true;
|
||||||
} else if (strcmp(arg, "--loader") == 0) {
|
} else if (strcmp(arg, "--loader") == 0) {
|
||||||
const char* module = argv[index + 1];
|
const char* module = argv[index + 1];
|
||||||
if (!config_experimental_modules) {
|
if (!config_experimental_modules) {
|
||||||
|
@ -82,6 +82,9 @@ static void InitConfig(Local<Object> target,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config_experimental_vm_modules)
|
||||||
|
READONLY_BOOLEAN_PROPERTY("experimentalVMModules");
|
||||||
|
|
||||||
if (config_pending_deprecation)
|
if (config_pending_deprecation)
|
||||||
READONLY_BOOLEAN_PROPERTY("pendingDeprecation");
|
READONLY_BOOLEAN_PROPERTY("pendingDeprecation");
|
||||||
|
|
||||||
|
@ -172,6 +172,11 @@ extern bool config_preserve_symlinks;
|
|||||||
// that is used by lib/module.js
|
// that is used by lib/module.js
|
||||||
extern bool config_experimental_modules;
|
extern bool config_experimental_modules;
|
||||||
|
|
||||||
|
// Set in node.cc by ParseArgs when --experimental-vm-modules is used.
|
||||||
|
// Used in node_config.cc to set a constant on process.binding('config')
|
||||||
|
// that is used by lib/vm.js
|
||||||
|
extern bool config_experimental_vm_modules;
|
||||||
|
|
||||||
// Set in node.cc by ParseArgs when --loader is used.
|
// Set in node.cc by ParseArgs when --loader is used.
|
||||||
// Used in node_config.cc to set a constant on process.binding('config')
|
// Used in node_config.cc to set a constant on process.binding('config')
|
||||||
// that is used by lib/internal/bootstrap_node.js
|
// that is used by lib/internal/bootstrap_node.js
|
||||||
|
54
test/parallel/test-vm-module-basic.js
Normal file
54
test/parallel/test-vm-module-basic.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Flags: --experimental-vm-modules
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const { Module, createContext } = require('vm');
|
||||||
|
|
||||||
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
|
(async function test1() {
|
||||||
|
const context = createContext({
|
||||||
|
foo: 'bar',
|
||||||
|
baz: undefined,
|
||||||
|
typeofProcess: undefined,
|
||||||
|
});
|
||||||
|
const m = new Module(
|
||||||
|
'baz = foo; typeofProcess = typeof process; typeof Object;',
|
||||||
|
{ context }
|
||||||
|
);
|
||||||
|
assert.strictEqual(m.status, 'uninstantiated');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
assert.strictEqual(m.status, 'instantiated');
|
||||||
|
const result = await m.evaluate();
|
||||||
|
assert.strictEqual(m.status, 'evaluated');
|
||||||
|
assert.strictEqual(Object.getPrototypeOf(result), null);
|
||||||
|
assert.deepStrictEqual(context, {
|
||||||
|
foo: 'bar',
|
||||||
|
baz: 'bar',
|
||||||
|
typeofProcess: 'undefined'
|
||||||
|
});
|
||||||
|
assert.strictEqual(result.result, 'function');
|
||||||
|
}());
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const m = new Module(
|
||||||
|
'global.vmResult = "foo"; Object.prototype.toString.call(process);'
|
||||||
|
);
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
const { result } = await m.evaluate();
|
||||||
|
assert.strictEqual(global.vmResult, 'foo');
|
||||||
|
assert.strictEqual(result, '[object process]');
|
||||||
|
delete global.vmResult;
|
||||||
|
})();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const m = new Module('while (true) {}');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
await m.evaluate({ timeout: 500 })
|
||||||
|
.then(() => assert(false), () => {});
|
||||||
|
})();
|
27
test/parallel/test-vm-module-dynamic-import.js
Normal file
27
test/parallel/test-vm-module-dynamic-import.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Flags: --experimental-vm-modules --experimental-modules --harmony-dynamic-import
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { Module, createContext } = require('vm');
|
||||||
|
|
||||||
|
const finished = common.mustCall();
|
||||||
|
|
||||||
|
(async function() {
|
||||||
|
const m = new Module('import("foo")', { context: createContext() });
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
const { result } = await m.evaluate();
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await result;
|
||||||
|
} catch (err) {
|
||||||
|
threw = true;
|
||||||
|
assert.strictEqual(err.message, 'import() called outside of main context');
|
||||||
|
}
|
||||||
|
assert(threw);
|
||||||
|
finished();
|
||||||
|
}());
|
264
test/parallel/test-vm-module-errors.js
Normal file
264
test/parallel/test-vm-module-errors.js
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Flags: --experimental-vm-modules
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const { Module, createContext } = require('vm');
|
||||||
|
|
||||||
|
async function expectsRejection(fn, settings) {
|
||||||
|
const validateError = common.expectsError(settings);
|
||||||
|
// Retain async context.
|
||||||
|
const storedError = new Error('Thrown from:');
|
||||||
|
try {
|
||||||
|
await fn();
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
validateError(err);
|
||||||
|
} catch (validationError) {
|
||||||
|
console.error(validationError);
|
||||||
|
console.error('Original error:');
|
||||||
|
console.error(err);
|
||||||
|
throw storedError;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert.fail('Missing expected exception');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEmptyLinkedModule() {
|
||||||
|
const m = new Module('');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkArgType() {
|
||||||
|
common.expectsError(() => {
|
||||||
|
new Module();
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
type: TypeError
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const invalidOptions of [
|
||||||
|
0, 1, null, true, 'str', () => {}, Symbol.iterator
|
||||||
|
]) {
|
||||||
|
common.expectsError(() => {
|
||||||
|
new Module('', invalidOptions);
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
type: TypeError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const invalidLinker of [
|
||||||
|
0, 1, undefined, null, true, 'str', {}, Symbol.iterator
|
||||||
|
]) {
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = new Module('');
|
||||||
|
await m.link(invalidLinker);
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
type: TypeError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check methods/properties can only be used under a specific state.
|
||||||
|
async function checkModuleState() {
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = new Module('');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
assert.strictEqual(m.linkingStatus, 'linked');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_ALREADY_LINKED'
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = new Module('');
|
||||||
|
m.link(common.mustNotCall());
|
||||||
|
assert.strictEqual(m.linkingStatus, 'linking');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_ALREADY_LINKED'
|
||||||
|
});
|
||||||
|
|
||||||
|
common.expectsError(() => {
|
||||||
|
const m = new Module('');
|
||||||
|
m.instantiate();
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_NOT_LINKED'
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = new Module('import "foo";');
|
||||||
|
try {
|
||||||
|
await m.link(common.mustCall(() => ({})));
|
||||||
|
} catch (err) {
|
||||||
|
assert.strictEqual(m.linkingStatus, 'errored');
|
||||||
|
m.instantiate();
|
||||||
|
}
|
||||||
|
assert.fail('Unreachable');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_NOT_LINKED'
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const m = new Module('import "foo";');
|
||||||
|
await m.link(common.mustCall(async (module, specifier) => {
|
||||||
|
assert.strictEqual(module, m);
|
||||||
|
assert.strictEqual(specifier, 'foo');
|
||||||
|
assert.strictEqual(m.linkingStatus, 'linking');
|
||||||
|
common.expectsError(() => {
|
||||||
|
m.instantiate();
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_NOT_LINKED'
|
||||||
|
});
|
||||||
|
return new Module('');
|
||||||
|
}));
|
||||||
|
m.instantiate();
|
||||||
|
await m.evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = new Module('');
|
||||||
|
await m.evaluate();
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_STATUS',
|
||||||
|
message: 'Module status must be one of instantiated, evaluated, and errored'
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = await createEmptyLinkedModule();
|
||||||
|
await m.evaluate();
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_STATUS',
|
||||||
|
message: 'Module status must be one of instantiated, evaluated, and errored'
|
||||||
|
});
|
||||||
|
|
||||||
|
common.expectsError(() => {
|
||||||
|
const m = new Module('');
|
||||||
|
m.error;
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_STATUS',
|
||||||
|
message: 'Module status must be errored'
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = await createEmptyLinkedModule();
|
||||||
|
m.instantiate();
|
||||||
|
await m.evaluate();
|
||||||
|
m.error;
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_STATUS',
|
||||||
|
message: 'Module status must be errored'
|
||||||
|
});
|
||||||
|
|
||||||
|
common.expectsError(() => {
|
||||||
|
const m = new Module('');
|
||||||
|
m.namespace;
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_STATUS',
|
||||||
|
message: 'Module status must not be uninstantiated or instantiating'
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = await createEmptyLinkedModule();
|
||||||
|
m.namespace;
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_STATUS',
|
||||||
|
message: 'Module status must not be uninstantiated or instantiating'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check link() fails when the returned module is not valid.
|
||||||
|
async function checkLinking() {
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const m = new Module('import "foo";');
|
||||||
|
try {
|
||||||
|
await m.link(common.mustCall(() => ({})));
|
||||||
|
} catch (err) {
|
||||||
|
assert.strictEqual(m.linkingStatus, 'errored');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
assert.fail('Unreachable');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_NOT_MODULE'
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const c = createContext({ a: 1 });
|
||||||
|
const foo = new Module('', { context: c });
|
||||||
|
await foo.link(common.mustNotCall());
|
||||||
|
const bar = new Module('import "foo";');
|
||||||
|
try {
|
||||||
|
await bar.link(common.mustCall(() => foo));
|
||||||
|
} catch (err) {
|
||||||
|
assert.strictEqual(bar.linkingStatus, 'errored');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
assert.fail('Unreachable');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT'
|
||||||
|
});
|
||||||
|
|
||||||
|
await expectsRejection(async () => {
|
||||||
|
const erroredModule = new Module('import "foo";');
|
||||||
|
try {
|
||||||
|
await erroredModule.link(common.mustCall(() => ({})));
|
||||||
|
} catch (err) {
|
||||||
|
// ignored
|
||||||
|
} finally {
|
||||||
|
assert.strictEqual(erroredModule.linkingStatus, 'errored');
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootModule = new Module('import "errored";');
|
||||||
|
await rootModule.link(common.mustCall(() => erroredModule));
|
||||||
|
}, {
|
||||||
|
code: 'ERR_VM_MODULE_LINKING_ERRORED'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the JavaScript engine deals with exceptions correctly
|
||||||
|
async function checkExecution() {
|
||||||
|
await (async () => {
|
||||||
|
const m = new Module('import { nonexistent } from "module";');
|
||||||
|
await m.link(common.mustCall(() => new Module('')));
|
||||||
|
|
||||||
|
// There is no code for this exception since it is thrown by the JavaScript
|
||||||
|
// engine.
|
||||||
|
assert.throws(() => {
|
||||||
|
m.instantiate();
|
||||||
|
}, SyntaxError);
|
||||||
|
})();
|
||||||
|
|
||||||
|
await (async () => {
|
||||||
|
const m = new Module('throw new Error();');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
const evaluatePromise = m.evaluate();
|
||||||
|
await evaluatePromise.catch(() => {});
|
||||||
|
assert.strictEqual(m.status, 'errored');
|
||||||
|
try {
|
||||||
|
await evaluatePromise;
|
||||||
|
} catch (err) {
|
||||||
|
assert.strictEqual(m.error, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert.fail('Missing expected exception');
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
const finished = common.mustCall();
|
||||||
|
|
||||||
|
(async function main() {
|
||||||
|
await checkArgType();
|
||||||
|
await checkModuleState();
|
||||||
|
await checkLinking();
|
||||||
|
await checkExecution();
|
||||||
|
finished();
|
||||||
|
})();
|
135
test/parallel/test-vm-module-link.js
Normal file
135
test/parallel/test-vm-module-link.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Flags: --experimental-vm-modules
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const { URL } = require('url');
|
||||||
|
|
||||||
|
const { Module } = require('vm');
|
||||||
|
|
||||||
|
async function simple() {
|
||||||
|
const foo = new Module('export default 5;');
|
||||||
|
await foo.link(common.mustNotCall());
|
||||||
|
|
||||||
|
const bar = new Module('import five from "foo"; five');
|
||||||
|
|
||||||
|
assert.deepStrictEqual(bar.dependencySpecifiers, ['foo']);
|
||||||
|
|
||||||
|
await bar.link(common.mustCall((module, specifier) => {
|
||||||
|
assert.strictEqual(module, bar);
|
||||||
|
assert.strictEqual(specifier, 'foo');
|
||||||
|
return foo;
|
||||||
|
}));
|
||||||
|
|
||||||
|
bar.instantiate();
|
||||||
|
|
||||||
|
assert.strictEqual((await bar.evaluate()).result, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function depth() {
|
||||||
|
const foo = new Module('export default 5');
|
||||||
|
await foo.link(common.mustNotCall());
|
||||||
|
|
||||||
|
async function getProxy(parentName, parentModule) {
|
||||||
|
const mod = new Module(`
|
||||||
|
import ${parentName} from '${parentName}';
|
||||||
|
export default ${parentName};
|
||||||
|
`);
|
||||||
|
await mod.link(common.mustCall((module, specifier) => {
|
||||||
|
assert.strictEqual(module, mod);
|
||||||
|
assert.strictEqual(specifier, parentName);
|
||||||
|
return parentModule;
|
||||||
|
}));
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bar = await getProxy('foo', foo);
|
||||||
|
const baz = await getProxy('bar', bar);
|
||||||
|
const barz = await getProxy('baz', baz);
|
||||||
|
|
||||||
|
barz.instantiate();
|
||||||
|
await barz.evaluate();
|
||||||
|
|
||||||
|
assert.strictEqual(barz.namespace.default, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function circular() {
|
||||||
|
const foo = new Module(`
|
||||||
|
import getFoo from 'bar';
|
||||||
|
export let foo = 42;
|
||||||
|
export default getFoo();
|
||||||
|
`);
|
||||||
|
const bar = new Module(`
|
||||||
|
import { foo } from 'foo';
|
||||||
|
export default function getFoo() {
|
||||||
|
return foo;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
await foo.link(common.mustCall(async (fooModule, fooSpecifier) => {
|
||||||
|
assert.strictEqual(fooModule, foo);
|
||||||
|
assert.strictEqual(fooSpecifier, 'bar');
|
||||||
|
await bar.link(common.mustCall((barModule, barSpecifier) => {
|
||||||
|
assert.strictEqual(barModule, bar);
|
||||||
|
assert.strictEqual(barSpecifier, 'foo');
|
||||||
|
assert.strictEqual(foo.linkingStatus, 'linking');
|
||||||
|
return foo;
|
||||||
|
}));
|
||||||
|
assert.strictEqual(bar.linkingStatus, 'linked');
|
||||||
|
return bar;
|
||||||
|
}));
|
||||||
|
|
||||||
|
foo.instantiate();
|
||||||
|
await foo.evaluate();
|
||||||
|
assert.strictEqual(foo.namespace.default, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function circular2() {
|
||||||
|
const sourceMap = {
|
||||||
|
root: `
|
||||||
|
import * as a from './a.mjs';
|
||||||
|
import * as b from './b.mjs';
|
||||||
|
if (!('fromA' in a))
|
||||||
|
throw new Error();
|
||||||
|
if (!('fromB' in a))
|
||||||
|
throw new Error();
|
||||||
|
if (!('fromA' in b))
|
||||||
|
throw new Error();
|
||||||
|
if (!('fromB' in b))
|
||||||
|
throw new Error();
|
||||||
|
`,
|
||||||
|
'./a.mjs': `
|
||||||
|
export * from './b.mjs';
|
||||||
|
export var fromA;
|
||||||
|
`,
|
||||||
|
'./b.mjs': `
|
||||||
|
export * from './a.mjs';
|
||||||
|
export var fromB;
|
||||||
|
`
|
||||||
|
};
|
||||||
|
const moduleMap = new Map();
|
||||||
|
const rootModule = new Module(sourceMap.root, { url: 'vm:root' });
|
||||||
|
async function link(referencingModule, specifier) {
|
||||||
|
if (moduleMap.has(specifier)) {
|
||||||
|
return moduleMap.get(specifier);
|
||||||
|
}
|
||||||
|
const mod = new Module(sourceMap[specifier], { url: new URL(specifier, 'file:///').href });
|
||||||
|
moduleMap.set(specifier, mod);
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
await rootModule.link(link);
|
||||||
|
rootModule.instantiate();
|
||||||
|
await rootModule.evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const finished = common.mustCall();
|
||||||
|
|
||||||
|
(async function main() {
|
||||||
|
await simple();
|
||||||
|
await depth();
|
||||||
|
await circular();
|
||||||
|
await circular2();
|
||||||
|
finished();
|
||||||
|
})();
|
49
test/parallel/test-vm-module-reevaluate.js
Normal file
49
test/parallel/test-vm-module-reevaluate.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Flags: --experimental-vm-modules
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const { Module } = require('vm');
|
||||||
|
|
||||||
|
const finished = common.mustCall();
|
||||||
|
|
||||||
|
(async function main() {
|
||||||
|
{
|
||||||
|
const m = new Module('1');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
assert.strictEqual((await m.evaluate()).result, 1);
|
||||||
|
assert.strictEqual((await m.evaluate()).result, undefined);
|
||||||
|
assert.strictEqual((await m.evaluate()).result, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const m = new Module('throw new Error()');
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
await m.evaluate();
|
||||||
|
} catch (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
assert(threw);
|
||||||
|
|
||||||
|
threw = false;
|
||||||
|
try {
|
||||||
|
await m.evaluate();
|
||||||
|
} catch (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
assert(threw);
|
||||||
|
}
|
||||||
|
|
||||||
|
finished();
|
||||||
|
})();
|
Loading…
x
Reference in New Issue
Block a user