Skip to content

Commit

Permalink
More detailed examples for lifetimes in combined matcher exprs
Browse files Browse the repository at this point in the history
Example taken from #2777
  • Loading branch information
horenmar committed Dec 13, 2023
1 parent 3f23192 commit 3acb8b3
Showing 1 changed file with 30 additions and 12 deletions.
42 changes: 30 additions & 12 deletions docs/matchers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <catch2/matchers/catch_matchers_string.hpp>`.

### 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 <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
CHECK_THAT(value, WithinAbs(0, 2e-2) && !WithinULP(0., 1));
```
TEST_CASE("Bugs, bugs, bugs", "[Bug]"){
std::string str = "Bugs as a service";
and so is this
```cpp
auto is_close_to_zero = WithinAbs(0, 2e-2);
auto is_zero = WithinULP(0., 1);
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, 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
Expand Down

0 comments on commit 3acb8b3

Please sign in to comment.