I found one more case when compiler thinks that static_cast
may return nullptr on non-nullptr argument. It seems that this time there is no undefined behavior, like was before, but maybe I'm missing something.
Here's the code compiled with gcc with -O1 -fPIC -Wnull-dereference -Werror
options:
struct IR {
virtual ~IR() = default;
};
struct IF {
virtual ~IF() = default;
virtual void get() {};
};
struct SC : IR, IF {
bool h(); // { return true; };
};
struct HD : public IR {
virtual void g() {
if (r_ && r_->h()) {
static_cast<IF*>(r_)->get();
}
}
SC* r_ = nullptr;
};
bool SC::h() { return true; }
HD hd;
Any one of the following conditions will eliminate potential null pointer dereference
warning:
SC::h()
inline;IF::get()
non-virtual;-O1
to -O0
;-fPIC
option...So, is there a GCC bug, or still UB in code, or static_cast
for non-nullptr can results in nullptr?
Link to godbolt.
If you use -fPIC
and don't use an inline
function for h
, then the compiler can't make any assumptions about the behavior of h
because GCC's default semantics are to allow any shared library to override the function with alternative semantics. You can additionally specify -fno-semantic-interposition
to allow GCC to optimize under the assumption that other implementations of the function loaded from a shared library will not behave differently.
As far as I understand -Wnull-dereference
isn't guaranteed to only warn if there is a definitive null pointer dereference, only if there is a potential one (although documentation seems a bit unclear, see also https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86172). The compiler can't be sure that r_->h()
doesn't modify r_
for the reason above, and therefore the null pointer check cannot prove that static_cast<IF*>(r_)->get();
won't dereference a null pointer.
The warning doesn't appear for -O0
because it is documented that the warning only works when -fdelete-null-pointer-checks
is enabled, which it is only with optimizations enabled.
I guess the warning doesn't appear if get
isn't virtual
either intentionally because the compiler can be sure that the function call won't actually cause a null dereference in the practical sense even if the object pointer is a null pointer (although it is still UB per standard), or alternatively the function is inlined earlier than the null pointer check is performed. (If the function is virtual
the compiler can again make no assumptions about this call, since it could be calling a derived class override instead.)