Switch from AddVectoredExceptionHandler to SetUnhandledExceptionFilter

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 #2332
Closes #2286
Closes #898
This commit is contained in:
Alan Jowett 2021-12-15 09:51:04 -07:00 committed by Martin Hořeňovský
parent d3199c42c2
commit 98a6c69e1e
2 changed files with 39 additions and 10 deletions

View File

@ -84,7 +84,7 @@ namespace Catch {
{ static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, { 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) { for (auto const& def : signalDefs) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
reportFatal(def.name); reportFatal(def.name);
@ -98,7 +98,7 @@ namespace Catch {
// Since we do not support multiple instantiations, we put these // Since we do not support multiple instantiations, we put these
// into global variables and rely on cleaning them up in outlined // into global variables and rely on cleaning them up in outlined
// constructors/destructors // constructors/destructors
static PVOID exceptionHandlerHandle = nullptr; static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
// For MSVC, we reserve part of the stack memory for handling // For MSVC, we reserve part of the stack memory for handling
@ -120,18 +120,15 @@ namespace Catch {
void FatalConditionHandler::engage_platform() { void FatalConditionHandler::engage_platform() {
// Register as first handler in current chain // Register as a the top level exception filter.
exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter);
if (!exceptionHandlerHandle) {
CATCH_RUNTIME_ERROR("Could not register vectored exception handler");
}
} }
void FatalConditionHandler::disengage_platform() { void FatalConditionHandler::disengage_platform() {
if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { if (SetUnhandledExceptionFilter(reinterpret_cast<LPTOP_LEVEL_EXCEPTION_FILTER>(previousTopLevelExceptionFilter)) != topLevelExceptionFilter) {
CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); CATCH_RUNTIME_ERROR("Could not restore previous top level exception filter");
} }
exceptionHandlerHandle = nullptr; previousTopLevelExceptionFilter = nullptr;
} }
} // end namespace Catch } // end namespace Catch

View File

@ -6,6 +6,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp> #include <catch2/catch_template_test_macros.hpp>
#include <catch2/internal/catch_config_wchar.hpp> #include <catch2/internal/catch_config_wchar.hpp>
#include <catch2/internal/catch_windows_h_proxy.hpp>
#ifdef __clang__ #ifdef __clang__
# pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat"
@ -498,3 +499,34 @@ TEMPLATE_TEST_CASE_SIG("#1954 - 7 arg template test case sig compiles", "[regres
TEST_CASE("Same test name but with different tags is fine", "[.approvals][some-tag]") {} TEST_CASE("Same test name but with different tags is fine", "[.approvals][some-tag]") {}
TEST_CASE("Same test name but with different tags is fine", "[.approvals][other-tag]") {} TEST_CASE("Same test name but with different tags is fine", "[.approvals][other-tag]") {}
#if defined(CATCH_PLATFORM_WINDOWS)
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