Skip to content

Commit 2b257ba

Browse files
committed
module: correctly detect top-level await in ambiguous contexts
Fixes: #58331
1 parent bbc0593 commit 2b257ba

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

src/node_contextify.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,12 @@ static std::vector<std::string_view> throws_only_in_cjs_error_messages = {
16321632
"await is only valid in async functions and "
16331633
"the top level bodies of modules"};
16341634

1635+
static std::vector<std::string_view> maybe_top_level_await_errors = {
1636+
// example: `func(await 1);`
1637+
"missing ) after argument list",
1638+
// example: `if(await 1)`
1639+
"SyntaxError: Unexpected"};
1640+
16351641
// If cached_data is provided, it would be used for the compilation and
16361642
// the on-disk compilation cache from NODE_COMPILE_CACHE (if configured)
16371643
// would be ignored.
@@ -1858,6 +1864,16 @@ bool ShouldRetryAsESM(Realm* realm,
18581864
break;
18591865
}
18601866
}
1867+
1868+
for (const auto& error_message : maybe_top_level_await_errors) {
1869+
if (message_view.find(error_message) != std::string_view::npos) {
1870+
// If the error message is related to top-level await, we can try to
1871+
// compile it as ESM.
1872+
maybe_valid_in_esm = true;
1873+
break;
1874+
}
1875+
}
1876+
18611877
if (!maybe_valid_in_esm) {
18621878
return false;
18631879
}

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)