impr: Unionize exception and assertion handling

(cherry picked from commit cfac7ff0ba)
This commit is contained in:
WerWolv 2025-12-15 09:52:13 +01:00
parent f6b2251205
commit 81826df897
10 changed files with 84 additions and 37 deletions

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
#include <hex/helpers/debugging.hpp>
#include <hex/helpers/logger.hpp>
#include <hex/trace/stacktrace.hpp>
namespace hex::dbg {
@ -21,4 +23,23 @@ namespace hex::dbg {
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 <chrono>
#include <fmt/chrono.h>
#include <hex/helpers/debugging.hpp>
#if defined(OS_WINDOWS)
#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 {
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.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
namespace hex::log::impl {
void assertionHandler(const char* expr_str, const char* file, int line);
namespace hex::dbg {
[[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
// 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 {
using AssertionHandler = void(*)(const char* file, int line, const char *function, const char* exprString);
std::optional<StackTraceResult> getLastExceptionStackTrace();
void setAssertionHandler(AssertionHandler handler);
void enableExceptionCaptureForCurrentThread();
void disableExceptionCaptureForCurrentThread();
}

View File

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

View File

@ -16,6 +16,7 @@
#include <csignal>
#include <exception>
#include <typeinfo>
#include <hex/helpers/debugging.hpp>
#include <hex/helpers/utils.hpp>
#if defined(IMGUI_TEST_ENGINE)
@ -71,23 +72,12 @@ namespace hex::crash {
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) {
// Call the crash callback
crashCallback(msg);
// Print the stacktrace to the console or log file
printStackTrace();
dbg::printStackTrace(trace::getStackTrace());
// Flush all streams
std::fflush(stdout);
@ -188,6 +178,7 @@ namespace hex::crash {
// Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex
void setupCrashHandlers() {
trace::initialize();
trace::setAssertionHandler(dbg::assertionHandler);
// Register signal handlers
{

View File

@ -1700,6 +1700,9 @@ namespace hex::plugin::builtin {
EventHighlightingChanged::post();
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 &runtime = ContentRegistry::PatternLanguage::getRuntime();