diff --git a/include/internal/catch_fatal_condition.hpp b/include/internal/catch_fatal_condition.hpp index 1dcd545d..04bb0fbd 100644 --- a/include/internal/catch_fatal_condition.hpp +++ b/include/internal/catch_fatal_condition.hpp @@ -9,10 +9,12 @@ #ifndef TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED +#include +#include namespace Catch { - // Report the error condition + //! Signals fatal error message to the run context inline void reportFatal( std::string const& message ) { IContext& context = Catch::getCurrentContext(); IResultCapture* resultCapture = context.getResultCapture(); @@ -27,8 +29,31 @@ namespace Catch { # if !defined ( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { - struct FatalConditionHandler { - void reset() {} + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform() {} + void disengage_platform() {} + + public: + // Should also have platform-specific implementations as needed + FatalConditionHandler() {} + ~FatalConditionHandler() {} + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } + + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } }; } @@ -48,53 +73,72 @@ namespace Catch { { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, }; - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { - for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { - reportFatal(signalDefs[i].name); - } + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } - FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = CATCH_NULL; + // 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 = CATCH_NULL; + + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + + void engage_platform() { // Register as first handler in current chain exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } - - static void reset() { - if (isSet) { - // Unregister handler and restore the old guarantee - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = CATCH_NULL; - isSet = false; + if (!exceptionHandlerHandle) { + throw std::runtime_error("Could not register vectored exception handler"); } } - ~FatalConditionHandler() { - reset(); + void disengage_platform() { + if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { + throw std::runtime_error("Could not unregister vectored exception handler"); + } + exceptionHandlerHandle = CATCH_NULL; } - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - bool FatalConditionHandler::isSet = false; - ULONG FatalConditionHandler::guaranteeSize = 0; - PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; + public: + FatalConditionHandler() { + ULONG guaranteeSize = static_cast(32 * 1024); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + ~FatalConditionHandler() {} + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } + + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } + }; } // namespace Catch @@ -105,8 +149,31 @@ namespace Catch { # if !defined(CATCH_CONFIG_POSIX_SIGNALS) namespace Catch { - struct FatalConditionHandler { - void reset() {} + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform() {} + void disengage_platform() {} + + public: + // Should also have platform-specific implementations as needed + FatalConditionHandler() {} + ~FatalConditionHandler() {} + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } + + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } }; } @@ -131,29 +198,56 @@ namespace Catch { { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; - struct FatalConditionHandler { +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif - static bool isSet; - static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; - static stack_t oldSigStack; - static char altStackMem[SIGSTKSZ]; + static char* altStackMem = CATCH_NULL; + static std::size_t altStackSize = 0; + static stack_t oldSigStack; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]; - static void handleSignal( int sig ) { - std::string name = ""; - for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - SignalDefs &def = signalDefs[i]; - if (sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise( sig ); + static void restorePreviousSignalHandlers() { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); } + // Return the old stack + sigaltstack(&oldSigStack, CATCH_NULL); + } - FatalConditionHandler() { - isSet = true; + static void handleSignal( int sig ) { + char const * name = ""; + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + SignalDefs &def = signalDefs[i]; + if (sig == def.id) { + name = def.name; + break; + } + } + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); + raise( sig ); + } + + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + + void engage_platform() { stack_t sigStack; sigStack.ss_sp = altStackMem; sigStack.ss_size = SIGSTKSZ; @@ -168,28 +262,42 @@ namespace Catch { } } + void disengage_platform() { + restorePreviousSignalHandlers(); + } + + public: + FatalConditionHandler() { + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = SIGSTKSZ; + } + altStackMem = new char[altStackSize](); + } ~FatalConditionHandler() { - reset(); + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = CATCH_NULL; } - static void reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); - } - // Return the old stack - sigaltstack(&oldSigStack, CATCH_NULL); - isSet = false; - } + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } + + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); } }; - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; - +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif } // namespace Catch @@ -197,4 +305,21 @@ namespace Catch { #endif // not Windows +namespace Catch { + + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } + }; + +} // end namespace Catch + #endif // TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED diff --git a/include/internal/catch_run_context.hpp b/include/internal/catch_run_context.hpp index 67ff68b6..64bbe935 100644 --- a/include/internal/catch_run_context.hpp +++ b/include/internal/catch_run_context.hpp @@ -363,9 +363,8 @@ namespace Catch { } void invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals + FatalConditionHandlerGuard _(&m_fatalConditionhandler); m_activeTestCase->invoke(); - fatalConditionHandler.reset(); } private: @@ -403,6 +402,7 @@ namespace Catch { std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; size_t m_prevPassed; bool m_shouldReportUnexpected; };