Search code examples
c++language-lawyerstatic-castnull-pointer

Potential null pointer dereference or compiler bug?


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:

  • making SC::h() inline;
  • making IF::get() non-virtual;
  • changing optimization level from -O1 to -O0;
  • removing -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.


Solution

  • 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.)