From 3a40c5b6879fdd57943f1d624bfc0ea378fbe5f7 Mon Sep 17 00:00:00 2001 From: robobun Date: Sat, 11 Jul 2026 03:05:37 +0000 Subject: [PATCH] JSModuleRecord::evaluate: resume suspended TLA body when a cycle sibling throws When a top-level-await module is part of an import cycle and suspends at its first await, it is still on the InnerModuleEvaluation stack with status Evaluating. If a sibling in the same Evaluate() pass then throws, step 9 of Evaluate() stamps that error onto every stack member as evaluationError and transitions them to Evaluated. When the suspended body's resume microtask later fires, it re-enters JSModuleRecord::evaluate(), which would see evaluationError() set and throw it instead of resuming the body. Everything after the await (finally blocks, export initialisers, resource registration) is then silently skipped while the process continues. Node and the spec resume the body: AsyncBlockStart runs it as an independent async context, and AsyncModuleExecutionFulfilled/Rejected already handle the "status is already Evaluated" case afterwards. Gate the evaluationError() early-return on the generator State still being Init (body not yet started). A suspended body (State > 0) proceeds to executeModuleProgram and runs to completion. --- Source/JavaScriptCore/runtime/JSModuleRecord.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index 7c2d4ee4bc785..b70bb4f47b051 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -112,9 +112,15 @@ JSValue JSModuleRecord::evaluate(JSGlobalObject* globalObject, JSValue sentValue VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - if (JSValue error = evaluationError()) { - scope.throwException(globalObject, error); - return { }; + // A TLA body suspended at an await (State != Init) must resume even if a + // sibling in the same cycle set our graph-level evaluationError meanwhile; + // asyncExecution{Fulfilled,Rejected} already no-op when status == Evaluated. + JSValue state = internalField(Field::State).get(); + if (state.isNumber() && state.asInt32AsAnyInt() == std::to_underlying(State::Init)) { + if (JSValue error = evaluationError()) { + scope.throwException(globalObject, error); + return { }; + } } ModuleProgramExecutable* executable = m_moduleProgramExecutable.get();