0

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.

αλεχολυτ
  • 4,792
  • 1
  • 35
  • 71
  • It doesn't seem trivial to me to prove there's no null dereference. It has to know from non-local information that there's no way `h()` can change `r_`. – chris Dec 25 '22 at 22:23
  • @chris `r_` is like `this` for `h()`. How it can be changed in `h()`? – αλεχολυτ Dec 25 '22 at 22:26
  • It could have a reference to `hd` or `hd.r_`. Obviously, we can tell this isn't the case from a glance at the complete program, but that's all information that needs to be carried into `g()` from outside analysis. From `g()`'s point of view without that outside help, it's a possibility. – chris Dec 25 '22 at 22:32
  • And, just like before, we wonder what the static_cast is for? Why not just `r_->get()`? – BoP Dec 25 '22 at 23:03
  • @BoP in actual code `get` is overridden and placed in private part of `SC`. So the casting to `IF*` is necessary to call `get`. – αλεχολυτ Dec 26 '22 at 08:24
  • In that case, have you tried `r_->IF::get()` to [explicitly call the base class function](https://stackoverflow.com/questions/672373/can-i-call-a-base-classs-virtual-function-if-im-overriding-it)? – BoP Dec 26 '22 at 08:49
  • @BoP There are two reasons: 1. used more than once, so explicit variable is better. 2. it's pure virtual in `IF`. – αλεχολυτ Dec 26 '22 at 11:13

1 Answers1

2

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

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • What do you think about possibility to get nullptr after the `static_cast`ing non-nullptr? Is it possible in C++? – αλεχολυτ Dec 26 '22 at 08:27
  • @αλεχολυτ It is not legally possible. Either the `static_cast` has defined behavior and doesn't result in a null pointer, or it has undefined behavior, in which case there isn't really any point in discussing possible values. However the compiler doesn't think in terms of the standard rules. It probably thinks in terms of numeric address values. In that sense `static_cast` may result in a null pointer because it negatively offsets the original pointer. But that would be UB. – user17732522 Dec 26 '22 at 12:15