Compare commits

..

5 Commits

Author SHA1 Message Date
Martin Hořeňovský
756ae05d30 Inline the getResultCapture helper into header.
We still keep the error check in the function, but hide it in an
outlined function inside a .cpp file, to promote inlining of the
retrieval part.

In the future, we should explore two things

1) Skipping over the context retrieval here, allowing direct access.
   I currently do not see a way to do this while keeping the
   "greppability" of mutable vs immutable accesses that is there now,
   but it would help a lot when inlining is not enabled.
2) Removing the error check, to make the function trivially inlinable,
   and without branches.

**runtime difference**

| --------- | Debug | Release |
|:----------|------:|--------:|
| Slow path |  0.98 |    1.07 |
| Fast path |  1.04 |    1.08 |

We lost bit of performance on the assertion slow path in debug mode,
but together with the previous commit, it comes out at net zero.
For other combinations, we see 5-10% perf improvement across the
two commits.
2025-09-27 13:24:46 +02:00
Martin Hořeňovský
a2e41916f2 Keep the main Context instance as static value, not pointer
This allows us to remove the lazy init checks, improving the inlining
potential when retrieving current context, thus slightly improving
the performance of assertions.

**runtime difference**

| --------- | Debug | Release |
|:----------|------:|--------:|
| Slow path |  1.01 |    0.98 |
| Fast path |  1.02 |    1.02 |

There is small slowdown in case of Release build + assertions taking
the slow path, but

1) going through the slow path is rare
2) Given the code change, I believe this to be artifact of the
   optimizer in the old GCC version I am using locally.
2025-09-27 13:24:44 +02:00
Duncan Horn
0e772cc0d2 Allow composability of unhandled exception filters (#3033)
The change is very simple. If a handler previously existed, Catch2 will invoke it after printing out its output. I've also updated the comment to better reflect that it's returning EXCEPTION_CONTINUE_SEARCH even in scenarios where the exception is one that the library cares about.
2025-09-27 11:09:34 +02:00
Mason Wilie
3bcd0a4e74 Changed GetOptions to use sequence 2025-09-25 21:06:10 +02:00
Duncan Horn
f7e7fa0983 Fix ReusableStringStream to access the container under the lock (#3031)
Commit 582200a made `ReusableStringStream`'s index reservation thread safe, however it's still accessing the `m_streams` vector outside the lock. This makes it so that the `add` call returns the pointer in addition to the index so that the lock doesn't need to get acquired again until destruction.

The issue with accessing `m_streams` outside the lock is that `add` can call `push_back` on the vector, which might re-allocate. If this re-allocation occurs concurrently with anther thread trying to index into this array, you get UB (typically a null pointer read).
2025-09-25 13:31:49 +02:00
7 changed files with 28 additions and 26 deletions

View File

@@ -78,5 +78,5 @@ WarningsAsErrors: >-
readability-duplicate-include, readability-duplicate-include,
HeaderFilterRegex: '.*\.(c|cxx|cpp)$' HeaderFilterRegex: '.*\.(c|cxx|cpp)$'
FormatStyle: none FormatStyle: none
CheckOptions: {} CheckOptions: []
... ...

View File

@@ -7,7 +7,14 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
#include <catch2/interfaces/catch_interfaces_capture.hpp> #include <catch2/interfaces/catch_interfaces_capture.hpp>
#include <catch2/internal/catch_enforce.hpp>
namespace Catch { namespace Catch {
namespace Detail {
void missingCaptureInstance() {
CATCH_INTERNAL_ERROR( "No result capture instance" );
}
} // namespace Detail
IResultCapture::~IResultCapture() = default; IResultCapture::~IResultCapture() = default;
} } // namespace Catch

View File

@@ -111,8 +111,6 @@ namespace Catch {
} else { } else {
Detail::missingCaptureInstance(); Detail::missingCaptureInstance();
} }
} }
} }

View File

@@ -32,7 +32,6 @@ namespace Catch {
m_resultCapture = resultCapture; m_resultCapture = resultCapture;
} }
constexpr void setConfig( IConfig const* config ) { m_config = config; } constexpr void setConfig( IConfig const* config ) { m_config = config; }
}; };
Context& getCurrentMutableContext(); Context& getCurrentMutableContext();

View File

@@ -86,23 +86,27 @@ namespace Catch {
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
}; };
// Since we do not support multiple instantiations, we put these
// into global variables and rely on cleaning them up in outlined
// constructors/destructors
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
for (auto const& def : signalDefs) { for (auto const& def : signalDefs) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
reportFatal(def.name); reportFatal(def.name);
} }
} }
// If its not an exception we care about, pass it along. // If a filter was previously registered, invoke it
if (previousTopLevelExceptionFilter) {
return previousTopLevelExceptionFilter(ExceptionInfo);
}
// Otherwise, pass along all exceptions.
// This stops us from eating debugger breaks etc. // This stops us from eating debugger breaks etc.
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
// Since we do not support multiple instantiations, we put these
// into global variables and rely on cleaning them up in outlined
// constructors/destructors
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
// For MSVC, we reserve part of the stack memory for handling // For MSVC, we reserve part of the stack memory for handling
// memory overflow structured exception. // memory overflow structured exception.
FatalConditionHandler::FatalConditionHandler() { FatalConditionHandler::FatalConditionHandler() {

View File

@@ -12,6 +12,7 @@
#include <cstdio> #include <cstdio>
#include <sstream> #include <sstream>
#include <tuple>
#include <vector> #include <vector>
namespace Catch { namespace Catch {
@@ -23,16 +24,16 @@ namespace Catch {
std::ostringstream m_referenceStream; // Used for copy state/ flags from std::ostringstream m_referenceStream; // Used for copy state/ flags from
Detail::Mutex m_mutex; Detail::Mutex m_mutex;
auto add() -> std::size_t { auto add() -> std::pair<std::size_t, std::ostringstream*> {
Detail::LockGuard _( m_mutex ); Detail::LockGuard _( m_mutex );
if( m_unused.empty() ) { if( m_unused.empty() ) {
m_streams.push_back( Detail::make_unique<std::ostringstream>() ); m_streams.push_back( Detail::make_unique<std::ostringstream>() );
return m_streams.size()-1; return { m_streams.size()-1, m_streams.back().get() };
} }
else { else {
auto index = m_unused.back(); auto index = m_unused.back();
m_unused.pop_back(); m_unused.pop_back();
return index; return { index, m_streams[index].get() };
} }
} }
@@ -46,10 +47,10 @@ namespace Catch {
} }
}; };
ReusableStringStream::ReusableStringStream() ReusableStringStream::ReusableStringStream() {
: m_index( Singleton<StringStreams>::getMutable().add() ), std::tie( m_index, m_oss ) =
m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) Singleton<StringStreams>::getMutable().add();
{} }
ReusableStringStream::~ReusableStringStream() { ReusableStringStream::~ReusableStringStream() {
static_cast<std::ostringstream*>( m_oss )->str(""); static_cast<std::ostringstream*>( m_oss )->str("");

View File

@@ -21,7 +21,6 @@
#include <catch2/internal/catch_assertion_handler.hpp> #include <catch2/internal/catch_assertion_handler.hpp>
#include <catch2/internal/catch_test_failure_exception.hpp> #include <catch2/internal/catch_test_failure_exception.hpp>
#include <catch2/internal/catch_result_type.hpp> #include <catch2/internal/catch_result_type.hpp>
#include <catch2/interfaces/catch_interfaces_capture.hpp>
#include <cassert> #include <cassert>
#include <algorithm> #include <algorithm>
@@ -840,10 +839,4 @@ namespace Catch {
return getCurrentContext().getConfig()->rngSeed(); return getCurrentContext().getConfig()->rngSeed();
} }
namespace Detail {
void missingCaptureInstance() {
CATCH_INTERNAL_ERROR( "No result capture instance" );
}
} // namespace Detail
} }