mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-11-04 05:59:32 +01:00 
			
		
		
		
	Backport fix for SIGSTKSZ
This commit is contained in:
		
				
					committed by
					
						
						Martin Hořeňovský
					
				
			
			
				
	
			
			
			
						parent
						
							72df457bab
						
					
				
				
					commit
					6c6cfe126a
				
			@@ -9,10 +9,12 @@
 | 
			
		||||
#ifndef TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
 | 
			
		||||
#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
 | 
			
		||||
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
 | 
			
		||||
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,8 +73,6 @@ 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) {
 | 
			
		||||
@@ -61,41 +84,62 @@ namespace Catch {
 | 
			
		||||
        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);
 | 
			
		||||
            if (!exceptionHandlerHandle) {
 | 
			
		||||
                throw std::runtime_error("Could not register vectored exception handler");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static void reset() {
 | 
			
		||||
            if (isSet) {
 | 
			
		||||
                // Unregister handler and restore the old guarantee
 | 
			
		||||
                RemoveVectoredExceptionHandler(exceptionHandlerHandle);
 | 
			
		||||
                SetThreadStackGuarantee(&guaranteeSize);
 | 
			
		||||
        void disengage_platform() {
 | 
			
		||||
            if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) {
 | 
			
		||||
                throw std::runtime_error("Could not unregister vectored exception handler");
 | 
			
		||||
            }
 | 
			
		||||
            exceptionHandlerHandle = CATCH_NULL;
 | 
			
		||||
                isSet = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        FatalConditionHandler() {
 | 
			
		||||
            ULONG guaranteeSize = static_cast<ULONG>(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.";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ~FatalConditionHandler() {
 | 
			
		||||
            reset();
 | 
			
		||||
        // 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();
 | 
			
		||||
        }
 | 
			
		||||
    private:
 | 
			
		||||
        static bool isSet;
 | 
			
		||||
        static ULONG guaranteeSize;
 | 
			
		||||
        static PVOID exceptionHandlerHandle;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    bool FatalConditionHandler::isSet = false;
 | 
			
		||||
    ULONG FatalConditionHandler::guaranteeSize = 0;
 | 
			
		||||
    PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL;
 | 
			
		||||
 | 
			
		||||
} // namespace Catch
 | 
			
		||||
 | 
			
		||||
#  endif // CATCH_CONFIG_WINDOWS_SEH
 | 
			
		||||
@@ -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,15 +198,33 @@ 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 char* altStackMem = CATCH_NULL;
 | 
			
		||||
    static std::size_t altStackSize = 0;
 | 
			
		||||
    static stack_t oldSigStack;
 | 
			
		||||
        static char altStackMem[SIGSTKSZ];
 | 
			
		||||
    static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)];
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void handleSignal( int sig ) {
 | 
			
		||||
            std::string name = "<unknown signal>";
 | 
			
		||||
        char const * name = "<unknown signal>";
 | 
			
		||||
        for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
 | 
			
		||||
            SignalDefs &def = signalDefs[i];
 | 
			
		||||
            if (sig == def.id) {
 | 
			
		||||
@@ -147,13 +232,22 @@ namespace Catch {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            reset();
 | 
			
		||||
        // 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 );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        FatalConditionHandler() {
 | 
			
		||||
            isSet = true;
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<SectionEndInfo> m_unfinishedSections;
 | 
			
		||||
        std::vector<ITracker*> m_activeSections;
 | 
			
		||||
        TrackerContext m_trackerContext;
 | 
			
		||||
        FatalConditionHandler m_fatalConditionhandler;
 | 
			
		||||
        size_t m_prevPassed;
 | 
			
		||||
        bool m_shouldReportUnexpected;
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user