When checkPostCall will be skipped?

Hi, I implemented a checker that checks the program points:

void checkPostCall(const CallEvent &call, CheckerContext &ctx) const;
void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;
// other program points

In one of my test case, “checkPostStmt(call, ctx)” is executed but “checkPostCall” does not. I am wondering why? In my current understanding, both program points should be checked at the same time (same exploded graph node).


Could you post the code for which you have this behavior?
And also the Stmt->dump() for the statement case.

That’s really surprising. As seen in ExprEngine::VisitCallExpr(), PostCall is invoked before PostStmt<CallExpr>, and there doesn’t seem to be a way to reach PostStmt without going through PostCall.

Are you sure that in your case the PostStmt<CallExpr> invocation and the (missing) PostCall invocation refer to the same function call, on the same execution path?

Wait, there’s that caveat that none of this is actually executed for functions that were inlined. In this case the destination set is going to be empty after runCheckersForEvalCall(), and the responsibility to call PostCall and PostStmt<CallExpr> falls on ExprEngine::processCallExit(). Which is much more convoluted, but I still don’t see how it would break like that.

In any case, a reproducer would be really welcome.

The test case I used is listed below:

// test.c
void *myAllocInner(int size);
void myFreeInner(char* addr);

#define myAlloc(size) myAllocInner(size)
#define myFree(addr) myFreeInner(addr)

void test() {
  char *arr = (char *)myAlloc(10 * sizeof(char));

there is no function bodies in this test.c file (using g++ -shared to compile).

I dumped the (trimmed) exploded graph. There are PostStmt program points but no PostCall points.

“program_points”: [
{ “kind”: “Statement”, “stmt_kind”: “CallExpr”, “stmt_id”: xxx, “pointer”: “xxx”, “pretty”: “myFreeInner(arr)”, “location”: {xxx }, “stmt_point_kind”: “PostStmt”, “tag”: null, “node_id”: xx, “is_sink”: 0, “has_report”: 0 }

@NoQ @steakhal

This is the most basic case. The callback is invoked correctly: Compiler Explorer

PostCall program points don’t exist: clang: clang::ProgramPoint Class Reference


I have double checked the callback invocation. It is invoked. The reason I got confused is that the trimmed exploded graph does not show a postCall program point (as you pointed out, it does not exist for stmt_point_kind).

Yay, glad I could help! Unfortunately there’s no one-to-one correspondence between checker callbacks and program points. This is somewhat expected because program points are tied to CFG (so basically to AST) whereas checker callbacks often deal with higher-level abstractions. Eg. checkPostCall() deals with all call exits regardless of how they’re represented syntactically - normal calls, C++ destructors and new/delete operators that don’t correspond to any call-expressions, ObjC messages that aren’t technically call-expressions, don’t get me started on ObjC property accessors…

Also note that ExplodedGraph nodes are sometimes “reclaimed” to save memory. This could be another reason why you wouldn’t see some program points in the graph dump. Arguably a premature optimization, maybe we should stop doing that just to make the machine simpler.

So I think the right way to study how checker callbacks work, is to make toy examples with linear control flow and do what I did on godbolt:

clang -cc1 -analyzer-checker "debug.AnalysisOrder" \
           -analyzer-config  "debug.AnalysisOrder:*=true"

(Unfortunately, the wildcard * doesn’t tell you everything; somebody needs to finish debug.AnalysisOrder to actually cover all callbacks.)

1 Like

Off topic. But we were also bitten by this node reclamation downstream.
At Sonar, we experiment with a different bugreporting mechanism, where we take the ExplodedGraph as an input and walk it in a fancy way. During this walk, we had assumptions about the presence of certain ExplodedNodes, which were sometimes there (for small reproducers :D), and were randomly removed in the world.
I had some fun debugging it, you must imagine…
We had to apply some workarounds to handle such cases, and now I wonder if we should then consider removing the reclamation altogether.
We haven’t measured the impact of disabling reclamation, but I’m definitely interested now.

1 Like

Yeah I totally had some fun debugging this even without doing what you’re doing. Given that our supported limit is 225k nodes, and reclaimed nodes never have a unique Store or GDM pointer (though they could have a unique Environment which will therefore remain retained in memory), I totally suspect that the effect is generally negligible. Just take a few files on which we hit the 225k node limit and compare; if it’s like 1MB per process, it could push some low-memory high-CPU-count systems over the edge (128 cores => 128MB). But if it’s much lower than that, we can probably just remove this facility.