mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 12:17:11 +01:00 
			
		
		
		
	Annotate matcher combinators with CATCH_ATTR_LIFETIMEBOUND
The matcher combinators do not take ownership of the matchers
being combined, which can catch the users off-guard, when code like
this
```cpp
using Catch::Matchers::EndsWith;
using Catch::Matchers::ContainsSubstring;
auto combinedMatcher = EndsWith("as a service")
                       && ContainsSubstring("web scale");
REQUIRE_THAT( getSomeString(), combinedMatcher );
```
leads to use-after-free, as the `combinedMatcher` refers to matcher
temporaries that no longer exists. With this commit, users of Clang,
MSVC or other compiler that understands the `lifetimebound` attribute,
should get a warning.
			
			
This commit is contained in:
		| @@ -10,6 +10,7 @@ | ||||
|  | ||||
| #include <catch2/matchers/internal/catch_matchers_impl.hpp> | ||||
| #include <catch2/internal/catch_move_and_forward.hpp> | ||||
| #include <catch2/internal/catch_lifetimebound.hpp> | ||||
|  | ||||
| #include <string> | ||||
| #include <vector> | ||||
| @@ -79,11 +80,15 @@ namespace Matchers { | ||||
|                 return description; | ||||
|             } | ||||
|  | ||||
|             friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase<ArgT> const& rhs) { | ||||
|             friend MatchAllOf operator&&( MatchAllOf&& lhs, | ||||
|                                           MatcherBase<ArgT> const& rhs | ||||
|                                               CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 lhs.m_matchers.push_back(&rhs); | ||||
|                 return CATCH_MOVE(lhs); | ||||
|             } | ||||
|             friend MatchAllOf operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf&& rhs) { | ||||
|             friend MatchAllOf | ||||
|             operator&&( MatcherBase<ArgT> const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                         MatchAllOf&& rhs ) { | ||||
|                 rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); | ||||
|                 return CATCH_MOVE(rhs); | ||||
|             } | ||||
| @@ -131,11 +136,15 @@ namespace Matchers { | ||||
|                 return description; | ||||
|             } | ||||
|  | ||||
|             friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase<ArgT> const& rhs) { | ||||
|             friend MatchAnyOf operator||( MatchAnyOf&& lhs, | ||||
|                                           MatcherBase<ArgT> const& rhs | ||||
|                                               CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 lhs.m_matchers.push_back(&rhs); | ||||
|                 return CATCH_MOVE(lhs); | ||||
|             } | ||||
|             friend MatchAnyOf operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf&& rhs) { | ||||
|             friend MatchAnyOf | ||||
|             operator||( MatcherBase<ArgT> const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                         MatchAnyOf&& rhs ) { | ||||
|                 rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); | ||||
|                 return CATCH_MOVE(rhs); | ||||
|             } | ||||
| @@ -155,7 +164,8 @@ namespace Matchers { | ||||
|             MatcherBase<ArgT> const& m_underlyingMatcher; | ||||
|  | ||||
|         public: | ||||
|             explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ): | ||||
|             explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher | ||||
|                                      CATCH_ATTR_LIFETIMEBOUND ): | ||||
|                 m_underlyingMatcher( underlyingMatcher ) | ||||
|             {} | ||||
|  | ||||
| @@ -171,16 +181,22 @@ namespace Matchers { | ||||
|     } // namespace Detail | ||||
|  | ||||
|     template <typename T> | ||||
|     Detail::MatchAllOf<T> operator&& (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) { | ||||
|     Detail::MatchAllOf<T> | ||||
|     operator&&( MatcherBase<T> const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherBase<T> const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return Detail::MatchAllOf<T>{} && lhs && rhs; | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     Detail::MatchAnyOf<T> operator|| (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) { | ||||
|     Detail::MatchAnyOf<T> | ||||
|     operator||( MatcherBase<T> const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherBase<T> const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return Detail::MatchAnyOf<T>{} || lhs || rhs; | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|     Detail::MatchNotOf<T> operator! (MatcherBase<T> const& matcher) { | ||||
|     Detail::MatchNotOf<T> | ||||
|     operator!( MatcherBase<T> const& matcher CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return Detail::MatchNotOf<T>{ matcher }; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include <catch2/matchers/catch_matchers.hpp> | ||||
| #include <catch2/internal/catch_stringref.hpp> | ||||
| #include <catch2/internal/catch_move_and_forward.hpp> | ||||
| #include <catch2/internal/catch_lifetimebound.hpp> | ||||
| #include <catch2/internal/catch_logical_traits.hpp> | ||||
|  | ||||
| #include <array> | ||||
| @@ -114,7 +115,8 @@ namespace Matchers { | ||||
|             MatchAllOfGeneric(MatchAllOfGeneric&&) = default; | ||||
|             MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default; | ||||
|  | ||||
|             MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} | ||||
|             MatchAllOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND) | ||||
|                 : m_matchers{ {std::addressof(matchers)...} } {} | ||||
|             explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {} | ||||
|  | ||||
|             template<typename Arg> | ||||
| @@ -136,8 +138,8 @@ namespace Matchers { | ||||
|             template<typename... MatchersRHS> | ||||
|             friend | ||||
|             MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && ( | ||||
|                     MatchAllOfGeneric<MatcherTs...>&& lhs, | ||||
|                     MatchAllOfGeneric<MatchersRHS...>&& rhs) { | ||||
|                 MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatchAllOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; | ||||
|             } | ||||
|  | ||||
| @@ -145,8 +147,8 @@ namespace Matchers { | ||||
|             template<typename MatcherRHS> | ||||
|             friend std::enable_if_t<is_matcher_v<MatcherRHS>, | ||||
|             MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && ( | ||||
|                     MatchAllOfGeneric<MatcherTs...>&& lhs, | ||||
|                     MatcherRHS const& rhs) { | ||||
|                 MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))}; | ||||
|             } | ||||
|  | ||||
| @@ -154,8 +156,8 @@ namespace Matchers { | ||||
|             template<typename MatcherLHS> | ||||
|             friend std::enable_if_t<is_matcher_v<MatcherLHS>, | ||||
|             MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && ( | ||||
|                     MatcherLHS const& lhs, | ||||
|                     MatchAllOfGeneric<MatcherTs...>&& rhs) { | ||||
|                 MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatchAllOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; | ||||
|             } | ||||
|         }; | ||||
| @@ -169,7 +171,8 @@ namespace Matchers { | ||||
|             MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default; | ||||
|             MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default; | ||||
|  | ||||
|             MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} | ||||
|             MatchAnyOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND) | ||||
|                 : m_matchers{ {std::addressof(matchers)...} } {} | ||||
|             explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {} | ||||
|  | ||||
|             template<typename Arg> | ||||
| @@ -190,8 +193,8 @@ namespace Matchers { | ||||
|             //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case | ||||
|             template<typename... MatchersRHS> | ||||
|             friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || ( | ||||
|                     MatchAnyOfGeneric<MatcherTs...>&& lhs, | ||||
|                     MatchAnyOfGeneric<MatchersRHS...>&& rhs) { | ||||
|                 MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatchAnyOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; | ||||
|             } | ||||
|  | ||||
| @@ -199,8 +202,8 @@ namespace Matchers { | ||||
|             template<typename MatcherRHS> | ||||
|             friend std::enable_if_t<is_matcher_v<MatcherRHS>, | ||||
|             MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || ( | ||||
|                     MatchAnyOfGeneric<MatcherTs...>&& lhs, | ||||
|                     MatcherRHS const& rhs) { | ||||
|                     MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                     MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))}; | ||||
|             } | ||||
|  | ||||
| @@ -208,8 +211,8 @@ namespace Matchers { | ||||
|             template<typename MatcherLHS> | ||||
|             friend std::enable_if_t<is_matcher_v<MatcherLHS>, | ||||
|             MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || ( | ||||
|                 MatcherLHS const& lhs, | ||||
|                 MatchAnyOfGeneric<MatcherTs...>&& rhs) { | ||||
|                 MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatchAnyOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND) { | ||||
|                 return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; | ||||
|             } | ||||
|         }; | ||||
| @@ -225,7 +228,8 @@ namespace Matchers { | ||||
|             MatchNotOfGeneric(MatchNotOfGeneric&&) = default; | ||||
|             MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default; | ||||
|  | ||||
|             explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {} | ||||
|             explicit MatchNotOfGeneric(MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND) | ||||
|                 : m_matcher{matcher} {} | ||||
|  | ||||
|             template<typename Arg> | ||||
|             bool match(Arg&& arg) const { | ||||
| @@ -237,7 +241,9 @@ namespace Matchers { | ||||
|             } | ||||
|  | ||||
|             //! Negating negation can just unwrap and return underlying matcher | ||||
|             friend MatcherT const& operator ! (MatchNotOfGeneric<MatcherT> const& matcher) { | ||||
|             friend MatcherT const& | ||||
|             operator!( MatchNotOfGeneric<MatcherT> const& matcher | ||||
|                            CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|                 return matcher.m_matcher; | ||||
|             } | ||||
|         }; | ||||
| @@ -247,20 +253,22 @@ namespace Matchers { | ||||
|     // compose only generic matchers | ||||
|     template<typename MatcherLHS, typename MatcherRHS> | ||||
|     std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>> | ||||
|         operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { | ||||
|     operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return { lhs, rhs }; | ||||
|     } | ||||
|  | ||||
|     template<typename MatcherLHS, typename MatcherRHS> | ||||
|     std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>> | ||||
|         operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { | ||||
|     operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return { lhs, rhs }; | ||||
|     } | ||||
|  | ||||
|     //! Wrap provided generic matcher in generic negator | ||||
|     template<typename MatcherT> | ||||
|     std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<MatcherT>> | ||||
|         operator ! (MatcherT const& matcher) { | ||||
|     operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return Detail::MatchNotOfGeneric<MatcherT>{matcher}; | ||||
|     } | ||||
|  | ||||
| @@ -268,25 +276,29 @@ namespace Matchers { | ||||
|     // compose mixed generic and non-generic matchers | ||||
|     template<typename MatcherLHS, typename ArgRHS> | ||||
|     std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>> | ||||
|         operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) { | ||||
|     operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherBase<ArgRHS> const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return { lhs, rhs }; | ||||
|     } | ||||
|  | ||||
|     template<typename ArgLHS, typename MatcherRHS> | ||||
|     std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>> | ||||
|         operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) { | ||||
|     operator&&( MatcherBase<ArgLHS> const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return { lhs, rhs }; | ||||
|     } | ||||
|  | ||||
|     template<typename MatcherLHS, typename ArgRHS> | ||||
|     std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>> | ||||
|         operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) { | ||||
|     operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherBase<ArgRHS> const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return { lhs, rhs }; | ||||
|     } | ||||
|  | ||||
|     template<typename ArgLHS, typename MatcherRHS> | ||||
|     std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>> | ||||
|         operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) { | ||||
|     operator||( MatcherBase<ArgLHS> const& lhs CATCH_ATTR_LIFETIMEBOUND, | ||||
|                 MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { | ||||
|         return { lhs, rhs }; | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský