Why doesn't -Wuninitialized find what scan-build finds?

Hello —

Given this code:

void f(int *i) { }
void g(int i) { }

int main() {
    int i;

Using clang 14.0.5, compiling with -Wuninitialized produces no warning, but compiling with scan-build produces:

foo.c:11:5: warning: 1st function call argument is an uninitialized value [core.CallAndMessage]
1 warning generated.

Why doesn’t -Wuninitialized also produce the same warning?

— Paul

My understanding is that the static analyzer does interprocedural analysis, but normal compilation (and therefore -Wuninitialized) does not. I guess without the static analyzer, clang assumes that f(&i) initializes i since that’s a common idiom and would cause false positives everywhere if not assumed.

That’s correct, static analyzer’s/scan-build’s analysis is significantly more sophisticated - path-sensitive and interprocedural. One reason we can’t have this as part of -Wuninitialized is performance: static analyzer can afford to be much slower than the compiler. This is because static analysis isn’t a mandatory part of normal compilation pipeline, you can always skip it if you just want the binary, but you can’t skip compiler warnings, so the compiler can only warn about things that it can notice basically for free.

Similarly, UBSan doesn’t warn either. So I guess if you really want to find as many instances of undefined behavior as you can, always use scan-build.

msan would be the runtime tool to catch use of uninitialized - though I believe it’d only diagnose at the branch-on-uninitialized (ie: you’d have to use the value inside g in a way that makes a difference to the program behavior, or something like that, I think)

MSan didn’t warn either. Only scan-build warned about it being undefined behavior. (Being undefined behavior is why I assumed UBSan would have warned about it also.)

Yes, the general recommendation is to use a combination of tools. All tools have their fair share of limitations.

1 Like

FWIW UBSan doesn’t even try to catch this.
If you want to get a better feel for what is reasonable to expect from it I recommend reading briefly the list of available checks:

I think the explanation goes in the right direction but I’d say that the logic in clang is a little bit different.
(Sorry if this is too nitpicky!)

There are two goals:

  1. avoid slowdown in compile-time (even when warnings are emitted)
  2. minimum false positive warnings

As @NoQ said - any non-trivial analysis would slow down compilation which forces logic of the warnings to be relatively dumb.
Specifically in this situation clang won’t even try to check if the definition of f is available as even if it is checking for initialization of the parameter properly for general case requires all the machinery that Static Analyzer uses (and that’d be slow). That’s a “constraint” imposed by 1.

We know pretty much nothing about the function f and we want to avoid false positives (much more than we want to avoid false negatives). In theory f might initialize i but we don’t know if it really does. Since we can’t prove i is still uninitialized after f(&i); the only thing we/clang can do to avoid false positives is to not emit any warning.

EDIT: changed misleading statement.

Just to clarify - what I said is just description of current situation. I don’t really have clarity (or strong opinions) about if there is any better solution.

It might be possible to add logic for the specific warning to handle some special cases fast enough?
It might be worth to implement and maintain it?
But since we won’t be able to do this consistently (only special cases) the users still might be confused and this exact discussion with a different snippet would still occur?


As an FYI, the example I gave is a whittled down version of some real code of mine. In the real code (and very likely in whittled down version too), the compiler inlined (what is here) f(), so it had full knowledge of its body.