mirror of
https://github.com/catchorg/Catch2.git
synced 2025-01-15 14:48:00 +00:00
Compare commits
4 Commits
cbcab2dbcd
...
313071e8fe
Author | SHA1 | Date | |
---|---|---|---|
|
313071e8fe | ||
|
f9bb2668e4 | ||
|
baf0cd0be4 | ||
|
c0d0a50bdb |
@ -3,6 +3,8 @@
|
||||
# Release notes
|
||||
**Contents**<br>
|
||||
[3.0.1](#301)<br>
|
||||
[2.13.6](#2136)<br>
|
||||
[2.13.5](#2135)<br>
|
||||
[2.13.4](#2134)<br>
|
||||
[2.13.3](#2133)<br>
|
||||
[2.13.2](#2132)<br>
|
||||
@ -172,6 +174,33 @@ new design.
|
||||
* `catch2-with-main` also links in the default main
|
||||
|
||||
|
||||
## 2.13.6
|
||||
|
||||
### Fixes
|
||||
* Disabling all signal handlers no longer breaks compilation (#2212, #2213)
|
||||
|
||||
### Miscellaneous
|
||||
* `catch_discover_tests` should handle escaped semicolon (`;`) better (#2214, #2215)
|
||||
|
||||
|
||||
## 2.13.5
|
||||
|
||||
### Improvements
|
||||
* Detection of MAC and IPHONE platforms has been improved (#2140, #2157)
|
||||
* Added workaround for bug in XLC 16.1.0.1 (#2155)
|
||||
* Add detection for LCC when it is masquerading as GCC (#2199)
|
||||
* Modified posix signal handling so it supports newer libcs (#2178)
|
||||
* `MINSIGSTKSZ` was no longer usable in constexpr context.
|
||||
|
||||
### Fixes
|
||||
* Fixed compilation of benchmarking when `min` and `max` macros are defined (#2159)
|
||||
* Including `windows.h` without `NOMINMAX` remains a really bad idea, don't do it
|
||||
|
||||
### Miscellaneous
|
||||
* The check whether Catch2 is being built as a subproject is now more reliable (#2202, #2204)
|
||||
* The problem was that if the variable name used internally was defined the project including Catch2 as subproject, it would not be properly overwritten for Catch2's CMake.
|
||||
|
||||
|
||||
## 2.13.4
|
||||
|
||||
### Improvements
|
||||
|
@ -5,30 +5,73 @@
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
/** \file
|
||||
* This file provides platform specific implementations of FatalConditionHandler
|
||||
*
|
||||
* This means that there is a lot of conditional compilation, and platform
|
||||
* specific code. Currently, Catch2 supports a dummy handler (if no
|
||||
* handler is desired), and 2 platform specific handlers:
|
||||
* * Windows' SEH
|
||||
* * POSIX signals
|
||||
*
|
||||
* Consequently, various pieces of code below are compiled if either of
|
||||
* the platform specific handlers is enabled, or if none of them are
|
||||
* enabled. It is assumed that both cannot be enabled at the same time,
|
||||
* and doing so should cause a compilation error.
|
||||
*
|
||||
* If another platform specific handler is added, the compile guards
|
||||
* below will need to be updated taking these assumptions into account.
|
||||
*/
|
||||
|
||||
#include <catch2/internal/catch_fatal_condition_handler.hpp>
|
||||
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
#include <catch2/internal/catch_windows_h_proxy.hpp>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
#endif
|
||||
#include <algorithm>
|
||||
|
||||
#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS )
|
||||
|
||||
namespace Catch {
|
||||
|
||||
// If neither SEH nor signal handling is required, the handler impls
|
||||
// do not have to do anything, and can be empty.
|
||||
void FatalConditionHandler::engage_platform() {}
|
||||
void FatalConditionHandler::disengage_platform() {}
|
||||
FatalConditionHandler::FatalConditionHandler() = default;
|
||||
FatalConditionHandler::~FatalConditionHandler() = default;
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS
|
||||
|
||||
#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS )
|
||||
#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time"
|
||||
#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS
|
||||
|
||||
#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
|
||||
|
||||
namespace {
|
||||
// Report the error condition
|
||||
//! Signals fatal error message to the run context
|
||||
void reportFatal( char const * const message ) {
|
||||
Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
|
||||
}
|
||||
}
|
||||
|
||||
#endif // signals/SEH handling
|
||||
//! Minimal size Catch2 needs for its own fatal error handling.
|
||||
//! Picked empirically, so it might not be sufficient on all
|
||||
//! platforms, and for all configurations.
|
||||
constexpr std::size_t minStackSizeForErrors = 32 * 1024;
|
||||
} // end unnamed namespace
|
||||
|
||||
#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS
|
||||
|
||||
#if defined( CATCH_CONFIG_WINDOWS_SEH )
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct SignalDefs { DWORD id; const char* name; };
|
||||
|
||||
// There is no 1-1 mapping between signals and windows exceptions.
|
||||
@ -41,7 +84,7 @@ namespace Catch {
|
||||
{ static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" },
|
||||
};
|
||||
|
||||
LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
|
||||
static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
|
||||
for (auto const& def : signalDefs) {
|
||||
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
|
||||
reportFatal(def.name);
|
||||
@ -52,35 +95,52 @@ namespace Catch {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
FatalConditionHandler::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 = nullptr;
|
||||
// Register as first handler in current chain
|
||||
exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
|
||||
// Pass in guarantee size to be filled
|
||||
SetThreadStackGuarantee(&guaranteeSize);
|
||||
}
|
||||
// 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 = nullptr;
|
||||
|
||||
void FatalConditionHandler::reset() {
|
||||
if (isSet) {
|
||||
RemoveVectoredExceptionHandler(exceptionHandlerHandle);
|
||||
SetThreadStackGuarantee(&guaranteeSize);
|
||||
exceptionHandlerHandle = nullptr;
|
||||
isSet = false;
|
||||
|
||||
// For MSVC, we reserve part of the stack memory for handling
|
||||
// memory overflow structured exception.
|
||||
FatalConditionHandler::FatalConditionHandler() {
|
||||
ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors);
|
||||
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.";
|
||||
}
|
||||
}
|
||||
|
||||
bool FatalConditionHandler::isSet = false;
|
||||
ULONG FatalConditionHandler::guaranteeSize = 0;
|
||||
PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr;
|
||||
// We do not attempt to unset the stack guarantee, because
|
||||
// Windows does not support lowering the stack size guarantee.
|
||||
FatalConditionHandler::~FatalConditionHandler() = default;
|
||||
|
||||
|
||||
} // namespace Catch
|
||||
void FatalConditionHandler::engage_platform() {
|
||||
// Register as first handler in current chain
|
||||
exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
|
||||
if (!exceptionHandlerHandle) {
|
||||
CATCH_RUNTIME_ERROR("Could not register vectored exception handler");
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined( CATCH_CONFIG_POSIX_SIGNALS )
|
||||
void FatalConditionHandler::disengage_platform() {
|
||||
if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) {
|
||||
CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler");
|
||||
}
|
||||
exceptionHandlerHandle = nullptr;
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // CATCH_CONFIG_WINDOWS_SEH
|
||||
|
||||
#if defined( CATCH_CONFIG_POSIX_SIGNALS )
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
@ -89,10 +149,6 @@ namespace Catch {
|
||||
const char* name;
|
||||
};
|
||||
|
||||
// 32kb for the alternate stack seems to be sufficient. However, this value
|
||||
// is experimentally determined, so that's not guaranteed.
|
||||
static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ;
|
||||
|
||||
static SignalDefs signalDefs[] = {
|
||||
{ SIGINT, "SIGINT - Terminal interrupt signal" },
|
||||
{ SIGILL, "SIGILL - Illegal instruction signal" },
|
||||
@ -102,8 +158,32 @@ namespace Catch {
|
||||
{ SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
|
||||
};
|
||||
|
||||
// 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
|
||||
|
||||
void FatalConditionHandler::handleSignal( int sig ) {
|
||||
static char* altStackMem = nullptr;
|
||||
static std::size_t altStackSize = 0;
|
||||
static stack_t oldSigStack{};
|
||||
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], nullptr);
|
||||
}
|
||||
// Return the old stack
|
||||
sigaltstack(&oldSigStack, nullptr);
|
||||
}
|
||||
|
||||
static void handleSignal( int sig ) {
|
||||
char const * name = "<unknown signal>";
|
||||
for (auto const& def : signalDefs) {
|
||||
if (sig == def.id) {
|
||||
@ -111,16 +191,33 @@ namespace Catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
reset();
|
||||
reportFatal(name);
|
||||
// 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::FatalConditionHandler() {
|
||||
isSet = true;
|
||||
assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists");
|
||||
if (altStackSize == 0) {
|
||||
altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors);
|
||||
}
|
||||
altStackMem = new char[altStackSize]();
|
||||
}
|
||||
|
||||
FatalConditionHandler::~FatalConditionHandler() {
|
||||
delete[] altStackMem;
|
||||
// We signal that another instance can be constructed by zeroing
|
||||
// out the pointer.
|
||||
altStackMem = nullptr;
|
||||
}
|
||||
|
||||
void FatalConditionHandler::engage_platform() {
|
||||
stack_t sigStack;
|
||||
sigStack.ss_sp = altStackMem;
|
||||
sigStack.ss_size = sigStackSize;
|
||||
sigStack.ss_size = altStackSize;
|
||||
sigStack.ss_flags = 0;
|
||||
sigaltstack(&sigStack, &oldSigStack);
|
||||
struct sigaction sa = { };
|
||||
@ -132,28 +229,15 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
void FatalConditionHandler::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], nullptr);
|
||||
}
|
||||
// Return the old stack
|
||||
sigaltstack(&oldSigStack, nullptr);
|
||||
isSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FatalConditionHandler::isSet = false;
|
||||
struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
|
||||
stack_t FatalConditionHandler::oldSigStack = {};
|
||||
char FatalConditionHandler::altStackMem[sigStackSize] = {};
|
||||
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
#endif // signals/SEH handling
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
||||
void FatalConditionHandler::disengage_platform() {
|
||||
restorePreviousSignalHandlers();
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // CATCH_CONFIG_POSIX_SIGNALS
|
||||
|
@ -10,57 +10,60 @@
|
||||
|
||||
#include <catch2/internal/catch_platform.hpp>
|
||||
#include <catch2/internal/catch_compiler_capabilities.hpp>
|
||||
#include <catch2/internal/catch_windows_h_proxy.hpp>
|
||||
|
||||
|
||||
#if defined( CATCH_CONFIG_WINDOWS_SEH )
|
||||
#include <cassert>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct FatalConditionHandler {
|
||||
/**
|
||||
* Wrapper for platform-specific fatal error (signals/SEH) handlers
|
||||
*
|
||||
* Tries to be cooperative with other handlers, and not step over
|
||||
* other handlers. This means that unknown structured exceptions
|
||||
* are passed on, previous signal handlers are called, and so on.
|
||||
*
|
||||
* Can only be instantiated once, and assumes that once a signal
|
||||
* is caught, the binary will end up terminating. Thus, there
|
||||
*/
|
||||
class FatalConditionHandler {
|
||||
bool m_started = false;
|
||||
|
||||
static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo);
|
||||
// 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();
|
||||
static void reset();
|
||||
~FatalConditionHandler() { reset(); }
|
||||
~FatalConditionHandler();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void disengage() {
|
||||
assert(m_started && "Handler cannot be uninstalled without being installed first");
|
||||
m_started = false;
|
||||
disengage_platform();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
#elif defined ( CATCH_CONFIG_POSIX_SIGNALS )
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct FatalConditionHandler {
|
||||
|
||||
static bool isSet;
|
||||
static struct sigaction oldSigActions[];
|
||||
static stack_t oldSigStack;
|
||||
static char altStackMem[];
|
||||
|
||||
static void handleSignal( int sig );
|
||||
|
||||
FatalConditionHandler();
|
||||
~FatalConditionHandler() { reset(); }
|
||||
static void reset();
|
||||
//! 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();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
|
||||
#else
|
||||
|
||||
namespace Catch {
|
||||
struct FatalConditionHandler {};
|
||||
}
|
||||
|
||||
#endif
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED
|
||||
|
@ -459,10 +459,10 @@ namespace Catch {
|
||||
}
|
||||
|
||||
void RunContext::invokeActiveTestCase() {
|
||||
// We need to register a handler for signals/structured exceptions
|
||||
// We need to engage a handler for signals/structured exceptions
|
||||
// before running the tests themselves, or the binary can crash
|
||||
// without failed test being reported.
|
||||
FatalConditionHandler _;
|
||||
FatalConditionHandlerGuard _(&m_fatalConditionhandler);
|
||||
m_activeTestCase->invoke();
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <catch2/interfaces/catch_interfaces_runner.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_reporter.hpp>
|
||||
#include <catch2/internal/catch_test_registry.hpp>
|
||||
#include <catch2/internal/catch_fatal_condition_handler.hpp>
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
#include <catch2/catch_message.hpp>
|
||||
#include <catch2/catch_totals.hpp>
|
||||
@ -139,6 +140,7 @@ namespace Catch {
|
||||
std::vector<SectionEndInfo> m_unfinishedSections;
|
||||
std::vector<ITracker*> m_activeSections;
|
||||
TrackerContext m_trackerContext;
|
||||
FatalConditionHandler m_fatalConditionhandler;
|
||||
bool m_lastAssertionPassed = false;
|
||||
bool m_shouldReportUnexpected = true;
|
||||
bool m_includeSuccessfulResults;
|
||||
|
Loading…
Reference in New Issue
Block a user