This is an archive of the discontinued LLVM Phabricator instance.

[analyzer] Improve suppression for inlined defensive checks when operator& is involved.
ClosedPublic

Authored by NoQ on Apr 12 2017, 9:55 AM.

Details

Summary

In code

void foo(int *p) {
  if (p) {}
}
void bar(int *p) {
  foo(p);
  *p = 5;
}

we suppress the null dereference warning in *p = 5 because the state split within the inlined function is essentially irrelevant, as this is merely a defensive check that does not necessarily trigger in this caller context.

The suppression works by tracking the null pointer symbol in a bug report visitor and seeing if it was constrained to 0 inside a callee context. The fact that we have to rely on such manual suppressions is a downside of the inlining-based approach to interprocedural analysis.

The suppression gets confused when the null dereference becomes more complex, eg:

struct S {
  int x;
}
void foo(struct S *s) {
  if (s) {}
}
void bar(struct S *s) {
  foo(s);
  *(&(s->x)) = 5;
}

In this case s is &SymRegion{reg_$0<s>}, and reg_$0<s> is constrained to [0, 0], similarly to the above. However, while computing s->x and then &(s->x), the symbol is collapsed to a constant 0 (Loc), making it harder to track the symbol. The visitor is improved to handle this case.

While the fact that offset of field x in struct S in our example is equal to 0 is purely accidental, with any other offset it'd still be a null dereference, worth suppressing. How does the analyzer handle the non-zero offset case and still see a null dereference? Simply and powerfully: it mis-computes the offset, incorrectly believing that all offsets are equal to 0 (woohoo!). I add a test case to document this behavior; even though it's trivial to fix, the positive side effects of this bug are for now too useful to discard.

Diff Detail

Repository
rL LLVM

Event Timeline

NoQ created this revision.Apr 12 2017, 9:55 AM
alexander-shaposhnikov added inline comments.
lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
965 ↗(On Diff #94986)

"Address-of" operator can be overloaded,
just wondering - doest this code work correctly in that case ?

NoQ added inline comments.Apr 12 2017, 10:05 AM
lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
965 ↗(On Diff #94986)

In this case we'd see a CXXOperatorCallExpr instead of UnaryOperator (all hail clang AST!).

NoQ updated this revision to Diff 95082.Apr 13 2017, 12:00 AM

Remove accidentally added braces in unrelated code.

xazax.hun added inline comments.Apr 13 2017, 3:14 AM
lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
975 ↗(On Diff #95082)

Maybe you could move this condition one level up with &&?

977 ↗(On Diff #95082)

Can't you writhe this like:

while (const auto *ME = dyn_cast<MemberExpr>(Ex)) {
              Ex = ME->getBase()->IgnoreParenCasts();
      }

?

dcoughlin accepted this revision.Apr 13 2017, 9:23 AM

Yay! This is going to fix some really confusing false positives.LGTM with Gabor's comments addressed.

One note. I am not a big fan of using clang_analyzer_explain() in tests for functionality. It is great for debugging, but for testing it exposes too much of the implementation details of the region hierarchy and store. Exposing this kind of state is fine in tests specific to the store/region store, but in my opinion it should be another clang_analyzer_ entry point and should be limited to regions.

Similarly we should have a clang_analyzer_ entry point for testing symbols but that doesn't expose the guts of MemRegions. And (maybe?) another one for SymExprs.

test/Analysis/explain-svals.cpp
103 ↗(On Diff #95082)

It would be good to mention that this is a modeling bug and point to where it could be fixed. Otherwise the FIXME is not super helpful to future maintainers.

This revision is now accepted and ready to land.Apr 13 2017, 9:23 AM
zaks.anna edited edge metadata.Apr 13 2017, 9:52 AM

Thanks!!!

lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
965 ↗(On Diff #94986)

Adding a test case for that would be good.

968 ↗(On Diff #94986)

Incorrect implies that there is a better "correct" model and invites a fix. Do we know what better model would be? If so, we could add that to the comment. If not, I'd prefer explaining why it works this way (similarly to how you did in the comment below). Maybe adding an example of what does not work. And you could add a FIXME to say that it's worth investigating if there is a better way of handling it. (The majority of this info should probably go to Store.cpp)

Also, maybe it's just the choice of words here. "Incorrect" sounds like something that needs to be corrected. Whereas you could use something like "is modeled imprecisely with respect to what happens at execution time", which could still mean that this is how we do want to model it going forward.

It seems that the problem with modeling this way is demonstrated with a test case in explain-svals.cpp. So the benefits of the current modeling seem to be worth it.

Can we add a note along the path saying that "p" in "p->f" is null? This would address the user confusion with imprecise modeling.

977 ↗(On Diff #94986)

Why do we need the "while (true)"? Can we just use "dyn_cast<MemberExpr>(Ex)" as the loop condition?

Take a look at the getDerefExpr(const Stmt *S) and see if that would be a better place to add this code. Maybe not..

lib/StaticAnalyzer/Core/Store.cpp
405 ↗(On Diff #94986)

Could you rephrase this existing comment while you are here? Using word "funny" seems content-free and a bit strange.

409 ↗(On Diff #94986)

Thanks for adding the explanation!

Can you think of other cases where the same would apply? (Ex: array index)

NoQ updated this revision to Diff 95890.Apr 19 2017, 11:04 PM
NoQ marked 5 inline comments as done.

Fix comments!

NoQ added inline comments.Apr 19 2017, 11:06 PM
lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
965 ↗(On Diff #94986)

Not sure. There are so many things that work differently in this scenario that i'm having troubles coming up with a test that tests exactly that and doesn't throw or not throw a warning for a dozen of other reasons. I'm even having troubles understanding what particular overload are we interested in. Did you have anything specific in mind?

lib/StaticAnalyzer/Core/Store.cpp
440 ↗(On Diff #95890)

Note that this code doesn't really trigger; we return UnknownVal() somewhere above, as shown on the newly added tests. I suspect we may be missing valid null dereferences because of that; will have a look.

NoQ added inline comments.Apr 20 2017, 6:46 AM
lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
977 ↗(On Diff #94986)

Accidentally clicked "Done" and forgot about the getDerefExpr() part.

I put it into a follow-up patch though: D32291, because it's quite a cascade of changes already.

lib/StaticAnalyzer/Core/Store.cpp
440 ↗(On Diff #95890)

This is also addressed by D32291.

This revision was automatically updated to reflect the committed changes.