Skip to content

Commit

Permalink
Switch from AddVectoredExceptionHandler to SetUnhandledExceptionFilter
Browse files Browse the repository at this point in the history
This avoids issues with Catch2's handler firing too early, on
structured exceptions that would be handled later. This issue
meant that the old attempts at structured exception handling
were incompatible with Windows's ASan, because it throws
continuable `C0000005` exception, which it then handles.

With the new handling, Catch2 is only notified if nothing else,
including the debugger, has handled the exception.

Signed-off-by: Alan Jowett <alanjo@microsoft.com>

Closes catchorg#2332
Closes catchorg#2286
Closes catchorg#898
  • Loading branch information
Alan-Jowett authored and nicramage committed Jan 30, 2024
1 parent 75262dc commit a7cfbfc
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 10 deletions.
17 changes: 7 additions & 10 deletions include/internal/catch_fatal_condition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ namespace Catch {
{ static_cast<DWORD>(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);
Expand All @@ -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
Expand All @@ -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");
if (SetUnhandledExceptionFilter(reinterpret_cast<LPTOP_LEVEL_EXCEPTION_FILTER>(previousTopLevelExceptionFilter)) != topLevelExceptionFilter) {
CATCH_RUNTIME_ERROR("Could not restore previous top level exception filter");
}
exceptionHandlerHandle = nullptr;
previousTopLevelExceptionFilter = nullptr;
}

} // end namespace Catch
Expand Down
33 changes: 33 additions & 0 deletions projects/SelfTest/UsageTests/Misc.tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,4 +490,37 @@ TEMPLATE_TEST_CASE_SIG("#1954 - 7 arg template test case sig compiles", "[regres
SUCCEED();
}

#if defined(CATCH_PLATFORM_WINDOWS)
#include <Windows.h>

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();
}
#endif

}} // namespace MiscTests

0 comments on commit a7cfbfc

Please sign in to comment.