Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/catch2/internal/catch_result_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ namespace Catch {
constexpr bool isFalseTest( int flags ) {
return ( flags & ResultDisposition::FalseTest ) != 0;
}
constexpr bool shouldTerminateOnFailure( int flags ) {
return ( flags & ResultDisposition::Normal ) != 0;
}
constexpr bool shouldSuppressFailure( int flags ) {
return ( flags & ResultDisposition::SuppressFail ) != 0;
}
Expand Down
108 changes: 108 additions & 0 deletions src/catch2/internal/catch_test_macro_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@

#include <catch2/catch_user_config.hpp>
#include <catch2/internal/catch_assertion_handler.hpp>
#include <catch2/internal/catch_config_static_analysis_support.hpp>
#include <catch2/internal/catch_preprocessor_internal_stringify.hpp>
#include <catch2/internal/catch_result_type.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_unreachable.hpp>

namespace Catch {
namespace Detail {
Expand Down Expand Up @@ -44,6 +47,8 @@ namespace Catch {

#endif

#if !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT )

///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \
do { /* NOLINT(bugprone-infinite-loop) */ \
Expand All @@ -60,6 +65,25 @@ namespace Catch {
} while( (void)0, (false) && static_cast<const bool&>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look
// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.

#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

///////////////////////////////////////////////////////////////////////////////
# define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \
do { \
const bool catchInternalAssertionResult = static_cast<bool>( __VA_ARGS__ ); \
if ( Catch::shouldTerminateOnFailure( resultDisposition ) ) { \
if ( Catch::isFalseTest( resultDisposition ) ) { \
if ( catchInternalAssertionResult ) { Catch::Detail::Unreachable(); } \
} else { \
if ( !catchInternalAssertionResult ) { Catch::Detail::Unreachable(); } \
} \
} \
} while ( false )

#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

#if !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT )

///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \
INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
Expand All @@ -70,6 +94,20 @@ namespace Catch {
INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \
if( !Catch::Detail::lastAssertionPassed() )

#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

///////////////////////////////////////////////////////////////////////////////
# define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \
if ( __VA_ARGS__ )

///////////////////////////////////////////////////////////////////////////////
# define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \
if ( !( __VA_ARGS__ ) )

#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

#if !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT )

///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \
do { \
Expand All @@ -87,6 +125,27 @@ namespace Catch {
catchAssertionHandler.complete(); \
} while( false )

#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

///////////////////////////////////////////////////////////////////////////////
# define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \
do { \
try { \
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \
static_cast<void>(__VA_ARGS__); \
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
} catch ( ... ) { \
if ( Catch::shouldTerminateOnFailure( resultDisposition ) ) { \
Catch::Detail::Unreachable(); \
} \
} \
} while ( false )

#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

#if !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT )

///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \
do { \
Expand All @@ -108,6 +167,31 @@ namespace Catch {
catchAssertionHandler.complete(); \
} while( false )

#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

///////////////////////////////////////////////////////////////////////////////
# define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \
do { \
bool catchInternalThrew = false; \
try { \
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \
CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \
static_cast<void>(__VA_ARGS__); \
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
} catch ( ... ) { \
catchInternalThrew = true; \
} \
if ( !catchInternalThrew && \
Catch::shouldTerminateOnFailure( resultDisposition ) ) { \
Catch::Detail::Unreachable(); \
} \
} while ( false )

#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

#if !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT )

///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \
do { \
Expand All @@ -132,6 +216,30 @@ namespace Catch {
catchAssertionHandler.complete(); \
} while( false )

#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT

///////////////////////////////////////////////////////////////////////////////
# define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \
do { \
bool catchInternalCaughtExpected = false; \
try { \
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \
CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \
static_cast<void>(expr); \
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \
} catch ( exceptionType const& ) { \
catchInternalCaughtExpected = true; \
} catch ( ... ) { \
} \
if ( !catchInternalCaughtExpected && \
Catch::shouldTerminateOnFailure( resultDisposition ) ) { \
Catch::Detail::Unreachable(); \
} \
} while ( false )

#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT



///////////////////////////////////////////////////////////////////////////////
Expand Down
18 changes: 18 additions & 0 deletions tests/ExtraTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,20 @@ set_tests_properties(CompileConfiguration::FastCompile
PASS_REGULAR_EXPRESSION "test cases: 6 \\| 1 passed \\| 1 failed \\| 4 failed as expected\nassertions: 13 \\| 6 passed \\| 2 failed \\| 5 failed as expected"
)

# The static analysis mode is not runnable, so compiling
# this one is the whole test. GCC cannot compile the mode at all.
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_library(StaticAnalysisSupport OBJECT ${TESTS_DIR}/X08-StaticAnalysisSupport.cpp)
target_link_libraries(StaticAnalysisSupport PRIVATE Catch2::Catch2WithMain)
target_compile_definitions(StaticAnalysisSupport
PRIVATE
CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT
)
# The mode's `TEST_CASE` expansion uses `[[maybe_unused]]`, which is
# a C++17 extension in C++14, and the build compiles with -Wpedantic.
target_compile_features(StaticAnalysisSupport PRIVATE cxx_std_17)
endif()

# This sets up a one-off executable that compiles against the amalgamated
# files, and then runs it for a super simple check that the amalgamated
# files are usable.
Expand Down Expand Up @@ -654,6 +668,10 @@ set(EXTRA_TEST_BINARIES
# DebugBreakMacros
)

if(TARGET StaticAnalysisSupport)
list(APPEND EXTRA_TEST_BINARIES StaticAnalysisSupport)
endif()

# Notice that we are modifying EXTRA_TEST_BINARIES destructively, do not
# use it after this point!
list(FILTER EXTRA_TEST_BINARIES EXCLUDE REGEX "DisabledExceptions.*")
Expand Down
102 changes: 102 additions & 0 deletions tests/ExtraTests/X08-StaticAnalysisSupport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0

/**\file
* Test that the macros compile with
* `CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT`.
*
* The expansions in this mode are meant to be scanned, not run, and use
* symbols that are left undefined on purpose.
*
* The compilation itself is the test.
*/

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_exception.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>

#include <stdexcept>
#include <string>

// Left undefined on purpose. Their results are opaque to the compiler.
int const* make_pointer();
std::string make_string();

namespace {

[[noreturn]]
void throws() {
throw std::runtime_error( "some exception" );
}
void doesnt_throw() {}

} // namespace

TEST_CASE( "REQUIRE and CHECK" ) {
int const* const ptr = make_pointer();

REQUIRE( ptr );
CHECK( *ptr == 42 );

REQUIRE_FALSE( ptr == nullptr );
CHECK_FALSE( ptr == nullptr );
CHECK_NOFAIL( *ptr == 42 );
}

TEST_CASE( "CHECKED_IF and CHECKED_ELSE" ) {
int const* const ptr = make_pointer();

CHECKED_IF( ptr ) {
CHECK( *ptr == 42 );
}
CHECKED_ELSE( ptr ) {
CHECK_FALSE( ptr );
}
}

TEST_CASE( "Exception assertions" ) {
REQUIRE_NOTHROW( doesnt_throw() );
CHECK_NOTHROW( doesnt_throw() );

REQUIRE_THROWS( throws() );
CHECK_THROWS( throws() );

REQUIRE_THROWS_AS( throws(), std::runtime_error );
CHECK_THROWS_AS( throws(), std::runtime_error );
}

// The matcher-based assertions are not modelled for static analysis atm,
// but they still have to compile in this mode.
TEST_CASE( "Matcher assertions" ) {
using namespace Catch::Matchers;

REQUIRE_THAT( make_string(), StartsWith( "some" ) );
CHECK_THAT( make_string(), Equals( "some string" ) );

REQUIRE_THROWS_WITH( throws(), "some exception" );
CHECK_THROWS_WITH( throws(), ContainsSubstring( "exception" ) );

REQUIRE_THROWS_MATCHES(
throws(), std::runtime_error, Message( "some exception" ) );
CHECK_THROWS_MATCHES(
throws(), std::runtime_error, Message( "some exception" ) );
}

TEST_CASE( "Non-assertion macros" ) {
STATIC_REQUIRE( 1 + 1 == 2 );
STATIC_REQUIRE_FALSE( 1 + 1 == 3 );
STATIC_CHECK( 1 + 1 == 2 );
STATIC_CHECK_FALSE( 1 + 1 == 3 );

SUCCEED( "succeeded" );

if ( make_pointer() ) {
FAIL( "failed" );
}
SKIP( "skipped" );
}