diff --git a/include/internal/catch_fatal_condition.cpp b/include/internal/catch_fatal_condition.cpp index 873e2ed53a..36e418ff5b 100644 --- a/include/internal/catch_fatal_condition.cpp +++ b/include/internal/catch_fatal_condition.cpp @@ -85,7 +85,7 @@ namespace Catch { { static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, }; - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); @@ -99,7 +99,7 @@ namespace Catch { // Since we do not support multiple instantiations, we put these // into global variables and rely on cleaning them up in outlined // constructors/destructors - static PVOID exceptionHandlerHandle = nullptr; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; // For MSVC, we reserve part of the stack memory for handling @@ -121,18 +121,15 @@ namespace Catch { void FatalConditionHandler::engage_platform() { - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - if (!exceptionHandlerHandle) { - CATCH_RUNTIME_ERROR("Could not register vectored exception handler"); - } + // Register as a the top level exception filter. + previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); } - void FatalConditionHandler::disengage_platform() { - if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { - CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); + void FatalConditionHandler::disengage_platform() noexcept { + if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { + CATCH_RUNTIME_ERROR("Could not restore previous top level exception filter"); } - exceptionHandlerHandle = nullptr; + previousTopLevelExceptionFilter = nullptr; } } // end namespace Catch diff --git a/include/internal/catch_fatal_condition.h b/include/internal/catch_fatal_condition.h index b5744f8f21..1b6c5613bd 100644 --- a/include/internal/catch_fatal_condition.h +++ b/include/internal/catch_fatal_condition.h @@ -31,7 +31,7 @@ namespace Catch { // Should be if-defed to work on current platform, can assume // engage-disengage 1:1 pairing. void engage_platform(); - void disengage_platform(); + void disengage_platform() noexcept; public: // Should also have platform-specific implementations as needed FatalConditionHandler(); diff --git a/projects/SelfTest/UsageTests/Misc.tests.cpp b/projects/SelfTest/UsageTests/Misc.tests.cpp index dcd9fc8a8a..87670cd0e4 100644 --- a/projects/SelfTest/UsageTests/Misc.tests.cpp +++ b/projects/SelfTest/UsageTests/Misc.tests.cpp @@ -7,6 +7,7 @@ */ #include "catch.hpp" +#include "internal/catch_windows_h_proxy.h" #ifdef __clang__ # pragma clang diagnostic ignored "-Wc++98-compat" @@ -490,4 +491,48 @@ TEMPLATE_TEST_CASE_SIG("#1954 - 7 arg template test case sig compiles", "[regres SUCCEED(); } +// MinGW doesn't support __try, and Clang has only very partial support +#if defined(_MSC_VER) +void throw_and_catch() +{ + __try { + RaiseException(0xC0000005, 0, 0, NULL); + } + __except (1) + { + + } +} + + +TEST_CASE("Validate SEH behavior - handled", "[approvals][FatalConditionHandler][CATCH_PLATFORM_WINDOWS]") +{ + // Validate that Catch2 framework correctly handles tests raising and handling SEH exceptions. + throw_and_catch(); +} + +void throw_no_catch() +{ + RaiseException(0xC0000005, 0, 0, NULL); +} + +TEST_CASE("Validate SEH behavior - unhandled", "[.approvals][FatalConditionHandler][CATCH_PLATFORM_WINDOWS]") +{ + // Validate that Catch2 framework correctly handles tests raising and not handling SEH exceptions. + throw_no_catch(); +} + +static LONG CALLBACK dummyExceptionFilter(PEXCEPTION_POINTERS /*ExceptionInfo*/) { + return EXCEPTION_CONTINUE_SEARCH; +} + +TEST_CASE("Validate SEH behavior - no crash for stack unwinding", "[approvals][!throws][!shouldfail][FatalConditionHandler][CATCH_PLATFORM_WINDOWS]") +{ + // Trigger stack unwinding with SEH top-level filter changed and validate the test fails expectedly with no application crash + SetUnhandledExceptionFilter(dummyExceptionFilter); + throw 1; +} + +#endif // _MSC_VER + }} // namespace MiscTests