-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[clang][static analyzer] false positive cplusplus.NewDelete ("use after free") w/ reference counting #90498
Comments
@llvm/issue-subscribers-clang-static-analyzer Author: None (sharkautarch)
version of clang: `17.0.6` (archlinux pkg)
(see also: this closed issue thread w/ more details about the false positive: ValveSoftware/gamescope#1275)
as detailed under the last comment I made in the issue thread here: ValveSoftware/gamescope#1275 (comment)
what should happen instead: |
Hi, it's great to see projects using the Static Analyzer, and even reporting FPs. Thank you.
I haven't read the linked discussion, but your statements are true: we only reason about a single TU at a time; and assuming no dead code, we consider that both branches are possible to take. It's difficult for me to assess this particular case, and there could be caveats of course. So let's see a detailed investigation.
Some have already tried cross-translation unit (CTU) analysis, but my experience is that it's hard, and the tradeoffs are not great. The way we do path-sensitive analysis in the CSA, it's not really suitable for scalable CTU analysis. |
@steakhal |
Folks, I haven't actually looked at it yet, but at a glance one thing worries me: it looks to me as if you aren't actually reading static analyzer warnings as intended. The "warning: Use of memory after it is freed" text you see on the command line isn't the actual output, this is more of a notification that a warning exists. You should open the HTML files produced by scan-build in the (Please ignore me if you're already doing this. It's just that a surprising amount of users completely miss this. We should make a better message about this.) These step-by-step reports are the intended way to consume static analyzer warnings; IDE integration also usually looks similar to this. Static analyzer warnings are emitted against an entire execution path, not a single line of code; the final statement may not be true on a different execution path. The step-by-step report may ultimately still be a false positive, but in many cases it'd actually explain to you what the analyzer was thinking and where it made a mistake, and maybe even help you find an elegant way to suppress the warning (while we're getting it fixed on our end). You definitely shouldn't debug this with debug prints inside clang. The analyzer simulates branch-y code out of order so you just get prints from different universes mixed up. For "hardcore" debugging we usually print graphviz graphs of execution paths explored by the analyzer (like |
Good to know that you’re actually supposed to look at the browser view to get more information.
Also about the graphvis exploded graph stuff, I actually tried to dump a debug graph and looked at it with graphvis, but there were so many nodes that I couldn’t make heads or tails about it. Though maybe I picked the wrong graph option thingy? |
…pression. Fixes llvm#90498. Same as 5337efc for atomic builtins, but for `std::atomic` this time. This is useful because even though the actual builtin atomic is still there, it may be buried beyond the inlining depth limit. Also add one popular custom smart pointer class name to the name-based heuristics, which isn't necessary to fix the bug but arguably a good idea regardless.
Yes I completely agree that we have a messaging problem here. We should probably turn on full text output by default. It's not readable whatsoever but at least it gives you complete information no matter where you look. (I like how I also think you're right that most of our warnings are quite decipherable without the full path. Especially if it's freshly written code. (Also our "dead stores" warnings aren't even path-sensitive and they're our "loudest" warnings usually.) But I'm coming from a more bayesian perspective here: a large portion of LLVM bug reports about false positives were historically explained away into a true positive when the full path is considered. It's probably also true that those non-trivial-path reports are the most "important" ones. We find weird edgecase paths that haven't been considered by the programmer, that haven't been covered by runtime tests. Simpler bugs can be found with much simpler technology, compile-time or run-time. In any case, yeah I think you're right, this report looks pretty bad. Here's (part of) the HTML view: And the most nested stack frame for "memory was released" is: The highlighted step "(12) Assuming 'uRefPrivate' is 0" indicates that the static analyzer doesn't know that the reference count drops to zero, but it has to consider this possibility because an immediate branch depends on it. So technically the use-after-free would happen under the assumption that on step (12) the last remaining reference to the object is removed and the reference count drops to zero. Notice how step (12) isn't part of But it sounds like it's fairly safe to assume that ownership wasn't transfered. Even if it was from some philosophical perspective, there's clearly the So what really happened is: on step (12) the static analyzer started exploring the possibility that the reference count was And this is a really annoying problem that quickly goes back to the fundamentals of the analysis technique that we're using, roughly explained in the bug #61669 for which we don't really have a very good solution; we're probably stuck with it for a while. Basically, whenever we see a branch, we cannot continue the analysis at all until we "fork" the analysis to explore both sides of the branch independently. And then the two parallel universes never merge back. Which is fine in most cases. We just see the branch and assume that it's there "for a reason". But often bites us in a few specific areas. C++ reference-counting smart pointers are one of such areas. Such false positives are common in their presense. We've got some very crude heuristics to deal with this smart pointer related false positives. One of them works by noticing that the delete operation happened in a destructor and was surrounded by atomic operations. That's probably why So, let me improve that heuristic real quick. This appears to take care of your false positive. #90918 |
Yeah the budget limit is |
@haoNoQ Thanks for the advice & help! :) |
…pression. (#90918) Fixes #90498. Same as 5337efc for atomic builtins, but for `std::atomic` this time. This is useful because even though the actual builtin atomic is still there, it may be buried beyond the inlining depth limit. Also add one popular custom smart pointer class name to the name-based heuristics, which isn't necessary to fix the bug but arguably a good idea regardless.
version of clang:
17.0.6
(archlinux pkg)(see also: this closed issue thread w/ more details about the false positive: ValveSoftware/gamescope#1275)
After Joshua-Ashton did a refactor of a reference counting class, I started seeing a new use-after-free warning from scan-build:
as detailed under the last comment I made in the issue thread here: ValveSoftware/gamescope#1275 (comment)
I found that, in this situation, the static analyzer is:
if (<condition>) { delete this }
won't be taken, then it'll evaluate both the branch-taken path and the branch-not-taken path.what should happen instead:
Whenever the static analyzer sees
free
ordelete
inside something like anif
, wherein it currently doesn't know if the branching condition will always evaluate to true or false, it should check beyond the local translation unit to try to figure out if said condition would always turn out to be true or always turn out to be false.The text was updated successfully, but these errors were encountered: