Skip to content

Commit d6df9be

Browse files
committed
src/node_errors: add re-entrancy guard to prevent infinite recursion in TriggerUncaughtException
Add a thread_local re-entrancy guard to TriggerUncaughtException() that detects when the JS-level exception handler (process._fatalException) itself triggers another exception through the inspector protocol. This prevents the infinite loop: TriggerUncaughtException -> InspectorConsoleCall -> TriggerUncaughtException -> InspectorConsoleCall -> ... When re-entrancy is detected, the function prints a diagnostic to stderr, reports the fatal exception with kDontEnhance (to bypass the inspector path), and aborts the process. Fixes: #64326
1 parent 6adfff9 commit d6df9be

1 file changed

Lines changed: 31 additions & 0 deletions

File tree

src/node_errors.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ static std::string GetErrorSource(Isolate* isolate,
191191

192192
static std::atomic<bool> is_in_oom{false};
193193
static thread_local std::atomic<bool> is_retrieving_js_stacktrace{false};
194+
static thread_local bool is_in_uncaught_exception = false;
194195
MaybeLocal<StackTrace> GetCurrentStackTrace(Isolate* isolate, int frame_count) {
195196
if (isolate == nullptr) {
196197
return MaybeLocal<StackTrace>();
@@ -1278,6 +1279,36 @@ void TriggerUncaughtException(Isolate* isolate,
12781279
CHECK(isolate->InContext());
12791280
Local<Context> context = isolate->GetCurrentContext();
12801281
Environment* env = Environment::GetCurrent(context);
1282+
// Re-entrancy guard: prevent infinite recursion when the JS-level
1283+
// exception handler (process._fatalException) itself triggers another
1284+
// exception through the inspector protocol, causing V8's pending message
1285+
// reporting to call TriggerUncaughtException reentrantly.
1286+
if (is_in_uncaught_exception) {
1287+
if (env != nullptr) {
1288+
PrintToStderrAndFlush(
1289+
"FATAL ERROR: Re-entrant uncaught exception detected.\n"
1290+
"The exception handler threw an error while processing a\n"
1291+
"previous uncaught exception, likely due to an inspector\n"
1292+
"protocol issue. Aborting to prevent infinite recursion.");
1293+
// Use kDontEnhance to avoid the inspector reporting path that may
1294+
// have caused the re-entrancy.
1295+
ReportFatalException(env,
1296+
error,
1297+
message,
1298+
EnhanceFatalException::kDontEnhance);
1299+
} else {
1300+
PrintToStderrAndFlush(
1301+
"FATAL ERROR: Re-entrant uncaught exception (no environment).");
1302+
}
1303+
ABORT();
1304+
}
1305+
// RAII guard to ensure the flag is cleared when TriggerUncaughtException
1306+
// returns normally. Note: ABORT() and env->Exit() do not run this
1307+
// destructor, which is intentional — the process is terminating.
1308+
struct UncaughtExceptionGuard {
1309+
UncaughtExceptionGuard() { is_in_uncaught_exception = true; }
1310+
~UncaughtExceptionGuard() { is_in_uncaught_exception = false; }
1311+
} uncaught_exception_guard;
12811312
if (env == nullptr) {
12821313
// This means that the exception happens before Environment is assigned
12831314
// to the context e.g. when there is a SyntaxError in a per-context

0 commit comments

Comments
 (0)