When compiling with Microsoft's /analyze
static analysis command line option for cl.exe
, I get the warning
warning C6011: Dereferencing NULL pointer 'foo'
on a code path that calls a trivial function that guarantees that foo
is not NULL where the analyzer thinks it can be.
The trivial function:
bool check_ptr(void* ptr)
{
if (!ptr)
{
// The original does more things here, but
// the repro is valid even without that.
return false;
}
return true;
}
The calling site:
foo_t* foo = lookup_foo(id);
if (!check_ptr(foo))
return;
foo->bar = 4711; // warning C6011: Dereferencing NULL pointer 'foo'
The analyzer is really bad at seeing through function calls, even trivial ones. If check_ptr
is reduced to
bool check_ptr(void* ptr)
{
return !!ptr;
}
then the analyzer can deduce that foo
cannot be NULL when dereferenced, but that's not an option. The checker function is there for a reason.
So, I assume that there is an ungodly SAL annotation combination that can be applied to check_ptr
to convince the analyzer that if it returns true
, then the foo
argument is not NULL.
Is there such a SAL annotation?
EDIT: I found a SAL solution and added it as a separate answer https://stackoverflow.com/a/74459650/6345
CodePudding user response:
I found a way to SAL annotate the check_ptr
function so that the analyzer knows what it does, by using _Post_equal_to_
. By annotating the function return declaration with _Post_equal_to_(!!p)
, the analyzer knows that the return value is non-zero if p
is non-NULL.
Added a complete minimal repro example on godbolt / Compiler Explorer that looks more like the actual usage scenario in my code. Note the UNCOMMENT NEXT LINE TO REMOVE
comment.
// Compile with these cl.exe command line options: /TC /WX -std:c17 /analyze
// https://godbolt.org/z/5sj9j7dY3
#include <sal.h>
typedef struct source_location {
const char* file;
int line;
} source_location;
// UNCOMMENT NEXT LINE TO REMOVE "warning C6011: Dereferencing NULL pointer"
//_Post_equal_to_(!!p)
int check_ptr__(void* p, source_location location);
#define check_ptr(p) \
check_ptr__((p), (source_location) { __FILE__, __LINE__ })
// '_In_opt_' tells the analyzer that 'i' can validly be NULL
void minimal_analyzer_repro(_In_opt_ int* i)
{
if (check_ptr(i))
*i = 4711; // "warning C6011: Dereferencing NULL pointer"
}
CodePudding user response:
It's not exactly using SAL, but a generic answer that should work all the time: instead of returning bool, you might take two lambdas: one for then, one for else:
template<typename Then, typename Else>
void if_check_ptr(Foo* ptr, Then then, Else els)
{
if (!ptr)
{
// The original does more things here, but
// the repro is valid even without that.
then(*ptr);
} else {
els();
}
}
// usage:
if_check_ptr(
lookup_foo(id),
/*then*/ [&](Foo& foo) { foo.bar = 4711; },
/*else*/ []() {}
);
You could even have lookup_foo()
and if_check_ptr()
in one call.