Backport fix for SIGSTKSZ

This commit is contained in:
Golubchikov Mihail 2022-07-14 13:10:27 +03:00 committed by Martin Hořeňovský
parent 72df457bab
commit 6c6cfe126a
2 changed files with 204 additions and 79 deletions

View File

@ -9,10 +9,12 @@
#ifndef TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED #ifndef TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
#include <cassert>
#include <stdexcept>
namespace Catch { namespace Catch {
// Report the error condition //! Signals fatal error message to the run context
inline void reportFatal( std::string const& message ) { inline void reportFatal( std::string const& message ) {
IContext& context = Catch::getCurrentContext(); IContext& context = Catch::getCurrentContext();
IResultCapture* resultCapture = context.getResultCapture(); IResultCapture* resultCapture = context.getResultCapture();
@ -27,8 +29,31 @@ namespace Catch {
# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) # if !defined ( CATCH_CONFIG_WINDOWS_SEH )
namespace Catch { namespace Catch {
struct FatalConditionHandler { class FatalConditionHandler {
void reset() {} 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" }, { 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) {
static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { reportFatal(signalDefs[i].name);
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() { // Since we do not support multiple instantiations, we put these
isSet = true; // into global variables and rely on cleaning them up in outlined
// 32k seems enough for Catch to handle stack overflow, // constructors/destructors
// but the value was found experimentally, so there is no strong guarantee static PVOID exceptionHandlerHandle = CATCH_NULL;
guaranteeSize = 32 * 1024;
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 // Register as first handler in current chain
exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
// Pass in guarantee size to be filled if (!exceptionHandlerHandle) {
SetThreadStackGuarantee(&guaranteeSize); 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);
exceptionHandlerHandle = CATCH_NULL;
isSet = false;
} }
} }
~FatalConditionHandler() { void disengage_platform() {
reset(); 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; public:
ULONG FatalConditionHandler::guaranteeSize = 0; FatalConditionHandler() {
PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; 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.";
}
}
// 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 } // namespace Catch
@ -105,8 +149,31 @@ namespace Catch {
# if !defined(CATCH_CONFIG_POSIX_SIGNALS) # if !defined(CATCH_CONFIG_POSIX_SIGNALS)
namespace Catch { namespace Catch {
struct FatalConditionHandler { class FatalConditionHandler {
void reset() {} 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" } { 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 char* altStackMem = CATCH_NULL;
static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; static std::size_t altStackSize = 0;
static stack_t oldSigStack; static stack_t oldSigStack;
static char altStackMem[SIGSTKSZ]; static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)];
static void handleSignal( int sig ) { static void restorePreviousSignalHandlers() {
std::string name = "<unknown signal>"; // We set signal handlers back to the previous ones. Hopefully
for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { // nobody overwrote them in the meantime, and doesn't expect
SignalDefs &def = signalDefs[i]; // their signal handlers to live past ours given that they
if (sig == def.id) { // installed them after ours..
name = def.name; for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
break; sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL);
}
}
reset();
reportFatal(name);
raise( sig );
} }
// Return the old stack
sigaltstack(&oldSigStack, CATCH_NULL);
}
FatalConditionHandler() { static void handleSignal( int sig ) {
isSet = true; 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) {
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; stack_t sigStack;
sigStack.ss_sp = altStackMem; sigStack.ss_sp = altStackMem;
sigStack.ss_size = SIGSTKSZ; 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() { ~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 ) { void engage() {
// Set signals back to previous values -- hopefully nobody overwrote them in the meantime assert(!m_started && "Handler cannot be installed twice.");
for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { m_started = true;
sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); engage_platform();
} }
// Return the old stack
sigaltstack(&oldSigStack, CATCH_NULL); void disengage() {
isSet = false; assert(m_started && "Handler cannot be uninstalled without being installed first");
} m_started = false;
disengage_platform();
} }
}; };
bool FatalConditionHandler::isSet = false; #if defined(__GNUC__)
struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; # pragma GCC diagnostic pop
stack_t FatalConditionHandler::oldSigStack = {}; #endif
char FatalConditionHandler::altStackMem[SIGSTKSZ] = {};
} // namespace Catch } // namespace Catch
@ -197,4 +305,21 @@ namespace Catch {
#endif // not Windows #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 #endif // TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED

View File

@ -363,9 +363,8 @@ namespace Catch {
} }
void invokeActiveTestCase() { void invokeActiveTestCase() {
FatalConditionHandler fatalConditionHandler; // Handle signals FatalConditionHandlerGuard _(&m_fatalConditionhandler);
m_activeTestCase->invoke(); m_activeTestCase->invoke();
fatalConditionHandler.reset();
} }
private: private:
@ -403,6 +402,7 @@ namespace Catch {
std::vector<SectionEndInfo> m_unfinishedSections; std::vector<SectionEndInfo> m_unfinishedSections;
std::vector<ITracker*> m_activeSections; std::vector<ITracker*> m_activeSections;
TrackerContext m_trackerContext; TrackerContext m_trackerContext;
FatalConditionHandler m_fatalConditionhandler;
size_t m_prevPassed; size_t m_prevPassed;
bool m_shouldReportUnexpected; bool m_shouldReportUnexpected;
}; };