================
@@ -538,21 +536,35 @@ void 
CheckerManager::runCheckersForBeginFunction(ExplodedNodeSet &Dst,
   expandGraphWithCheckers(C, Dst, Src);
 }
 
-/// Run checkers for end of path.
-// Note, We do not chain the checker output (like in expandGraphWithCheckers)
-// for this callback since end of path nodes are expected to be final.
+/// Run checkers for end of a function (either the entrypoint or another
+/// function that was inlined). Note that this function places the
+/// checker activations on separate execution paths:
+///        /-[checker1]-> N1 ...
+///   Pred --[checker2]-> N2 ...
+///        \-[checker3]-> N3 ...
+/// (If none of the checkers produce a transition, we continue with 'Pred'.)
+///
+/// This differs from the handling of all the other checker callbacks, where
+/// the checker activations are chained sequentially on a single path:
+///   Pred --[checker1]-> N1 --[checker2]-> N2 --[checker3]-> N3 ...
+///
+/// This difference has historical reasons: originally this callback was called
+/// 'EndPath' and only activated at the end of an execution paths, and
+/// (according to an old comment) those 'EndPath' checkers expected that they
+/// create an "end of path" node which will be final.
+/// TODO: Check whether this exceptional behavior is still justified.
 void CheckerManager::runCheckersForEndFunction(ExplodedNodeSet &Dst,
                                                ExplodedNode *Pred,
                                                ExprEngine &Eng,
                                                const ReturnStmt *RS) {
----------------
NagyDonat wrote:

> Could you trace it back to when this bug was introduced [...]

This checker callback always had this behavior. The name `EndFunction was 
introduced in 2013 by commit 3fdcc0bda3c486c371159acdbc370933860dbe42 which 
renamed the callback `EndPath` to `EndFunction` without changing its behavior. 
(The commit message of that commit says "This better reflects when callback is 
called and what the checkers are relying on. (Both names meant the same 
pre-IPA.)" where IPA presumably means interprocedural analysis i.e. inlining 
function bodies.)

The implementation of the "execute checkers in parallel" logic can be traced 
back to commit 3eae33412d18c4a4a4a8592898b3e65ad5946a89 from 2011 -- but that 
commit just refactors an earlier implementation (by eliminating a class called 
`EndOfFunctionNodeBuilder` from the codebase). I'm pretty sure that earlier 
implementations also had this behavior and I don't think that we need to dig 
deeper.

> [...] and how many checks used this callback to judge the blast radius?

The following checkers (8 "real" + 3 dummy) have `check::EndFunction` callbacks:
- `ObjCDeallocChecker`  calls `diagnoseMissingReleases()` which creates 
non-fatal error nodes and tweaks the state in checker-specific and apparently 
uninteresting ways (to suppress duplicate reporting of the same error),
- `MIGChecker` generates fatal error nodes,
- `MallocChecker` calls `CheckEscapeOnReturn` to emit fatal use-after-free 
error nodes
- `RetainCountChecker` can emit non-fatal error nodes and tweaks the 
checker-specific state in a complex way that I don't (want to) understand,
- `StackAddrEscapeChecker` has a `checkEndFunction` callback that consist of 
150+ lines with a locally defined class -- I don't know what does it do,
- `TestAfterDivZeroChecker` and `UnconditionalVAArgChecker clean up their 
checker-specific state by removing entries that "belong to" the function that 
is being ended
- there are three dummy checkers: `AnalysisOrderChecker`, 
`CheckerDocumentation`, `TraversalDumper`

> How did we not notice this earlier?

- Answer 1: We don't read the source code where this unusual behavior was 
proudly highlighted by fat comment blocks.
- Answer 2: As far as I see the existing `EndFunction` callbacks all do things 
that usually keep working if they are "banished" to a parallel execution path 
instead of the primary one:
  - Error nodes are still created, the associated bug report still appears 
(although the analysis can continue "after" a fatal error, if the parallel 
execution of another checker provides a path forward).
  - The cleanup of checker-specific state is a "nice to have" thing, and 
failure to remove irrelevant checker-specific information is almost completely 
irrelevant (but theoretically it could prevent merging execution paths).

With our current set of checkers, this exceptional logic only introduces a few 
redundant execution paths and other minor irregularities of the 
`ExplodedGraph`. As the growth of the graph is a poorly understood, almost 
magical process, it is not surprising that we did not spot these.

In general it is important to note that **the existing checkers only exercise a 
small fraction of the engine logic** so logic errors like this are probably 
widespread in the engine.

In fact, one major motivation for this `NodeBuilder` removal commit series was 
that this is a good framework for reviewing many parts of the engine to improve 
my knowledge and spot issues like this one. (This is roughly the fifth logic 
error uncovered this way.) The removal of `class NodeBuilder` is a good thing 
in itself, but these side effects are perhaps even more important.

https://github.com/llvm/llvm-project/pull/217319
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to