Skip to content
Merged
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
16 changes: 16 additions & 0 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,12 @@ static const auto throws_only_in_cjs_error_messages =
"await is only valid in async functions and "
"the top level bodies of modules"};

static const auto maybe_top_level_await_errors =
std::array<std::string_view, 2>{
"missing ) after argument list", // example: `func(await 1);`
"SyntaxError: Unexpected" // example: `if(await 1)`
};

// If cached_data is provided, it would be used for the compilation and
// the on-disk compilation cache from NODE_COMPILE_CACHE (if configured)
// would be ignored.
Expand Down Expand Up @@ -1877,6 +1883,16 @@ bool ShouldRetryAsESM(Realm* realm,
break;
}
}

for (const auto& error_message : maybe_top_level_await_errors) {
if (message_view.find(error_message) != std::string_view::npos) {
// If the error message is related to top-level await, we can try to
// compile it as ESM.
maybe_valid_in_esm = true;
break;
}
}

if (!maybe_valid_in_esm) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { spawnPromisified } from '../common/index.mjs';
import { describe, it } from 'node:test';
import { strictEqual, match } from 'node:assert';

describe('unusual top-level await syntax errors', () => {
const expressions = [
// string
{ expression: '""' },
// number
{ expression: '0' },
// boolean
{ expression: 'true' },
// null
{ expression: 'null' },
// undefined
{ expression: 'undefined' },
// object
{ expression: '{}' },
// array
{ expression: '[]' },
// new
{ expression: 'new Date()' },
// identifier
{ initialize: 'const a = 2;', expression: 'a' },
];
it('should not crash the process', async () => {
for (const { expression, initialize } of expressions) {
const wrapperExpressions = [
`function callAwait() {}; callAwait(await ${expression});`,
`if (await ${expression}) {}`,
`{ key: await ${expression} }`,
`[await ${expression}]`,
`(await ${expression})`,
];
for (const wrapperExpression of wrapperExpressions) {
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
'--eval',
`
${initialize || ''}
${wrapperExpression}
`,
]);

strictEqual(stderr, '');
strictEqual(stdout, '');
strictEqual(code, 0);
strictEqual(signal, null);
}
}
});

it('should throw the error for unrelated syntax errors', async () => {
const expression = 'foo bar';
const wrapperExpressions = [
[`function callSyntaxError() {}; callSyntaxError(${expression});`, /missing \) after argument list/],
[`if (${expression}) {}`, /Unexpected identifier/],
[`{ key: ${expression} }`, /Unexpected identifier/],
[`[${expression}]`, /Unexpected identifier/],
[`(${expression})`, /Unexpected identifier/],
[`const ${expression} = 1;`, /Missing initializer in const declaration/],
[`console.log('PI: ' Math.PI);`, /missing \) after argument list/],
[`callAwait(await "" "");`, /missing \) after argument list/],
];

for (const [wrapperExpression, error] of wrapperExpressions) {
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
'--eval',
`
${wrapperExpression}
`,
]);
match(stderr, error);
strictEqual(stdout, '');
strictEqual(code, 1);
strictEqual(signal, null);
}
});
});
Loading