Skip to content

Commit

Permalink
esm: fix loader hooks accepting too many arguments
Browse files Browse the repository at this point in the history
PR-URL: #44109
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
  • Loading branch information
JakobJingleheimer authored and targos committed Aug 6, 2022
1 parent e6db809 commit ae157d9
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 15 deletions.
22 changes: 7 additions & 15 deletions lib/internal/modules/esm/loader.js
Expand Up @@ -15,7 +15,6 @@ const {
ObjectDefineProperty,
ObjectSetPrototypeOf,
PromiseAll,
ReflectApply,
RegExpPrototypeExec,
SafeArrayIterator,
SafeWeakMap,
Expand Down Expand Up @@ -148,29 +147,22 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
}

return ObjectDefineProperty(
async (...args) => {
async (arg0 = undefined, context) => {
// Update only when hook is invoked to avoid fingering the wrong filePath
meta.hookErrIdentifier = `${hookFilePath} '${hookName}'`;

validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, args);
validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, arg0, context);

const outputErrIdentifier = `${chain[generatedHookIndex].url} '${hookName}' hook's ${nextHookName}()`;

// Set when next<HookName> is actually called, not just generated.
if (generatedHookIndex === 0) { meta.chainFinished = true; }

// `context` is an optional argument that only needs to be passed when changed
switch (args.length) {
case 1: // It was omitted, so supply the cached value
ArrayPrototypePush(args, meta.context);
break;
case 2: // Overrides were supplied, so update cached value
ObjectAssign(meta.context, args[1]);
break;
if (context) { // `context` has already been validated, so no fancy check needed.
ObjectAssign(meta.context, context);
}

ArrayPrototypePush(args, nextNextHook);
const output = await ReflectApply(hook, undefined, args);
const output = await hook(arg0, meta.context, nextNextHook);

validateOutput(outputErrIdentifier, output);

Expand Down Expand Up @@ -575,7 +567,7 @@ class ESMLoader {
shortCircuited: false,
};

const validateArgs = (hookErrIdentifier, { 0: nextUrl, 1: ctx }) => {
const validateArgs = (hookErrIdentifier, nextUrl, ctx) => {
if (typeof nextUrl !== 'string') {
// non-strings can be coerced to a url string
// validateString() throws a less-specific error
Expand Down Expand Up @@ -829,7 +821,7 @@ class ESMLoader {
shortCircuited: false,
};

const validateArgs = (hookErrIdentifier, { 0: suppliedSpecifier, 1: ctx }) => {
const validateArgs = (hookErrIdentifier, suppliedSpecifier, ctx) => {
validateString(
suppliedSpecifier,
`${hookErrIdentifier} specifier`,
Expand Down
22 changes: 22 additions & 0 deletions test/es-module/test-esm-loader-chaining.mjs
Expand Up @@ -101,6 +101,28 @@ describe('ESM: loader chaining', { concurrency: true }, () => {
assert.strictEqual(code, 0);
});

it('should accept only the correct arguments', async () => {
const { stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-log-args.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-with-too-many-args.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);

assert.match(stdout, /^resolve arg count: 3$/m);
assert.match(stdout, /specifier: 'node:fs'/);
assert.match(stdout, /next: \[AsyncFunction: nextResolve\]/);

assert.match(stdout, /^load arg count: 3$/m);
assert.match(stdout, /url: 'node:fs'/);
assert.match(stdout, /next: \[AsyncFunction: nextLoad\]/);
});

it('should result in proper output from multiple changes in resolve hooks', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
Expand Down
28 changes: 28 additions & 0 deletions test/fixtures/es-module-loaders/loader-log-args.mjs
@@ -0,0 +1,28 @@
export async function resolve(...args) {
console.log(`resolve arg count: ${args.length}`);
console.log({
specifier: args[0],
context: args[1],
next: args[2],
});

return {
shortCircuit: true,
url: args[0],
};
}

export async function load(...args) {
console.log(`load arg count: ${args.length}`);
console.log({
url: args[0],
context: args[1],
next: args[2],
});

return {
format: 'module',
source: '',
shortCircuit: true,
};
}
7 changes: 7 additions & 0 deletions test/fixtures/es-module-loaders/loader-with-too-many-args.mjs
@@ -0,0 +1,7 @@
export async function resolve(specifier, context, next) {
return next(specifier, context, 'resolve-extra-arg');
}

export async function load(url, context, next) {
return next(url, context, 'load-extra-arg');
}

0 comments on commit ae157d9

Please sign in to comment.