Skip to content

Commit 190449a

Browse files
authored
[analyzer] Detect leaks of stack addresses via output params, indirect globals 3/3 (#105648)
Fix some false negatives of StackAddrEscapeChecker: - Output parameters ``` void top(int **out) { int local = 42; *out = &local; // Noncompliant } ``` - Indirect global pointers ``` int **global; void top() { int local = 42; *global = &local; // Noncompliant } ``` Note that now StackAddrEscapeChecker produces a diagnostic if a function with an output parameter is analyzed as top-level or as a callee. I took special care to make sure the reports point to the same primary location and, in many cases, feature the same primary message. That is the motivation to modify Core/BugReporter.cpp and Core/ExplodedGraph.cpp To avoid false positive reports when a global indirect pointer is assigned a local address, invalidated, and then reset, I rely on the fact that the invalidation symbol will be a DerivedSymbol of a ConjuredSymbol that refers to the same memory region. The checker still has a false negative for non-trivial escaping via a returned value. It requires a more sophisticated traversal akin to scanReachableSymbols, which out of the scope of this change. CPP-4734 --------- This is the last of the 3 stacked PRs, it must not be merged before #105652 and #105653
1 parent e5a5ac0 commit 190449a

File tree

12 files changed

+181
-86
lines changed

12 files changed

+181
-86
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ class ExprEngine {
286286
const Stmt *DiagnosticStmt = nullptr,
287287
ProgramPoint::Kind K = ProgramPoint::PreStmtPurgeDeadSymbolsKind);
288288

289+
/// A tag to track convenience transitions, which can be removed at cleanup.
290+
/// This tag applies to a node created after removeDead.
291+
static const ProgramPointTag *cleanupNodeTag();
292+
289293
/// processCFGElement - Called by CoreEngine. Used to generate new successor
290294
/// nodes by processing the 'effects' of a CFG element.
291295
void processCFGElement(const CFGElement E, ExplodedNode *Pred,

clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@ static const MemSpaceRegion *getStackOrGlobalSpaceRegion(const MemRegion *R) {
305305
return nullptr;
306306
}
307307

308+
const MemRegion *getOriginBaseRegion(const MemRegion *Reg) {
309+
Reg = Reg->getBaseRegion();
310+
while (const auto *SymReg = dyn_cast<SymbolicRegion>(Reg)) {
311+
Reg = SymReg->getSymbol()->getOriginRegion()->getBaseRegion();
312+
}
313+
return Reg;
314+
}
315+
308316
std::optional<std::string> printReferrer(const MemRegion *Referrer) {
309317
assert(Referrer);
310318
const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) {
@@ -313,7 +321,8 @@ std::optional<std::string> printReferrer(const MemRegion *Referrer) {
313321
if (isa<GlobalsSpaceRegion>(Space))
314322
return "global";
315323
assert(isa<StackSpaceRegion>(Space));
316-
return "stack";
324+
// This case covers top-level and inlined analyses.
325+
return "caller";
317326
}(getStackOrGlobalSpaceRegion(Referrer));
318327

319328
while (!Referrer->canPrintPretty()) {
@@ -340,19 +349,45 @@ std::optional<std::string> printReferrer(const MemRegion *Referrer) {
340349
return buf;
341350
}
342351

352+
/// Check whether \p Region refers to a freshly minted symbol after an opaque
353+
/// function call.
354+
bool isInvalidatedSymbolRegion(const MemRegion *Region) {
355+
const auto *SymReg = Region->getAs<SymbolicRegion>();
356+
if (!SymReg)
357+
return false;
358+
SymbolRef Symbol = SymReg->getSymbol();
359+
360+
const auto *DerS = dyn_cast<SymbolDerived>(Symbol);
361+
return DerS && isa_and_nonnull<SymbolConjured>(DerS->getParentSymbol());
362+
}
363+
343364
void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
344365
CheckerContext &Ctx) const {
345366
if (!ChecksEnabled[CK_StackAddrEscapeChecker])
346367
return;
347368

348369
ExplodedNode *Node = Ctx.getPredecessor();
349370

371+
bool ExitingTopFrame =
372+
Ctx.getPredecessor()->getLocationContext()->inTopFrame();
373+
374+
if (ExitingTopFrame &&
375+
Node->getLocation().getTag() == ExprEngine::cleanupNodeTag() &&
376+
Node->getFirstPred()) {
377+
// When finishing analysis of a top-level function, engine proactively
378+
// removes dead symbols thus preventing this checker from looking through
379+
// the output parameters. Take 1 step back, to the node where these symbols
380+
// and their bindings are still present
381+
Node = Node->getFirstPred();
382+
}
383+
350384
// Iterate over all bindings to global variables and see if it contains
351385
// a memory region in the stack space.
352386
class CallBack : public StoreManager::BindingsHandler {
353387
private:
354388
CheckerContext &Ctx;
355389
const StackFrameContext *PoppedFrame;
390+
const bool TopFrame;
356391

357392
/// Look for stack variables referring to popped stack variables.
358393
/// Returns true only if it found some dangling stack variables
@@ -368,24 +403,51 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
368403

369404
const auto *ReferrerStackSpace =
370405
ReferrerMemSpace->getAs<StackSpaceRegion>();
406+
371407
if (!ReferrerStackSpace)
372408
return false;
373409

374-
if (ReferredMemSpace->getStackFrame() == PoppedFrame &&
375-
ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) {
410+
if (const auto *ReferredFrame = ReferredMemSpace->getStackFrame();
411+
ReferredFrame != PoppedFrame) {
412+
return false;
413+
}
414+
415+
if (ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) {
416+
V.emplace_back(Referrer, Referred);
417+
return true;
418+
}
419+
if (isa<StackArgumentsSpaceRegion>(ReferrerMemSpace) &&
420+
ReferrerStackSpace->getStackFrame() == PoppedFrame && TopFrame) {
421+
// Output parameter of a top-level function
376422
V.emplace_back(Referrer, Referred);
377423
return true;
378424
}
379425
return false;
380426
}
381427

428+
// Keep track of the variables that were invalidated through an opaque
429+
// function call. Even if the initial values of such variables were bound to
430+
// an address of a local variable, we cannot claim anything now, at the
431+
// function exit, so skip them to avoid false positives.
432+
void recordInInvalidatedRegions(const MemRegion *Region) {
433+
if (isInvalidatedSymbolRegion(Region))
434+
ExcludedRegions.insert(getOriginBaseRegion(Region));
435+
}
436+
382437
public:
383438
SmallVector<std::pair<const MemRegion *, const MemRegion *>, 10> V;
439+
// ExcludedRegions are skipped from reporting.
440+
// I.e., if a referrer in this set, skip the related bug report.
441+
// It is useful to avoid false positive for the variables that were
442+
// reset to a conjured value after an opaque function call.
443+
llvm::SmallPtrSet<const MemRegion *, 4> ExcludedRegions;
384444

385-
CallBack(CheckerContext &CC) : Ctx(CC), PoppedFrame(CC.getStackFrame()) {}
445+
CallBack(CheckerContext &CC, bool TopFrame)
446+
: Ctx(CC), PoppedFrame(CC.getStackFrame()), TopFrame(TopFrame) {}
386447

387448
bool HandleBinding(StoreManager &SMgr, Store S, const MemRegion *Region,
388449
SVal Val) override {
450+
recordInInvalidatedRegions(Region);
389451
const MemRegion *VR = Val.getAsRegion();
390452
if (!VR)
391453
return true;
@@ -394,15 +456,16 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
394456
return true;
395457

396458
// Check the globals for the same.
397-
if (!isa<GlobalsSpaceRegion>(Region->getMemorySpace()))
459+
if (!isa_and_nonnull<GlobalsSpaceRegion>(
460+
getStackOrGlobalSpaceRegion(Region)))
398461
return true;
399462
if (VR && VR->hasStackStorage() && !isNotInCurrentFrame(VR, Ctx))
400463
V.emplace_back(Region, VR);
401464
return true;
402465
}
403466
};
404467

405-
CallBack Cb(Ctx);
468+
CallBack Cb(Ctx, ExitingTopFrame);
406469
ProgramStateRef State = Node->getState();
407470
State->getStateManager().getStoreManager().iterBindings(State->getStore(),
408471
Cb);
@@ -423,6 +486,9 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
423486
for (const auto &P : Cb.V) {
424487
const MemRegion *Referrer = P.first->getBaseRegion();
425488
const MemRegion *Referred = P.second;
489+
if (Cb.ExcludedRegions.contains(getOriginBaseRegion(Referrer))) {
490+
continue;
491+
}
426492

427493
// Generate a report for this bug.
428494
const StringRef CommonSuffix =

clang/lib/StaticAnalyzer/Core/BugReporter.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2436,8 +2436,19 @@ PathSensitiveBugReport::getLocation() const {
24362436
if (auto FE = P.getAs<FunctionExitPoint>()) {
24372437
if (const ReturnStmt *RS = FE->getStmt())
24382438
return PathDiagnosticLocation::createBegin(RS, SM, LC);
2439+
2440+
// If we are exiting a destructor call, it is more useful to point to the
2441+
// next stmt which is usually the temporary declaration.
2442+
// For non-destructor and non-top-level calls, the next stmt will still
2443+
// refer to the last executed stmt of the body.
2444+
S = ErrorNode->getNextStmtForDiagnostics();
2445+
// If next stmt is not found, it is likely the end of a top-level function
2446+
// analysis. find the last execution statement then.
2447+
if (!S)
2448+
S = ErrorNode->getPreviousStmtForDiagnostics();
24392449
}
2440-
S = ErrorNode->getNextStmtForDiagnostics();
2450+
if (!S)
2451+
S = ErrorNode->getNextStmtForDiagnostics();
24412452
}
24422453

24432454
if (S) {

clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ const Stmt *ExplodedNode::getNextStmtForDiagnostics() const {
376376

377377
const Stmt *ExplodedNode::getPreviousStmtForDiagnostics() const {
378378
for (const ExplodedNode *N = getFirstPred(); N; N = N->getFirstPred())
379-
if (const Stmt *S = N->getStmtForDiagnostics())
379+
if (const Stmt *S = N->getStmtForDiagnostics(); S && !isa<CompoundStmt>(S))
380380
return S;
381381

382382
return nullptr;

clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,8 +1072,6 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out,
10721072
CleanedState, SFC, SymReaper);
10731073

10741074
// Process any special transfer function for dead symbols.
1075-
// A tag to track convenience transitions, which can be removed at cleanup.
1076-
static SimpleProgramPointTag cleanupTag(TagProviderName, "Clean Node");
10771075
// Call checkers with the non-cleaned state so that they could query the
10781076
// values of the soon to be dead symbols.
10791077
ExplodedNodeSet CheckedSet;
@@ -1102,10 +1100,15 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out,
11021100
// generate a transition to that state.
11031101
ProgramStateRef CleanedCheckerSt =
11041102
StateMgr.getPersistentStateWithGDM(CleanedState, CheckerState);
1105-
Bldr.generateNode(DiagnosticStmt, I, CleanedCheckerSt, &cleanupTag, K);
1103+
Bldr.generateNode(DiagnosticStmt, I, CleanedCheckerSt, cleanupNodeTag(), K);
11061104
}
11071105
}
11081106

1107+
const ProgramPointTag *ExprEngine::cleanupNodeTag() {
1108+
static SimpleProgramPointTag cleanupTag(TagProviderName, "Clean Node");
1109+
return &cleanupTag;
1110+
}
1111+
11091112
void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) {
11101113
// Reclaim any unnecessary nodes in the ExplodedGraph.
11111114
G.reclaimRecentlyAllocatedNodes();

clang/test/Analysis/copy-elision.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,19 @@ ClassWithoutDestructor make1(AddressVector<ClassWithoutDestructor> &v) {
158158
return ClassWithoutDestructor(v);
159159
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
160160
object of type 'ClassWithoutDestructor' is still \
161-
referred to by the stack variable 'v' upon returning to the caller}}
161+
referred to by the caller variable 'v' upon returning to the caller}}
162162
}
163163
ClassWithoutDestructor make2(AddressVector<ClassWithoutDestructor> &v) {
164164
return make1(v);
165165
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
166166
object of type 'ClassWithoutDestructor' is still \
167-
referred to by the stack variable 'v' upon returning to the caller}}
167+
referred to by the caller variable 'v' upon returning to the caller}}
168168
}
169169
ClassWithoutDestructor make3(AddressVector<ClassWithoutDestructor> &v) {
170170
return make2(v);
171171
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
172172
object of type 'ClassWithoutDestructor' is still \
173-
referred to by the stack variable 'v' upon returning to the caller}}
173+
referred to by the caller variable 'v' upon returning to the caller}}
174174
}
175175

176176
void testMultipleReturns() {
@@ -193,7 +193,7 @@ void testMultipleReturns() {
193193
void consume(ClassWithoutDestructor c) {
194194
c.push();
195195
// expected-warning@-1 {{Address of stack memory associated with local \
196-
variable 'c' is still referred to by the stack variable 'v' upon returning \
196+
variable 'c' is still referred to by the caller variable 'v' upon returning \
197197
to the caller}}
198198
}
199199

@@ -267,7 +267,7 @@ struct TestCtorInitializer {
267267
: c(ClassWithDestructor(v)) {}
268268
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
269269
object of type 'ClassWithDestructor' is still referred \
270-
to by the stack variable 'v' upon returning to the caller}}
270+
to by the caller variable 'v' upon returning to the caller}}
271271
};
272272

273273
void testCtorInitializer() {
@@ -303,19 +303,19 @@ ClassWithDestructor make1(AddressVector<ClassWithDestructor> &v) {
303303
return ClassWithDestructor(v);
304304
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
305305
object of type 'ClassWithDestructor' is still referred \
306-
to by the stack variable 'v' upon returning to the caller}}
306+
to by the caller variable 'v' upon returning to the caller}}
307307
}
308308
ClassWithDestructor make2(AddressVector<ClassWithDestructor> &v) {
309309
return make1(v);
310310
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
311311
object of type 'ClassWithDestructor' is still referred \
312-
to by the stack variable 'v' upon returning to the caller}}
312+
to by the caller variable 'v' upon returning to the caller}}
313313
}
314314
ClassWithDestructor make3(AddressVector<ClassWithDestructor> &v) {
315315
return make2(v);
316316
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
317317
object of type 'ClassWithDestructor' is still referred \
318-
to by the stack variable 'v' upon returning to the caller}}
318+
to by the caller variable 'v' upon returning to the caller}}
319319
}
320320

321321
void testMultipleReturnsWithDestructors() {
@@ -360,7 +360,7 @@ void testMultipleReturnsWithDestructors() {
360360
void consume(ClassWithDestructor c) {
361361
c.push();
362362
// expected-warning@-1 {{Address of stack memory associated with local \
363-
variable 'c' is still referred to by the stack variable 'v' upon returning \
363+
variable 'c' is still referred to by the caller variable 'v' upon returning \
364364
to the caller}}
365365
}
366366

@@ -407,7 +407,7 @@ struct Foo {
407407
Foo make1(Foo **r) {
408408
return Foo(r);
409409
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
410-
object of type 'Foo' is still referred to by the stack \
410+
object of type 'Foo' is still referred to by the caller \
411411
variable 'z' upon returning to the caller}}
412412
}
413413

clang/test/Analysis/incorrect-checker-names.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ char const *p;
1616
void f0() {
1717
char const str[] = "This will change";
1818
p = str;
19-
} // expected-warning{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}}
20-
// expected-note@-1{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}}
19+
} // expected-warning@-1{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference [core.StackAddressEscape]}}
20+
// expected-note@-2{{Address of stack memory associated with local variable 'str' is still referred to by the global variable 'p' upon returning to the caller. This will be a dangling reference}}

clang/test/Analysis/loop-block-counts.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ void callee(void **p) {
66
int x;
77
*p = &x;
88
// expected-warning@-1 {{Address of stack memory associated with local \
9-
variable 'x' is still referred to by the stack variable 'arr' upon \
9+
variable 'x' is still referred to by the caller variable 'arr' upon \
1010
returning to the caller}}
1111
}
1212

clang/test/Analysis/stack-addr-ps.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,13 @@ void callTestRegister(void) {
9898

9999
void top_level_leaking(int **out) {
100100
int local = 42;
101-
*out = &local; // no-warning FIXME
101+
*out = &local; // expected-warning{{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 'out'}}
102102
}
103103

104104
void callee_leaking_via_param(int **out) {
105105
int local = 1;
106106
*out = &local;
107-
// expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the stack variable 'ptr'}}
107+
// expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 'ptr'}}
108108
}
109109

110110
void caller_for_leaking_callee() {
@@ -115,7 +115,7 @@ void caller_for_leaking_callee() {
115115
void callee_nested_leaking(int **out) {
116116
int local = 1;
117117
*out = &local;
118-
// expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the stack variable 'ptr'}}
118+
// expected-warning@-1{{Address of stack memory associated with local variable 'local' is still referred to by the caller variable 'ptr'}}
119119
}
120120

121121
void caller_mid_for_nested_leaking(int **mid) {

0 commit comments

Comments
 (0)