Skip to content

Commit c725ed7

Browse files
committed
[analyzer] Avoid out-of-order node traversal on void return
The motivating example: ```C++ void inf_loop_break_callee() { void* data = malloc(10); while (1) { (void)data; // line 4 break; // -> execution continues on line 4 ?!! } } void inf_loop_break_caller() { inf_loop_break_callee(); } ``` To correct the flow steps in this example (see the fixed version in the added test case) I changed two things in the engine: - Make `processCallExit` create a new StmtPoint only for return statements. If the last non-jump statement is not a return statement, e.g. `(void)data;`, it is no longer inserted in the exploded graph after the function exit. - Optionally skip the purge program points. In the example above, purge points are still inserted after the `break;` executes. Now, when the bug reporter is looking for the next statement executed after the function execution is finished, it will ignore the purge program points, so it won't confusingly pick the `(void)data;` statement.
1 parent 7e312c3 commit c725ed7

File tree

6 files changed

+63
-20
lines changed

6 files changed

+63
-20
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,9 @@ class ExplodedNode : public llvm::FoldingSetNode {
278278
/// Useful for explaining control flow that follows the current node.
279279
/// If the statement belongs to a body-farmed definition, retrieve the
280280
/// call site for that definition.
281-
const Stmt *getNextStmtForDiagnostics() const;
281+
/// If skipPurge is true, skip the purge-dead-symbols nodes (that are often
282+
/// inserted out-of-order by the endinge).
283+
const Stmt *getNextStmtForDiagnostics(bool skipPurge) const;
282284

283285
/// Find the statement that was executed immediately before this node.
284286
/// Useful when the node corresponds to a CFG block entrance.

clang/lib/StaticAnalyzer/Core/BugReporter.cpp

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,8 @@ static void removePiecesWithInvalidLocations(PathPieces &Pieces) {
589589

590590
PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues(
591591
const PathDiagnosticConstruct &C) const {
592-
if (const Stmt *S = C.getCurrentNode()->getNextStmtForDiagnostics())
592+
if (const Stmt *S =
593+
C.getCurrentNode()->getNextStmtForDiagnostics(/*skipPurge=*/true))
593594
return PathDiagnosticLocation(S, getSourceManager(),
594595
C.getCurrLocationContext());
595596

@@ -882,7 +883,8 @@ void PathDiagnosticBuilder::generateMinimalDiagForBlockEdge(
882883

883884
case Stmt::GotoStmtClass:
884885
case Stmt::IndirectGotoStmtClass: {
885-
if (const Stmt *S = C.getCurrentNode()->getNextStmtForDiagnostics())
886+
if (const Stmt *S = C.getCurrentNode()->getNextStmtForDiagnostics(
887+
/*skipPurge=*/false))
886888
C.getActivePath().push_front(generateDiagForGotoOP(C, S, Start));
887889
break;
888890
}
@@ -2416,6 +2418,28 @@ PathSensitiveBugReport::getRanges() const {
24162418
return Ranges;
24172419
}
24182420

2421+
static bool exitingDestructor(const ExplodedNode *N) {
2422+
// Need to loop here, as some times the Error node is already outside of the
2423+
// destructor context, and the previous node is an edge that is also outside.
2424+
while (N && !N->getLocation().getAs<StmtPoint>()) {
2425+
N = N->getFirstPred();
2426+
}
2427+
return N && isa<CXXDestructorDecl>(N->getLocationContext()->getDecl());
2428+
}
2429+
2430+
static const Stmt *
2431+
findReasonableStmtCloseToFunctionExit(const ExplodedNode *N) {
2432+
if (exitingDestructor(N)) {
2433+
// If we are exiting a destructor call, it is more useful to point to
2434+
// the next stmt which is usually the temporary declaration.
2435+
if (const Stmt *S = N->getNextStmtForDiagnostics(/*skipPurge=*/false))
2436+
return S;
2437+
// If next stmt is not found, it is likely the end of a top-level
2438+
// function analysis. find the last execution statement then.
2439+
}
2440+
return N->getPreviousStmtForDiagnostics();
2441+
}
2442+
24192443
PathDiagnosticLocation
24202444
PathSensitiveBugReport::getLocation() const {
24212445
assert(ErrorNode && "Cannot create a location with a null node.");
@@ -2433,18 +2457,10 @@ PathSensitiveBugReport::getLocation() const {
24332457
if (const ReturnStmt *RS = FE->getStmt())
24342458
return PathDiagnosticLocation::createBegin(RS, SM, LC);
24352459

2436-
// If we are exiting a destructor call, it is more useful to point to the
2437-
// next stmt which is usually the temporary declaration.
2438-
// For non-destructor and non-top-level calls, the next stmt will still
2439-
// refer to the last executed stmt of the body.
2440-
S = ErrorNode->getNextStmtForDiagnostics();
2441-
// If next stmt is not found, it is likely the end of a top-level function
2442-
// analysis. find the last execution statement then.
2443-
if (!S)
2444-
S = ErrorNode->getPreviousStmtForDiagnostics();
2460+
S = findReasonableStmtCloseToFunctionExit(ErrorNode);
24452461
}
24462462
if (!S)
2447-
S = ErrorNode->getNextStmtForDiagnostics();
2463+
S = ErrorNode->getNextStmtForDiagnostics(/*skipPurge=*/false);
24482464
}
24492465

24502466
if (S) {

clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,11 @@ const Stmt *ExplodedNode::getStmtForDiagnostics() const {
347347
return nullptr;
348348
}
349349

350-
const Stmt *ExplodedNode::getNextStmtForDiagnostics() const {
350+
const Stmt *ExplodedNode::getNextStmtForDiagnostics(bool skipPurge) const {
351351
for (const ExplodedNode *N = getFirstSucc(); N; N = N->getFirstSucc()) {
352+
if (skipPurge && N->getLocation().isPurgeKind()) {
353+
continue;
354+
}
352355
if (const Stmt *S = N->getStmtForDiagnostics()) {
353356
// Check if the statement is '?' or '&&'/'||'. These are "merges",
354357
// not actual statement points.

clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,14 +353,19 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
353353
ExplodedNodeSet CleanedNodes;
354354
if (LastSt && Blk && AMgr.options.AnalysisPurgeOpt != PurgeNone) {
355355
static SimpleProgramPointTag retValBind("ExprEngine", "Bind Return Value");
356-
PostStmt Loc(LastSt, calleeCtx, &retValBind);
356+
auto Loc = isa<ReturnStmt>(LastSt)
357+
? ProgramPoint{PostStmt(LastSt, calleeCtx, &retValBind)}
358+
: ProgramPoint{EpsilonPoint(calleeCtx, /*Data1=*/nullptr,
359+
/*Data2=*/nullptr, &retValBind)};
360+
const CFGBlock *PrePurgeBlock =
361+
isa<ReturnStmt>(LastSt) ? Blk : &CEBNode->getCFG().getExit();
357362
bool isNew;
358363
ExplodedNode *BindedRetNode = G.getNode(Loc, state, false, &isNew);
359364
BindedRetNode->addPredecessor(CEBNode, G);
360365
if (!isNew)
361366
return;
362367

363-
NodeBuilderContext Ctx(getCoreEngine(), Blk, BindedRetNode);
368+
NodeBuilderContext Ctx(getCoreEngine(), PrePurgeBlock, BindedRetNode);
364369
currBldrCtx = &Ctx;
365370
// Here, we call the Symbol Reaper with 0 statement and callee location
366371
// context, telling it to clean up everything in the callee's context

clang/test/Analysis/cxx-uninitialized-object-unguarded-access.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ class UnguardedFieldThroughMethodTest {
163163
Volume = 0;
164164
break;
165165
case A:
166-
Area = 0; // expected-warning {{1 uninitialized field}}
167-
break;
166+
Area = 0;
167+
break; // expected-warning {{1 uninitialized field}}
168168
}
169169
}
170170

@@ -201,8 +201,8 @@ class UnguardedPublicFieldsTest {
201201
Volume = 0;
202202
break;
203203
case A:
204-
Area = 0; // expected-warning {{1 uninitialized field}}
205-
break;
204+
Area = 0;
205+
break; // expected-warning {{1 uninitialized field}}
206206
}
207207
}
208208

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-output text -verify %s
2+
3+
typedef __typeof(sizeof(int)) size_t;
4+
void *malloc(size_t size);
5+
6+
void inf_loop_break_callee() {
7+
void* data = malloc(10); // expected-note{{Memory is allocated}}
8+
while (1) { // expected-note{{Loop condition is true}}
9+
(void)data;
10+
break; // No note that we jump to the line above from this break
11+
} // expected-note@-1{{Execution jumps to the end of the function}}
12+
} // expected-warning{{Potential leak of memory pointed to by 'data'}}
13+
// expected-note@-1 {{Potential leak of memory pointed to by 'data'}}
14+
15+
void inf_loop_break_caller() {
16+
inf_loop_break_callee(); // expected-note{{Calling 'inf_loop_break_callee'}}
17+
}

0 commit comments

Comments
 (0)