impr: Unionize exception and assertion handling

This commit is contained in:
WerWolv 2025-12-15 09:52:13 +01:00
parent 49bbe7dc77
commit cfac7ff0ba
10 changed files with 84 additions and 37 deletions

View File

@ -10,6 +10,7 @@
#include <condition_variable> #include <condition_variable>
#include <source_location> #include <source_location>
#include <thread> #include <thread>
#include <hex/trace/exceptions.hpp>
EXPORT_MODULE namespace hex { EXPORT_MODULE namespace hex {
@ -94,7 +95,12 @@ EXPORT_MODULE namespace hex {
std::atomic_flag m_hadException; std::atomic_flag m_hadException;
std::string m_exceptionMessage; std::string m_exceptionMessage;
struct TaskInterruptor { virtual ~TaskInterruptor() = default; }; struct TaskInterruptor {
TaskInterruptor() {
trace::disableExceptionCaptureForCurrentThread();
}
virtual ~TaskInterruptor() = default;
};
friend class TaskHolder; friend class TaskHolder;
friend class TaskManager; friend class TaskManager;

View File

@ -14,6 +14,10 @@
static_assert(false, "Debug variables are only intended for use during development."); static_assert(false, "Debug variables are only intended for use during development.");
#endif #endif
namespace hex::trace {
struct StackTraceResult;
}
namespace hex::dbg { namespace hex::dbg {
namespace impl { namespace impl {
@ -47,4 +51,6 @@ namespace hex::dbg {
bool debugModeEnabled(); bool debugModeEnabled();
void setDebugModeEnabled(bool enabled); void setDebugModeEnabled(bool enabled);
void printStackTrace(const trace::StackTraceResult &stackTrace);
} }

View File

@ -7,6 +7,8 @@
#include <ranges> #include <ranges>
#include <jthread.hpp> #include <jthread.hpp>
#include <hex/helpers/debugging.hpp>
#include <hex/trace/exceptions.hpp>
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
#include <windows.h> #include <windows.h>
@ -310,6 +312,8 @@ namespace hex {
} }
try { try {
trace::enableExceptionCaptureForCurrentThread();
// Set the thread name to the name of the task // Set the thread name to the name of the task
TaskManager::setCurrentThreadName(Lang(task->m_unlocalizedName)); TaskManager::setCurrentThreadName(Lang(task->m_unlocalizedName));
@ -323,15 +327,21 @@ namespace hex {
} catch (const std::exception &e) { } catch (const std::exception &e) {
log::error("Exception in task '{}': {}", task->m_unlocalizedName.get(), e.what()); log::error("Exception in task '{}': {}", task->m_unlocalizedName.get(), e.what());
dbg::printStackTrace(trace::getStackTrace());
// Handle the task throwing an uncaught exception // Handle the task throwing an uncaught exception
task->exception(e.what()); task->exception(e.what());
} catch (...) { } catch (...) {
log::error("Exception in task '{}'", task->m_unlocalizedName.get()); log::error("Exception in task '{}'", task->m_unlocalizedName.get());
dbg::printStackTrace(trace::getStackTrace());
// Handle the task throwing an uncaught exception of unknown type // Handle the task throwing an uncaught exception of unknown type
task->exception("Unknown Exception"); task->exception("Unknown Exception");
} }
trace::disableExceptionCaptureForCurrentThread();
s_currentTask = nullptr; s_currentTask = nullptr;
task->finish(); task->finish();
} }

View File

@ -1,4 +1,6 @@
#include <hex/helpers/debugging.hpp> #include <hex/helpers/debugging.hpp>
#include <hex/helpers/logger.hpp>
#include <hex/trace/stacktrace.hpp>
namespace hex::dbg { namespace hex::dbg {
@ -21,4 +23,23 @@ namespace hex::dbg {
s_debugMode = enabled; s_debugMode = enabled;
} }
[[noreturn]] void assertionHandler(const char* file, int line, const char *function, const char* exprString) {
log::error("Assertion failed: {} at {}:{} => {}", exprString, file, line, function);
const auto stackTrace = trace::getStackTrace();
dbg::printStackTrace(stackTrace);
std::abort();
}
void printStackTrace(const trace::StackTraceResult &stackTrace) {
log::fatal("Printing stacktrace using implementation '{}'", stackTrace.implementationName);
for (const auto &stackFrame : stackTrace.stackFrames) {
if (stackFrame.line == 0)
log::fatal(" ({}) | {}", stackFrame.file, stackFrame.function);
else
log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function);
}
}
} }

View File

@ -12,6 +12,7 @@
#include <mutex> #include <mutex>
#include <chrono> #include <chrono>
#include <fmt/chrono.h> #include <fmt/chrono.h>
#include <hex/helpers/debugging.hpp>
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
#include <Windows.h> #include <Windows.h>
@ -149,14 +150,6 @@ namespace hex::log {
); );
} }
void assertionHandler(const char* exprString, const char* file, int line) {
log::error("Assertion failed: {} at {}:{}", exprString, file, line);
#if defined (DEBUG)
std::abort();
#endif
}
namespace color { namespace color {
fmt::color debug() { return fmt::color::medium_sea_green; } fmt::color debug() { return fmt::color::medium_sea_green; }

View File

@ -25,10 +25,17 @@
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) //#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
namespace hex::log::impl { namespace hex::dbg {
void assertionHandler(const char* expr_str, const char* file, int line); [[noreturn]] void assertionHandler(const char* file, int line, const char *function, const char* exprString);
} }
#define IM_ASSERT(_EXPR) do { if (!(_EXPR)) [[unlikely]] { hex::log::impl::assertionHandler(#_EXPR, __FILE__, __LINE__); } } while(0)
#if defined(__PRETTY_FUNCTION__)
#define IM_ASSERT(_EXPR) do { if (!(_EXPR)) [[unlikely]] { hex::dbg::assertionHandler(__FILE__, __LINE__, __PRETTY_FUNCTION__, #_EXPR); } } while(0)
#elif defined(__FUNCSIG__)
#define IM_ASSERT(_EXPR) do { if (!(_EXPR)) [[unlikely]] { hex::dbg::assertionHandler(__FILE__, __LINE__, __FUNCSIG__, #_EXPR); } } while(0)
#else
#define IM_ASSERT(_EXPR) do { if (!(_EXPR)) [[unlikely]] { hex::dbg::assertionHandler(__FILE__, __LINE__, __FUNCTION__, #_EXPR); } } while(0)
#endif
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.

View File

@ -6,7 +6,12 @@
namespace hex::trace { namespace hex::trace {
using AssertionHandler = void(*)(const char* file, int line, const char *function, const char* exprString);
std::optional<StackTraceResult> getLastExceptionStackTrace(); std::optional<StackTraceResult> getLastExceptionStackTrace();
void setAssertionHandler(AssertionHandler handler);
void enableExceptionCaptureForCurrentThread(); void enableExceptionCaptureForCurrentThread();
void disableExceptionCaptureForCurrentThread();
} }

View File

@ -2,8 +2,9 @@
namespace hex::trace { namespace hex::trace {
static std::optional<StackTraceResult> s_lastExceptionStackTrace; static thread_local std::optional<StackTraceResult> s_lastExceptionStackTrace;
static thread_local bool s_threadExceptionCaptureEnabled = false; static thread_local bool s_threadExceptionCaptureEnabled = false;
static AssertionHandler s_assertionHandler = nullptr;
std::optional<StackTraceResult> getLastExceptionStackTrace() { std::optional<StackTraceResult> getLastExceptionStackTrace() {
if (!s_lastExceptionStackTrace.has_value()) if (!s_lastExceptionStackTrace.has_value())
@ -15,18 +16,26 @@ namespace hex::trace {
return result; return result;
} }
void setAssertionHandler(AssertionHandler handler) {
s_assertionHandler = handler;
}
void enableExceptionCaptureForCurrentThread() { void enableExceptionCaptureForCurrentThread() {
s_threadExceptionCaptureEnabled = true; s_threadExceptionCaptureEnabled = true;
} }
void disableExceptionCaptureForCurrentThread() {
s_threadExceptionCaptureEnabled = false;
}
} }
#if defined(HEX_WRAP_CXA_THROW) #if defined(HEX_WRAP_CXA_THROW)
extern "C" { extern "C" {
[[noreturn]] void __real___cxa_throw(void* thrownException, void* type, void (*destructor)(void*)); [[noreturn]] void __real___cxa_throw(void* thrownException, std::type_info* type, void (*destructor)(void*));
[[noreturn]] void __wrap___cxa_throw(void* thrownException, void* type, void (*destructor)(void*)) { [[noreturn]] void __wrap___cxa_throw(void* thrownException, std::type_info* type, void (*destructor)(void*)) {
if (hex::trace::s_threadExceptionCaptureEnabled) if (hex::trace::s_threadExceptionCaptureEnabled)
hex::trace::s_lastExceptionStackTrace = hex::trace::getStackTrace(); hex::trace::s_lastExceptionStackTrace = hex::trace::getStackTrace();
@ -41,19 +50,15 @@ namespace hex::trace {
extern "C" { extern "C" {
[[noreturn]] void __real__ZSt21__glibcxx_assert_failPKciS0_S0_(const char* file, int line, const char* function, const char* condition);
[[noreturn]] void __wrap__ZSt21__glibcxx_assert_failPKciS0_S0_(const char* file, int line, const char* function, const char* condition) { [[noreturn]] void __wrap__ZSt21__glibcxx_assert_failPKciS0_S0_(const char* file, int line, const char* function, const char* condition) {
if (file != nullptr && function != nullptr && condition != nullptr) { if (hex::trace::s_assertionHandler != nullptr) {
fprintf(stderr, "Assertion failed (glibc++): (%s), function %s, file %s, line %d.\n", condition, function, file, line); hex::trace::s_assertionHandler(file, line, function, condition);
} else if (function != nullptr) { } else {
fprintf(stderr, "%s: Undefined behavior detected (glibc++).\n", function); __real__ZSt21__glibcxx_assert_failPKciS0_S0_(file, line, function, condition);
} }
auto stackTrace = hex::trace::getStackTrace(); std::abort();
for (const auto &entry : stackTrace.stackFrames) {
fprintf(stderr, " %s at %s:%d\n", entry.function.c_str(), entry.file.c_str(), entry.line);
}
std::terminate();
} }
} }

View File

@ -16,6 +16,7 @@
#include <csignal> #include <csignal>
#include <exception> #include <exception>
#include <typeinfo> #include <typeinfo>
#include <hex/helpers/debugging.hpp>
#include <hex/helpers/utils.hpp> #include <hex/helpers/utils.hpp>
#if defined(IMGUI_TEST_ENGINE) #if defined(IMGUI_TEST_ENGINE)
@ -71,23 +72,12 @@ namespace hex::crash {
log::warn("Could not write crash.json file!"); log::warn("Could not write crash.json file!");
} }
static void printStackTrace() {
auto stackTraceResult = trace::getStackTrace();
log::fatal("Printing stacktrace using implementation '{}'", stackTraceResult.implementationName);
for (const auto &stackFrame : stackTraceResult.stackFrames) {
if (stackFrame.line == 0)
log::fatal(" ({}) | {}", stackFrame.file, stackFrame.function);
else
log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function);
}
}
static void callCrashHandlers(const std::string &msg) { static void callCrashHandlers(const std::string &msg) {
// Call the crash callback // Call the crash callback
crashCallback(msg); crashCallback(msg);
// Print the stacktrace to the console or log file // Print the stacktrace to the console or log file
printStackTrace(); dbg::printStackTrace(trace::getStackTrace());
// Flush all streams // Flush all streams
std::fflush(stdout); std::fflush(stdout);
@ -188,6 +178,7 @@ namespace hex::crash {
// Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex // Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex
void setupCrashHandlers() { void setupCrashHandlers() {
trace::initialize(); trace::initialize();
trace::setAssertionHandler(dbg::assertionHandler);
// Register signal handlers // Register signal handlers
{ {

View File

@ -1707,6 +1707,9 @@ namespace hex::plugin::builtin {
EventHighlightingChanged::post(); EventHighlightingChanged::post();
TaskManager::createTask("hex.builtin.view.pattern_editor.evaluating", TaskManager::NoProgress, [this, code, provider](auto &task) { TaskManager::createTask("hex.builtin.view.pattern_editor.evaluating", TaskManager::NoProgress, [this, code, provider](auto &task) {
// Disable exception tracing to speed up evaluation
trace::disableExceptionCaptureForCurrentThread();
auto runtimeLock = std::scoped_lock(ContentRegistry::PatternLanguage::getRuntimeLock()); auto runtimeLock = std::scoped_lock(ContentRegistry::PatternLanguage::getRuntimeLock());
auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); auto &runtime = ContentRegistry::PatternLanguage::getRuntime();