I met a weird result.
The global version does not generate a warning like the local one does?
test.cpp:3:11: warning: Dereference of null pointer (loaded from variable ‘x’) [core.NullDereference]
Another observation is that for the global verion analyzer does not handle init expression (int *x = 0;), but for the local one it does. Therefore it stores x as &SymRegion{reg_$0<int * x>} for the global and 0 for the local.
It is expected. Global variables are infamous for being extremely hard to analyze statically. Clang static analyser is no exception. Basically it analyzes functions independently (inlining some of the calls), and this model doesn’t allow it to reason about global variables. It doesn’t know when foo is called because maybe some other function have already modified x. Even if we put direct assignment x = 0 into foo, it is still hard to reason about. Any call to another function is a potential modification of x.
I'd like to add to this that out that we do actually trust the global variable to still be null if the function under analysis is main(). I.e., this warns:
int \*x = 0;
int main\(\) \{
int y = \*x;
\}
It doesn't warn in C++ though, only in C, because in C++ it's too idiomatic to have code before main (eg., global constructors). This can be made more precise though, at least for non-escaping static globals.