Skip to content

Commit 7880978

Browse files
islandryutargos
authored andcommitted
module: correctly detect top-level await in ambiguous contexts
Fixes: #58331 PR-URL: #58646 Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent c0f0845 commit 7880978

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

src/node_contextify.cc

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

1649+
static const auto maybe_top_level_await_errors =
1650+
std::array<std::string_view, 2>{
1651+
"missing ) after argument list", // example: `func(await 1);`
1652+
"SyntaxError: Unexpected" // example: `if(await 1)`
1653+
};
1654+
16491655
// If cached_data is provided, it would be used for the compilation and
16501656
// the on-disk compilation cache from NODE_COMPILE_CACHE (if configured)
16511657
// would be ignored.
@@ -1876,6 +1882,16 @@ bool ShouldRetryAsESM(Realm* realm,
18761882
break;
18771883
}
18781884
}
1885+
1886+
for (const auto& error_message : maybe_top_level_await_errors) {
1887+
if (message_view.find(error_message) != std::string_view::npos) {
1888+
// If the error message is related to top-level await, we can try to
1889+
// compile it as ESM.
1890+
maybe_valid_in_esm = true;
1891+
break;
1892+
}
1893+
}
1894+
18791895
if (!maybe_valid_in_esm) {
18801896
return false;
18811897
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { spawnPromisified } from '../common/index.mjs';
2+
import { describe, it } from 'node:test';
3+
import { strictEqual, match } from 'node:assert';
4+
5+
describe('unusual top-level await syntax errors', () => {
6+
const expressions = [
7+
// string
8+
{ expression: '""' },
9+
// number
10+
{ expression: '0' },
11+
// boolean
12+
{ expression: 'true' },
13+
// null
14+
{ expression: 'null' },
15+
// undefined
16+
{ expression: 'undefined' },
17+
// object
18+
{ expression: '{}' },
19+
// array
20+
{ expression: '[]' },
21+
// new
22+
{ expression: 'new Date()' },
23+
// identifier
24+
{ initialize: 'const a = 2;', expression: 'a' },
25+
];
26+
it('should not crash the process', async () => {
27+
for (const { expression, initialize } of expressions) {
28+
const wrapperExpressions = [
29+
`function callAwait() {}; callAwait(await ${expression});`,
30+
`if (await ${expression}) {}`,
31+
`{ key: await ${expression} }`,
32+
`[await ${expression}]`,
33+
`(await ${expression})`,
34+
];
35+
for (const wrapperExpression of wrapperExpressions) {
36+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
37+
'--eval',
38+
`
39+
${initialize || ''}
40+
${wrapperExpression}
41+
`,
42+
]);
43+
44+
strictEqual(stderr, '');
45+
strictEqual(stdout, '');
46+
strictEqual(code, 0);
47+
strictEqual(signal, null);
48+
}
49+
}
50+
});
51+
52+
it('should throw the error for unrelated syntax errors', async () => {
53+
const expression = 'foo bar';
54+
const wrapperExpressions = [
55+
[`function callSyntaxError() {}; callSyntaxError(${expression});`, /missing \) after argument list/],
56+
[`if (${expression}) {}`, /Unexpected identifier/],
57+
[`{ key: ${expression} }`, /Unexpected identifier/],
58+
[`[${expression}]`, /Unexpected identifier/],
59+
[`(${expression})`, /Unexpected identifier/],
60+
[`const ${expression} = 1;`, /Missing initializer in const declaration/],
61+
[`console.log('PI: ' Math.PI);`, /missing \) after argument list/],
62+
[`callAwait(await "" "");`, /missing \) after argument list/],
63+
];
64+
65+
for (const [wrapperExpression, error] of wrapperExpressions) {
66+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
67+
'--eval',
68+
`
69+
${wrapperExpression}
70+
`,
71+
]);
72+
match(stderr, error);
73+
strictEqual(stdout, '');
74+
strictEqual(code, 1);
75+
strictEqual(signal, null);
76+
}
77+
});
78+
});

0 commit comments

Comments
 (0)