Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vm: reject in importModuleDynamically without --experimental-vm-modules #50137

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
[`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
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
[`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
73 changes: 40 additions & 33 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const {
SafeMap,
SafeWeakMap,
String,
Symbol,
StringPrototypeCharAt,
StringPrototypeCharCodeAt,
StringPrototypeEndsWith,
Expand Down Expand Up @@ -84,7 +85,12 @@ const {
setOwnProperty,
getLazy,
} = require('internal/util');
const { internalCompileFunction } = require('internal/vm');
const {
internalCompileFunction,
makeContextifyScript,
runScriptInThisContext,
} = require('internal/vm');

const assert = require('internal/assert');
const fs = require('fs');
const path = require('path');
Expand Down Expand Up @@ -1240,7 +1246,6 @@ Module.prototype.require = function(id) {
let resolvedArgv;
let hasPausedEntry = false;
/** @type {import('vm').Script} */
let Script;

/**
* Wraps the given content in a script and runs it in a new context.
Expand All @@ -1250,47 +1255,49 @@ let Script;
* @param {object} codeCache The SEA code cache
*/
function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
const hostDefinedOptionId = Symbol(`cjs:${filename}`);
async function importModuleDynamically(specifier, _, importAttributes) {
const cascadedLoader = getCascadedLoader();
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
importAttributes);
}
if (patched) {
const wrapper = Module.wrap(content);
if (Script === undefined) {
({ Script } = require('vm'));
}
const script = new Script(wrapper, {
filename,
lineOffset: 0,
importModuleDynamically: async (specifier, _, importAttributes) => {
const cascadedLoader = getCascadedLoader();
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
importAttributes);
},
});
const wrapped = Module.wrap(content);
const script = makeContextifyScript(
wrapped, // code
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
filename, // filename
0, // lineOffset
0, // columnOffset
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
hostDefinedOptionId, // hostDefinedOptionId
importModuleDynamically, // importModuleDynamically
);

// Cache the source map for the module if present.
if (script.sourceMapURL) {
maybeCacheSourceMap(filename, content, this, false, undefined, script.sourceMapURL);
}

return script.runInThisContext({
displayErrors: true,
});
return runScriptInThisContext(script, true, false);
}

const params = [ 'exports', 'require', 'module', '__filename', '__dirname' ];
try {
const result = internalCompileFunction(content, [
'exports',
'require',
'module',
'__filename',
'__dirname',
], {
filename,
cachedData: codeCache,
importModuleDynamically(specifier, _, importAttributes) {
const cascadedLoader = getCascadedLoader();
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
importAttributes);
},
});
const result = internalCompileFunction(
content, // code,
filename, // filename
0, // lineOffset
0, // columnOffset,
codeCache, // cachedData
false, // produceCachedData
undefined, // parsingContext
undefined, // contextExtensions
params, // params
hostDefinedOptionId, // hostDefinedOptionId
importModuleDynamically, // importModuleDynamically
);

// The code cache is used for SEAs only.
if (codeCache &&
Expand Down
35 changes: 23 additions & 12 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const {
StringPrototypeReplaceAll,
StringPrototypeSlice,
StringPrototypeStartsWith,
Symbol,
SyntaxErrorPrototype,
globalThis: { WebAssembly },
} = primordials;
Expand Down Expand Up @@ -192,19 +193,29 @@ function enrichCJSError(err, content, filename) {
*/
function loadCJSModule(module, source, url, filename) {
let compiledWrapper;
async function importModuleDynamically(specifier, _, importAttributes) {
return asyncESM.esmLoader.import(specifier, url, importAttributes);
}
try {
compiledWrapper = internalCompileFunction(source, [
'exports',
'require',
'module',
'__filename',
'__dirname',
], {
filename,
importModuleDynamically(specifier, _, importAttributes) {
return asyncESM.esmLoader.import(specifier, url, importAttributes);
},
}).function;
compiledWrapper = internalCompileFunction(
source, // code,
filename, // filename
0, // lineOffset
0, // columnOffset,
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
undefined, // contextExtensions
[ // params
'exports',
'require',
'module',
'__filename',
'__dirname',
],
Symbol(`cjs:${filename}`), // hostDefinedOptionsId
importModuleDynamically, // importModuleDynamically
).function;
} catch (err) {
enrichCJSError(err, source, url);
throw err;
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
35 changes: 23 additions & 12 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
Symbol,
RegExpPrototypeExec,
globalThis,
} = primordials;
Expand All @@ -25,7 +26,9 @@ const {
emitAfter,
popAsyncContext,
} = require('internal/async_hooks');

const {
makeContextifyScript, runScriptInThisContext,
} = require('internal/vm');
// shouldAbortOnUncaughtToggle is a typed array for faster
// communication with JS.
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
Expand Down Expand Up @@ -53,7 +56,6 @@ function evalModule(source, print) {

function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
const CJSModule = require('internal/modules/cjs/loader').Module;
const { kVmBreakFirstLineSymbol } = require('internal/util');
const { pathToFileURL } = require('internal/url');

const cwd = tryGetCwd();
Expand All @@ -79,16 +81,25 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
`;
globalThis.__filename = name;
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
const result = module._compile(script, `${name}-wrapper`)(() =>
require('vm').runInThisContext(body, {
filename: name,
displayErrors: true,
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
importModuleDynamically(specifier, _, importAttributes) {
const loader = asyncESM.esmLoader;
return loader.import(specifier, baseUrl, importAttributes);
},
}));
const result = module._compile(script, `${name}-wrapper`)(() => {
const hostDefinedOptionId = Symbol(name);
async function importModuleDynamically(specifier, _, importAttributes) {
const loader = asyncESM.esmLoader;
return loader.import(specifier, baseUrl, importAttributes);
}
const script = makeContextifyScript(
body, // code
name, // filename,
0, // lineOffset
0, // columnOffset,
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
hostDefinedOptionId, // hostDefinedOptionId
importModuleDynamically, // importModuleDynamically
);
return runScriptInThisContext(script, true, !!breakFirstLine);
});
if (print) {
const { log } = require('internal/console/global');
log(result);
Expand Down
Loading