From 3acb8b30f1ce0c801de0e3880ea1f6467dd0105c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Wed, 13 Dec 2023 17:07:59 +0100 Subject: [PATCH] More detailed examples for lifetimes in combined matcher exprs Example taken from #2777 --- docs/matchers.md | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/docs/matchers.md b/docs/matchers.md index b96cc5f4..d5be1f5a 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -50,25 +50,43 @@ Both of the string matchers used in the examples above live in the `catch_matchers_string.hpp` header, so to compile the code above also requires `#include `. +### Combining operators and lifetimes + **IMPORTANT**: The combining operators do not take ownership of the -matcher objects being combined. This means that if you store combined -matcher object, you have to ensure that the matchers being combined -outlive its last use. What this means is that the following code leads -to a use-after-free (UAF): +matcher objects being combined. +This means that if you store combined matcher object, you have to ensure +that the individual matchers being combined outlive the combined matcher. +Note that the negation matcher from `!` also counts as combining matcher +for this. + +Explained on an example, this is fine ```cpp -#include -#include - -TEST_CASE("Bugs, bugs, bugs", "[Bug]"){ - std::string str = "Bugs as a service"; - - auto match_expression = Catch::Matchers::EndsWith( "as a service" ) || - (Catch::Matchers::StartsWith( "Big data" ) && !Catch::Matchers::ContainsSubstring( "web scale" ) ); - REQUIRE_THAT(str, match_expression); -} +CHECK_THAT(value, WithinAbs(0, 2e-2) && !WithinULP(0., 1)); ``` +and so is this +```cpp +auto is_close_to_zero = WithinAbs(0, 2e-2); +auto is_zero = WithinULP(0., 1); + +CHECK_THAT(value, is_close_to_zero && !is_zero); +``` + +but this is not +```cpp +auto is_close_to_zero = WithinAbs(0, 2e-2); +auto is_zero = WithinULP(0., 1); +auto is_close_to_but_not_zero = is_close_to_zero && !is_zero; + +CHECK_THAT(a_value, is_close_to_but_not_zero); // UAF +``` + +because `!is_zero` creates a temporary instance of Negation matcher, +which the `is_close_to_but_not_zero` refers to. After the line ends, +the temporary is destroyed and the combined `is_close_to_but_not_zero` +matcher now refers to non-existent object, so using it causes use-after-free. + ## Built-in matchers