Skip to content

Commit 011e6e0

Browse files
joyeecheungmarco-ippolito
authored andcommitted
module: fix error thrown from require(esm) hitting TLA repeatedly
This tracks the asynchronicity in the ModuleWraps when they turn out to contain TLA after instantiation, and throw the right error (ERR_REQUIRE_ASYNC_MODULE) when it's required again. It removes the freezing of ModuleWraps since it's not meaningful to freeze this when the rest of the module loader is mutable, and we can record the asynchronicity in the ModuleWrap right after compilation after we get a V8 upgrade that contains v8::Module::HasTopLevelAwait() instead of searching through the module graph repeatedly which can be slow. PR-URL: #55520 Backport-PR-URL: #56927Fixes: #55516 Refs: #52697 Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Jacob Smith <[email protected]>
1 parent fdf5028 commit 011e6e0

File tree

5 files changed

+40
-11
lines changed

5 files changed

+40
-11
lines changed

‎lib/internal/errors.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,6 +1692,9 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) =>{
16921692
E('ERR_PERFORMANCE_INVALID_TIMESTAMP',
16931693
'%d is not a valid timestamp',TypeError);
16941694
E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS','%s',TypeError);
1695+
E('ERR_REQUIRE_ASYNC_MODULE','require() cannot be used on an ESM '+
1696+
'graph with top-level await. Use import() instead. To see where the'+
1697+
' top-level await comes from, use --experimental-print-required-tla.',Error);
16951698
E('ERR_REQUIRE_CYCLE_MODULE','%s',Error);
16961699
E('ERR_REQUIRE_ESM',
16971700
function(filename,hasEsmSyntax,parentPath=null,packageJsonPath=null){

‎lib/internal/modules/esm/loader.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const{imported_cjs_symbol } = internalBinding('symbols');
2222

2323
constassert=require('internal/assert');
2424
const{
25+
ERR_REQUIRE_ASYNC_MODULE,
2526
ERR_REQUIRE_CYCLE_MODULE,
2627
ERR_REQUIRE_ESM,
2728
ERR_NETWORK_IMPORT_DISALLOWED,
@@ -293,6 +294,9 @@ class ModuleLoader{
293294
// evaluated at this point.
294295
if(job!==undefined){
295296
mod[kRequiredModuleSymbol]=job.module;
297+
if(job.module.async){
298+
thrownewERR_REQUIRE_ASYNC_MODULE();
299+
}
296300
if(job.module.getStatus()!==kEvaluated){
297301
constparentFilename=urlToFilename(parent?.filename);
298302
letmessage=`Cannot require() ES Module ${filename} in a cycle.`;

‎lib/internal/modules/esm/module_job.js‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ const resolvedPromise = PromiseResolve();
3232
const{
3333
setHasStartedUserESMExecution,
3434
}=require('internal/modules/helpers');
35+
const{ getOptionValue }=require('internal/options');
3536
constnoop=FunctionPrototype;
36-
37+
const{
38+
ERR_REQUIRE_ASYNC_MODULE,
39+
}=require('internal/errors').codes;
3740
lethasPausedEntry=false;
3841

3942
constCJSGlobalLike=[
@@ -370,7 +373,16 @@ class ModuleJobSync extends ModuleJobBase{
370373

371374
runSync(){
372375
// TODO(joyeecheung): add the error decoration logic from the async instantiate.
373-
this.module.instantiateSync();
376+
this.module.async=this.module.instantiateSync();
377+
// If --experimental-print-required-tla is true, proceeds to evaluation even
378+
// if it's async because we want to search for the TLA and help users locate
379+
// them.
380+
// TODO(joyeecheung): track the asynchroniticy using v8::Module::HasTopLevelAwait()
381+
// and we'll be able to throw right after compilation of the modules, using acron
382+
// to find and print the TLA.
383+
if(this.module.async&&!getOptionValue('--experimental-print-required-tla')){
384+
thrownewERR_REQUIRE_ASYNC_MODULE();
385+
}
374386
setHasStartedUserESMExecution();
375387
constnamespace=this.module.evaluateSync();
376388
return{__proto__: null,module: this.module, namespace };

‎src/module_wrap.cc‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ using v8::FunctionTemplate;
3131
using v8::HandleScope;
3232
using v8::Int32;
3333
using v8::Integer;
34-
using v8::IntegrityLevel;
3534
using v8::Isolate;
3635
using v8::Local;
3736
using v8::MaybeLocal;
@@ -290,7 +289,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args){
290289

291290
obj->contextify_context_ = contextify_context;
292291

293-
that->SetIntegrityLevel(context, IntegrityLevel::kFrozen);
294292
args.GetReturnValue().Set(that);
295293
}
296294

@@ -581,13 +579,9 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args){
581579
}
582580
}
583581

584-
// If --experimental-print-required-tla is true, proceeds to evaluation even
585-
// if it's async because we want to search for the TLA and help users locate
586-
// them.
587-
if (module->IsGraphAsync() && !env->options()->print_required_tla){
588-
THROW_ERR_REQUIRE_ASYNC_MODULE(env);
589-
return;
590-
}
582+
// TODO(joyeecheung): record Module::HasTopLevelAwait() in every ModuleWrap
583+
// and infer the asynchronicity from a module's children during linking.
584+
args.GetReturnValue().Set(module->IsGraphAsync());
591585
}
592586

593587
voidModuleWrap::EvaluateSync(const FunctionCallbackInfo<Value>& args){
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// This tests that after failing to require an ESM that contains TLA,
2+
// retrying with require() still throws, and produces consistent results.
3+
'use strict';
4+
require('../common');
5+
constassert=require('assert');
6+
7+
assert.throws(()=>{
8+
require('../fixtures/es-modules/tla/resolved.mjs');
9+
},{
10+
code: 'ERR_REQUIRE_ASYNC_MODULE'
11+
});
12+
assert.throws(()=>{
13+
require('../fixtures/es-modules/tla/resolved.mjs');
14+
},{
15+
code: 'ERR_REQUIRE_ASYNC_MODULE'
16+
});

0 commit comments

Comments
(0)