From bf93779b4f3d2c4c01263a856a4d35c7e37f24a5 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Tue, 17 Jan 2023 09:33:19 +0100 Subject: [PATCH] Update catch.hpp (fixes #1856) See also catchorg/Catch2#2627 --- extras/tests/catch/catch.hpp | 421 +++++++++++++++++++++++------------ 1 file changed, 279 insertions(+), 142 deletions(-) diff --git a/extras/tests/catch/catch.hpp b/extras/tests/catch/catch.hpp index fdb046fe..fe13018d 100644 --- a/extras/tests/catch/catch.hpp +++ b/extras/tests/catch/catch.hpp @@ -1,6 +1,6 @@ /* * Catch v1.12.2 - * Generated: 2018-05-14 15:10:01.112442 + * Generated: 2023-01-17 08:45:40.979381 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -214,7 +214,7 @@ //////////////////////////////////////////////////////////////////////////////// // Use variadic macros if the compiler supports them -#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ +#if ( defined _MSC_VER && _MSC_VER >= 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) @@ -2129,6 +2129,9 @@ namespace Catch{ #define CATCH_TRAP() \ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ : : : "memory","r0","r3","r4" ) /* NOLINT */ + #elif defined(__aarch64__) + // Backport of https://github.com/catchorg/Catch2/commit/a25c1a24af8bffd35727a888a307ff0280cf9387 + #define CATCH_TRAP() __asm__(".inst 0xd4200000") #else #define CATCH_TRAP() __asm__("int $3\n" : : /* NOLINT */ ) #endif @@ -6392,18 +6395,21 @@ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS // #included from: catch_fatal_condition.hpp #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED +#include +#include + namespace Catch { - // Report the error condition - inline void reportFatal( std::string const& message ) { - IContext& context = Catch::getCurrentContext(); - IResultCapture* resultCapture = context.getResultCapture(); - resultCapture->handleFatalErrorCondition( message ); - } +//! Signals fatal error message to the run context +inline void reportFatal(std::string const &message) { + IContext &context = Catch::getCurrentContext(); + IResultCapture *resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition(message); +} } // namespace Catch -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// +#if defined(CATCH_PLATFORM_WINDOWS) ///////////////////////////////////////// // #included from: catch_windows_h_proxy.h #define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED @@ -6429,176 +6435,307 @@ namespace Catch { #endif -# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) +#if !defined(CATCH_CONFIG_WINDOWS_SEH) namespace Catch { - struct FatalConditionHandler { - void reset() {} - }; +class FatalConditionHandler { + bool m_started; + + // 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() : m_started(false) {} + ~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 + +#else // CATCH_CONFIG_WINDOWS_SEH is defined + +namespace Catch { + +struct SignalDefs { + DWORD id; + const char *name; +}; +extern SignalDefs signalDefs[]; +// There is no 1-1 mapping between signals and windows exceptions. +// Windows can easily distinguish between SO and SigSegV, +// but SigInt, SigTerm, etc are handled differently. +SignalDefs signalDefs[] = { + {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, + {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, + {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, + {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, +}; + +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; } -# else // CATCH_CONFIG_WINDOWS_SEH is defined +// 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; -namespace Catch { +class FatalConditionHandler { + bool m_started; - struct SignalDefs { DWORD id; const char* name; }; - extern SignalDefs signalDefs[]; - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - SignalDefs signalDefs[] = { - { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, - { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, - { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, - { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, - }; + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. - struct FatalConditionHandler { + void engage_platform() { + // Register as first handler in current chain + exceptionHandlerHandle = + AddVectoredExceptionHandler(1, handleVectoredException); + if (!exceptionHandlerHandle) { + throw std::runtime_error("Could not register vectored exception handler"); + } + } - 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; - } + void disengage_platform() { + if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { + throw std::runtime_error( + "Could not unregister vectored exception handler"); + } + exceptionHandlerHandle = CATCH_NULL; + } - 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; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } +public: + FatalConditionHandler() : m_started(false) { + 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."; + } + } - static void reset() { - if (isSet) { - // Unregister handler and restore the old guarantee - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = CATCH_NULL; - isSet = false; - } - } + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + ~FatalConditionHandler() {} - ~FatalConditionHandler() { - reset(); - } - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } - bool FatalConditionHandler::isSet = false; - ULONG FatalConditionHandler::guaranteeSize = 0; - PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; + void disengage() { + assert(m_started && + "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } +}; } // namespace Catch -# endif // CATCH_CONFIG_WINDOWS_SEH +#endif // CATCH_CONFIG_WINDOWS_SEH #else // Not Windows - assumed to be POSIX compatible ////////////////////////// -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) +#if !defined(CATCH_CONFIG_POSIX_SIGNALS) namespace Catch { - struct FatalConditionHandler { - void reset() {} - }; -} +class FatalConditionHandler { + bool m_started; -# else // CATCH_CONFIG_POSIX_SIGNALS is defined + // 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() : m_started(false) {} + ~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 + +#else // CATCH_CONFIG_POSIX_SIGNALS is defined #include namespace Catch { - struct SignalDefs { - int id; - const char* name; - }; - extern SignalDefs signalDefs[]; - SignalDefs signalDefs[] = { - { SIGINT, "SIGINT - Terminal interrupt signal" }, - { SIGILL, "SIGILL - Illegal instruction signal" }, - { SIGFPE, "SIGFPE - Floating point error signal" }, - { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, - { SIGTERM, "SIGTERM - Termination request signal" }, - { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } - }; +struct SignalDefs { + int id; + const char *name; +}; +extern SignalDefs signalDefs[]; +SignalDefs signalDefs[] = { + {SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {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; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = SIGSTKSZ; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = { 0 }; +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); +} - sa.sa_handler = handleSignal; - sa.sa_flags = SA_ONSTACK; - for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } +class FatalConditionHandler { + bool m_started; - ~FatalConditionHandler() { - reset(); - } - 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; - } - } - }; + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; + void engage_platform() { + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = SIGSTKSZ; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {0}; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + void disengage_platform() { restorePreviousSignalHandlers(); } + +public: + FatalConditionHandler() : m_started(false) { + assert(!altStackMem && + "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = SIGSTKSZ; + } + altStackMem = new char[altStackSize](); + } + + ~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = CATCH_NULL; + } + + 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(); + } +}; + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif } // namespace Catch -# endif // CATCH_CONFIG_POSIX_SIGNALS +#endif // CATCH_CONFIG_POSIX_SIGNALS #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 + #include #include #include @@ -6938,9 +7075,8 @@ namespace Catch { } void invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals + FatalConditionHandlerGuard _(&m_fatalConditionhandler); m_activeTestCase->invoke(); - fatalConditionHandler.reset(); } private: @@ -6978,6 +7114,7 @@ namespace Catch { std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; size_t m_prevPassed; bool m_shouldReportUnexpected; };