Skip to content

Commit

Permalink
vm: reject in importModuleDynamically without --experimental-vm-modules
Browse files Browse the repository at this point in the history
Users cannot access any API that can be used to return a module or
module namespace in this callback without --experimental-vm-modules
anyway, so this would eventually lead to a rejection. This patch
rejects in this case with our own error message and use a constant
host-defined option for the rejection, so that scripts with the
same source can still be compiled using the compilation cache
if no `import()` is actually called in the script.

PR-URL: nodejs#50137
Refs: nodejs#35375
Reviewed-By: Geoffrey Booth <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: Antoine du Hamel <[email protected]>
  • Loading branch information
joyeecheung authored and alexfernandez committed Nov 1, 2023
1 parent 84a4f48 commit b783e26
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 7 deletions.
6 changes: 6 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2981,6 +2981,12 @@ An attempt was made to use something that was already closed.
While using the Performance Timing API (`perf_hooks`), no valid performance
entry types are found.

<a id="ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG"></a>

### `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`

A dynamic import callback was invoked without `--experimental-vm-modules`.

<a id="ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING"></a>

### `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`
Expand Down
24 changes: 19 additions & 5 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ changes:
when `import()` is called. If this option is not specified, calls to
`import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][].
This option is part of the experimental modules API. We do not recommend
using it in a production environment.
using it in a production environment. If `--experimental-vm-modules` isn't
set, this callback will be ignored and calls to `import()` will reject with
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`][].
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `importAssertions` {Object} The `"assert"` value passed to the
Expand Down Expand Up @@ -760,6 +762,9 @@ changes:
* `importModuleDynamically` {Function} Called during evaluation of this module
when `import()` is called. If this option is not specified, calls to
`import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][].
If `--experimental-vm-modules` isn't set, this callback will be ignored
and calls to `import()` will reject with
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`][].
* `specifier` {string} specifier passed to `import()`
* `module` {vm.Module}
* `importAssertions` {Object} The `"assert"` value passed to the
Expand Down Expand Up @@ -1018,7 +1023,9 @@ changes:
when `import()` is called. If this option is not specified, calls to
`import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][].
This option is part of the experimental modules API, and should not be
considered stable.
considered stable. If `--experimental-vm-modules` isn't
set, this callback will be ignored and calls to `import()` will reject with
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`][].
* `specifier` {string} specifier passed to `import()`
* `function` {Function}
* `importAssertions` {Object} The `"assert"` value passed to the
Expand Down Expand Up @@ -1242,7 +1249,9 @@ changes:
when `import()` is called. If this option is not specified, calls to
`import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][].
This option is part of the experimental modules API. We do not recommend
using it in a production environment.
using it in a production environment. If `--experimental-vm-modules` isn't
set, this callback will be ignored and calls to `import()` will reject with
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`][].
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `importAssertions` {Object} The `"assert"` value passed to the
Expand Down Expand Up @@ -1341,7 +1350,9 @@ changes:
when `import()` is called. If this option is not specified, calls to
`import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][].
This option is part of the experimental modules API. We do not recommend
using it in a production environment.
using it in a production environment. If `--experimental-vm-modules` isn't
set, this callback will be ignored and calls to `import()` will reject with
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`][].
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `importAssertions` {Object} The `"assert"` value passed to the
Expand Down Expand Up @@ -1421,7 +1432,9 @@ changes:
when `import()` is called. If this option is not specified, calls to
`import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][].
This option is part of the experimental modules API. We do not recommend
using it in a production environment.
using it in a production environment. If `--experimental-vm-modules` isn't
set, this callback will be ignored and calls to `import()` will reject with
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`][].
* `specifier` {string} specifier passed to `import()`
* `script` {vm.Script}
* `importAssertions` {Object} The `"assert"` value passed to the
Expand Down Expand Up @@ -1585,6 +1598,7 @@ are not controllable through the timeout either.
[Source Text Module Record]: https://tc39.es/ecma262/#sec-source-text-module-records
[Synthetic Module Record]: https://heycam.github.io/webidl/#synthetic-module-records
[V8 Embedder's Guide]: https://v8.dev/docs/embed#contexts
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`]: errors.md#err_vm_dynamic_import_callback_missing_flag
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.md#err_vm_dynamic_import_callback_missing
[`ERR_VM_MODULE_STATUS`]: errors.md#err_vm_module_status
[`Error`]: errors.md#class-error
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1821,6 +1821,9 @@ E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
'At least one valid performance entry type is required', Error);
E('ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING',
'A dynamic import callback was not specified.', TypeError);
E('ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG',
'A dynamic import callback was invoked without --experimental-vm-modules',
TypeError);
E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked', Error);
E('ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA',
'Cached data cannot be created for a module which has been evaluated', Error);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/modules/esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ const {
} = internalBinding('util');
const {
default_host_defined_options,
vm_dynamic_import_missing_flag,
} = internalBinding('symbols');

const {
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG,
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
ERR_INVALID_ARG_VALUE,
} = require('internal/errors').codes;
Expand Down Expand Up @@ -132,7 +134,8 @@ const moduleRegistries = new SafeWeakMap();
*/
function registerModule(referrer, registry) {
const idSymbol = referrer[host_defined_option_symbol];
if (idSymbol === default_host_defined_options) {
if (idSymbol === default_host_defined_options ||
idSymbol === vm_dynamic_import_missing_flag) {
// The referrer is compiled without custom callbacks, so there is
// no registry to hold on to. We'll throw
// ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING when a callback is
Expand Down Expand Up @@ -173,6 +176,9 @@ async function importModuleDynamicallyCallback(symbol, specifier, attributes) {
return importModuleDynamically(specifier, callbackReferrer, attributes);
}
}
if (symbol === vm_dynamic_import_missing_flag) {
throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG();
}
throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
}

Expand Down
16 changes: 16 additions & 0 deletions lib/internal/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ const {
} = ContextifyScript.prototype;
const {
default_host_defined_options,
vm_dynamic_import_missing_flag,
} = internalBinding('symbols');
const {
validateFunction,
validateObject,
kValidateObjectAllowArray,
} = require('internal/validators');

const {
getOptionValue,
} = require('internal/options');


function isContext(object) {
validateObject(object, 'object', kValidateObjectAllowArray);

Expand All @@ -41,6 +47,16 @@ function getHostDefinedOptionId(importModuleDynamically, filename) {
// compilation cache can be hit.
return default_host_defined_options;
}
// We should've thrown here immediately when we introduced
// --experimental-vm-modules and importModuleDynamically, but since
// users are already using this callback to throw a similar error,
// we also defer the error to the time when an actual import() is called
// to avoid breaking them. To ensure that the isolate compilation
// cache can still be hit, use a constant sentinel symbol here.
if (!getOptionValue('--experimental-vm-modules')) {
return vm_dynamic_import_missing_flag;
}

return Symbol(filename);
}

Expand Down
3 changes: 2 additions & 1 deletion src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
V(owner_symbol, "owner_symbol") \
V(onpskexchange_symbol, "onpskexchange") \
V(resource_symbol, "resource_symbol") \
V(trigger_async_id_symbol, "trigger_async_id_symbol")
V(trigger_async_id_symbol, "trigger_async_id_symbol") \
V(vm_dynamic_import_missing_flag, "vm_dynamic_import_missing_flag")

// Strings are per-isolate primitives but Environment proxies them
// for the sake of convenience. Strings should be ASCII-only.
Expand Down
28 changes: 28 additions & 0 deletions test/parallel/test-vm-dynamic-import-callback-missing-flag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

const common = require('../common');
const { Script, compileFunction } = require('vm');
const assert = require('assert');

assert(
!process.execArgv.includes('--experimental-vm-modules'),
'This test must be run without --experimental-vm-modules');

assert.rejects(async () => {
const script = new Script('import("fs")', {
importModuleDynamically: common.mustNotCall(),
});
const imported = script.runInThisContext();
await imported;
}, {
code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG'
}).then(common.mustCall());

assert.rejects(async () => {
const imported = compileFunction('return import("fs")', [], {
importModuleDynamically: common.mustNotCall(),
})();
await imported;
}, {
code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG'
}).then(common.mustCall());

0 comments on commit b783e26

Please sign in to comment.