This false negative bug was exposed by D18860. The checker tries to detect the situation when a symbol previously passed through a _Nonnull-annotated function parameter is constrained to null later on the path, and suppress its warnings on such paths by setting a state-wide flag <InvariantViolated>. The detection for symbols being constrained to null is done, in particular, in checkDeadSymbols(), because that's the last moment when we see the symbol and therefore we have perfect knowledge of its constraints at this moment. The detection works by ascending the stack frame chain and seeing if any of the _Nonnull parameters has a null value in the current program state.
It turned out that such invariant violation detection in checkDeadSymbols() races with checkPreCall(). Suppose that we're stuffing a concrete null (rather than a symbol constrained to null) into the function. Depending on the result of the race, there would be two possible scenarios:
- checkPreCall() fires first. In this case a valid warning will be issued that null is being passed through a parameter that is annotated as _Nonnull.
- checkDeadSymbols() fires first. In this case it'll notice that a _Nonnull parameter of the call that we almost entered is equal to null and raise the <InvariantViolated> flag, suppressing all nullability warnings.
Which means that depending on "something" we either see or don't see a warning.
What is this "something"? Why are callbacks were called in different order? Easy: it's because checkDeadSymbols() weren't firing at all when there were no dead symbols. Now that zombie symbol bugs are fixed, we realize that it's always important to call checkDeadSymbols(), because we've no idea what symbols the checker may track. So after fixing zombie symbols in D18860, the race started resolving to scenario 2, resulting in more false negatives.
The bug is fixed by restricting the check to work with symbols only, not with concrete values. This works because by the time we reach the call, symbolic values should be either not constrained to null, or already replaced with concrete 0 (Loc) values. Though generally the code is a bit weird and might require more thought.
To me, "should've been handled" sounds like that could be translated to an assert.
Also, this doc seems confusing to me, but I might be wrong. You referenced the term "symbol" a couple types, but I don't see SymbolVal anywhere. Could you reword it a bit?