Skip to content

Commit 66e8399

Browse files
committed
module: correctly detect top-level await in ambiguous contexts
Fixes: #58331
1 parent be2120f commit 66e8399

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

src/node_contextify.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,12 @@ static const auto throws_only_in_cjs_error_messages =
16471647
"await is only valid in async functions and "
16481648
"the top level bodies of modules"};
16491649

1650+
static std::vector<std::string_view> maybe_top_level_await_errors = {
1651+
// example: `func(await 1);`
1652+
"missing ) after argument list",
1653+
// example: `if(await 1)`
1654+
"SyntaxError: Unexpected"};
1655+
16501656
// If cached_data is provided, it would be used for the compilation and
16511657
// the on-disk compilation cache from NODE_COMPILE_CACHE (if configured)
16521658
// would be ignored.
@@ -1877,6 +1883,16 @@ bool ShouldRetryAsESM(Realm* realm,
18771883
break;
18781884
}
18791885
}
1886+
1887+
for (const auto& error_message : maybe_top_level_await_errors) {
1888+
if (message_view.find(error_message) != std::string_view::npos) {
1889+
// If the error message is related to top-level await, we can try to
1890+
// compile it as ESM.
1891+
maybe_valid_in_esm = true;
1892+
break;
1893+
}
1894+
}
1895+
18801896
if (!maybe_valid_in_esm) {
18811897
return false;
18821898
}

test/es-module/test-esm-detect-ambiguous.mjs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,3 +423,77 @@ describe('when working with Worker threads', () => {
423423
strictEqual(signal, null);
424424
});
425425
});
426+
427+
describe('maybe top-level await syntax errors that are not recognized as top-level await errors', () => {
428+
const expressions = [
429+
// string
430+
{ expression: '""' },
431+
// number
432+
{ expression: '0' },
433+
// boolean
434+
{ expression: 'true' },
435+
// null
436+
{ expression: 'null' },
437+
// undefined
438+
{ expression: 'undefined' },
439+
// object
440+
{ expression: '{}' },
441+
// array
442+
{ expression: '[]' },
443+
// new
444+
{ expression: 'new Date()' },
445+
// identifier
446+
{ initialize: 'const a = 2;', expression: 'a' },
447+
];
448+
it('should not crash the process', async () => {
449+
for (const { expression, initialize } of expressions) {
450+
const wrapperExpressions = [
451+
`function callAwait() {}; callAwait(await ${expression});`,
452+
`if (await ${expression}) {}`,
453+
`{ key: await ${expression} }`,
454+
`[await ${expression}]`,
455+
`(await ${expression})`,
456+
];
457+
for (const wrapperExpression of wrapperExpressions) {
458+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
459+
'--eval',
460+
`
461+
${initialize || ''}
462+
${wrapperExpression}
463+
`,
464+
]);
465+
466+
strictEqual(stderr, '');
467+
strictEqual(stdout, '');
468+
strictEqual(code, 0);
469+
strictEqual(signal, null);
470+
}
471+
}
472+
});
473+
474+
it('should crash when the expression is not valid', async () => {
475+
let { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
476+
'--eval',
477+
`
478+
function callAwait() {}
479+
callAwait(await "" "");
480+
`,
481+
]);
482+
match(stderr, /SyntaxError: missing \) after argument list/);
483+
strictEqual(stdout, '');
484+
strictEqual(code, 1);
485+
strictEqual(signal, null);
486+
487+
({ code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
488+
'--eval',
489+
`
490+
function callAwait() {}
491+
if (a "") {}
492+
`,
493+
]));
494+
match(stderr, /SyntaxError: Unexpected string/);
495+
strictEqual(stdout, '');
496+
strictEqual(code, 1);
497+
strictEqual(signal, null);
498+
});
499+
});

0 commit comments

Comments
 (0)