Recently, I tried to solve a use-after-free problem, which involves checking for dereferencing a pointer that points to freed stack memory, for example:
It uses the CFGElement::LifetimeEnds markers. Those should be in a pretty good state. And the analyzer could also use them of you need your analysis implemented in the analyzer.
Indeed, the live/dead here does not mean whether the underlying storage is still there. But whether the value is reachable from the program state and could be read at a future program point.
You probably need to introduce a new callback. That can be a bit involved and a lot of plumbing. That being said, I think we eventually want to improve the analyzer’s understanding of lifetimes, so teaching the engine when the lifetime of certain regions end exactly would be quite useful!
So the right way forward might be not to just make these available for individual checks but to make the engine itself smarter. If you’d like some challenges
Yes it looks like nobody has implement the checker callback for CFGLifetimeEnds because for now they’re ignored. You may need to make your own. Basically make your own ProcessLifetimeEnds() function and make it dig all the way down to your new checker callback invocation, same as other Process...() functions. And double-check that the LifetimeEnds markers are enabled in CFG::BuildOptions.
(If you want to make it fancy and user-friendly you can pass the region of the variable into the callback. I.e. not just the raw VarDecl supplied by the marker, but its “run”-time (more like simulation-time) representation as MemRegion, which is basically a combination of a VarDecl and the StackFrameContext of the stack frame in which the instance of the variable you care about has been allocated on the stack. Which is probably always the current stack frame, given that variables don’t generally go out of scope when something happens in completely unrelated code.)
And, yeah, no, you don’t want to look at the liveness information at all. There’s indeed a difference between the liveness of a “run”-time region and the liveness of a syntactic variable, but neither are useful to you. Liveness analysis is mostly used for memory leaks and state cleanup, which are two surprisingly isomorphic problems. But for the purposes of use-after-free, just trust the lifetime marker. That’s your “free” moment.
(The liveness analysis also doesn’t benefit from lifetime markers. It already knows how to mark the variable as dead in the *middle* of the scope, simply by knowing that it’s not referenced anywhere in the code later. And if it’s actually referenced, like in your example, then it probably shouldn’t be marking the variable as dead, as @xazax.hun explained. Like, maybe it still should mark the variable as dead because it cannot be referenced *correctly* later, but that’s not the purpose of liveness analysis, and there’s nothing wrong with some separation of concerns.)
Also if the implementation of the markers is incomplete, I assume that it’s probably incomplete in the sense that some markers are *missing*. Which is perfectly fine for you, you’ll just miss some warnings, this isn’t nearly as bad as having false positives due to having extra markers. And you aren’t trying to “balance” the begin/end markers anyway so it won’t lead to correctness issues. But it may be a good idea to have a Duff’s Device test. You know, just in case.
Indeed, for the entire engine, handling graceful lifecycle management is a challenging task. However, for a specific Checker, perhaps implementing some simple functionality can quickly achieve the goal. Just as @NoQ said.
In any case, thank you very much for your reply, which has given me a direction. I will try to research it.
Mind that any configuration of CFG::BuildOptions that is not the default in clang, clang-tidy of the clang static analyzer is basically unsupported.
I wouldn’t be surprised if some combination of the options lead to unintended consequences, or even crashes. Don’t be surprised if you step on any of these.
Ideally, of course this wouldn’t be the case, and you could be the one pushing for enabling the Scope markers by default. Once that’s done, it would be in the supported territory.
FYI, downstream we implement a rule like the one you are working on relying on CFGElement::LifetimeEnds just as @Xazax-hun suggested. I can tell you, they work well enough. Unfortunately, because of the conflict of interest I cannot share the implementation, but I’ll be happy to have a look if you propose something upstream.
FYI, I got approval to upstream the callbacks for CFGElement::LifetimeEnds. It is nothing complicated, but it is just a piece of the rule that has been proven to work across a few years and Clang versions.
That looks good!
I found the LifeTimeEnd is very useful for me instead of ScopeEnd when I try to write a checker about use-stack-memory-after-block.
Your code is better and more comprehensive than mine. Through my testing, these Nodes work well in Clang19, but to confirm that they truly work well, perhaps more comprehensive testing is needed.