From 40bf6326c80a63e70908411abe92f1c9f045b085 Mon Sep 17 00:00:00 2001 From: "Marco Antonio J. Costa" Date: Fri, 31 Jul 2026 14:22:58 -0300 Subject: [PATCH] report a caught exception at its own line, not at line 0 _ExceptionMessage builds the full call stack of the exception it was given and then calls _FailMessage with "",0,"", so every caught sgp:: or vfs::Exception produces an identical, locationless report - the bucket telemetry will see most of and can act on least. Fail with the innermost frame instead, which is the frame that knows where it came from. That feeds a runtime-built string to a _FailMessage that passed it to sprintf as the format string, so copy it bounded instead. AssertMsg call sites already build messages out of game state, and a %s in one of those would read arguments that were never passed. Co-Authored-By: Claude Opus 5 --- sgp/DEBUG.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/sgp/DEBUG.cpp b/sgp/DEBUG.cpp index 794866982..c18b7bbc4 100644 --- a/sgp/DEBUG.cpp +++ b/sgp/DEBUG.cpp @@ -412,10 +412,12 @@ void _FailMessage(const char* message, unsigned lineNum, const char * functionNa outputString << "{ " << GetTickCount() << " } " << basicInformation.str(); //Build the output strings + // Never as a format string: AssertMsg call sites build this out of game state, + // so a stray %s in it would read arguments that were never passed. if( message ) - sprintf( gubAssertString, message ); + snprintf( gubAssertString, sizeof( gubAssertString ), "%s", message ); else - sprintf( gubAssertString, "" ); + gubAssertString[0] = '\0'; //Output to debugger if (gfRecordToDebugger) @@ -491,6 +493,23 @@ STR8 String(const STR8 String, ...) } +// Fail with the innermost frame of the exception list, not with nothing. The crash +// report can only stamp what _FailMessage was handed, and a caught exception knows +// exactly where it came from — passing "",0,"" turns every one of them into the same +// locationless report. The utf8() temporaries live to the end of the call, and the +// crash handler reads them synchronously inside it. +static void FailFromExceptionList() +{ + if (g_ExceptionList.empty()) + { + _FailMessage("", 0, ""); + return; + } + SExceptionData const& innermost = g_ExceptionList.front(); + _FailMessage(innermost.message.utf8().c_str(), innermost.line, + innermost.function.utf8().c_str(), innermost.file.utf8().c_str()); +} + void _ExceptionMessage( sgp::Exception &ex ) { g_ExceptionList.clear(); @@ -504,7 +523,7 @@ void _ExceptionMessage( sgp::Exception &ex ) exd.line = (*it).line; g_ExceptionList.push_back(exd); } - _FailMessage("",0,""); + FailFromExceptionList(); } void _ExceptionMessage( vfs::Exception &ex ) @@ -520,7 +539,7 @@ void _ExceptionMessage( vfs::Exception &ex ) exd.line = (*it).line; g_ExceptionList.push_back(exd); } - _FailMessage("",0,""); + FailFromExceptionList(); } #include