I managed to reproduce the issue, see here.
The issue is as follows:
Both BOOST_TEST_REQUIRE
and BOOST_FAIL
boost macros eventually expand to a call to report_assertion(..., REQUIRE, ...)
.
This call is not marked noreturn
, as it only aborts if REQUIRE
is passed.
The body of the function is not present in the translation unit, only its declaration, so from the analyzer’s perspective this function always returns.
Because of this, neither of BOOST_TEST_REQUIRE
nor BOOST_FAIL
macros will sink the execution path if the condition is not met.
In your example, you don’t have a warning in the case where you use BOOST_TEST_REQUIRE
, because in that case the passed t
eventually escapes - meaning that its address was passed to some function via a mutable reference or pointer, hence potentially overwriting its contents.
This means that we must erase all information about t
, and assume that the next call t->foo()
is valid and t
is nonnull.
You can also verify this theory by inserting a check against the nullness of t
after the macro. That will cause us to believe that t
is expected to be null in some cases (otherwise why would you check it
)
So, we would end up having the same FP as in the second case.
This is a tough problem, that we might be able to tackle by some cross-translation unit information.
Basically, if we could infer that report_assertion
aborts if the given parameter is REQUIRE
, then we would be able to sink that path, as one expects.
About the second problem - the escape of t
- it’s slightly more difficult but follows the same pattern.
If we could prove that even though the given function has a mutable reference by that parameter, it actually never mutates that object, hence we no longer need to conservatively invalidate it.
I created the CPP-4322 and CPP-4323 for conditional noreturn detection and argument escaping improvements respectively.
I wouldn’t expect to tackle these issues any time soon.
Possible workarounds:
Wrap the boost macros and insert an assert
to help us. Something like this:
#define MY_BOOST_TEST_REQUIRE(cond, msg) do { BOOST_TEST_REQUIRE(static_cast<bool>(cond), msg); assert(cond); } while (0)
#define MY_BOOST_FAIL(msg) do { BOOST_FAIL(msg); assert(0); } while(0)
The escape of t
could be prevented if the condition is eagerly converted to bool
.
Note the static_cast<bool>(cond)
in my macro. That way the address of the macro parameter can never escape, so it’s not going to be invalidated.