Skip to content

Commit 991f176

Browse files
committed
[analyzer] Detect leaks on top-level via output params, indirect globals
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
1 parent 95b37a7 commit 991f176

File tree

10 files changed

+157
-83
lines changed

10 files changed

+157
-83
lines changed

clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp

Lines changed: 58 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 *Referrer) {
309+
Referrer = Referrer->getBaseRegion();
310+
while (const auto *SymReg = dyn_cast<SymbolicRegion>(Referrer)) {
311+
Referrer = SymReg->getSymbol()->getOriginRegion()->getBaseRegion();
312+
}
313+
return Referrer;
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()) {
@@ -348,12 +357,27 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
348357

349358
ExplodedNode *Node = Ctx.getPredecessor();
350359

360+
bool ExitingTopFrame =
361+
Ctx.getPredecessor()->getLocationContext()->inTopFrame();
362+
363+
if (ExitingTopFrame && Node->getLocation().getTag() &&
364+
Node->getLocation().getTag()->getTagDescription() ==
365+
"ExprEngine : Clean Node" &&
366+
Node->getFirstPred()) {
367+
// When finishing analysis of a top-level function, engine proactively
368+
// removes dead symbols thus preventing this checker from looking through
369+
// the output parameters. Take 1 step back, to the node where these symbols
370+
// and their bindings are still present
371+
Node = Node->getFirstPred();
372+
}
373+
351374
// Iterate over all bindings to global variables and see if it contains
352375
// a memory region in the stack space.
353376
class CallBack : public StoreManager::BindingsHandler {
354377
private:
355378
CheckerContext &Ctx;
356379
const StackFrameContext *PoppedFrame;
380+
const bool TopFrame;
357381

358382
/// Look for stack variables referring to popped stack variables.
359383
/// Returns true only if it found some dangling stack variables
@@ -369,24 +393,48 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
369393

370394
const auto *ReferrerStackSpace =
371395
ReferrerMemSpace->getAs<StackSpaceRegion>();
396+
372397
if (!ReferrerStackSpace)
373398
return false;
374399

375-
if (ReferredMemSpace->getStackFrame() == PoppedFrame &&
376-
ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) {
400+
if (const auto *ReferredFrame = ReferredMemSpace->getStackFrame();
401+
ReferredFrame != PoppedFrame) {
402+
return false;
403+
}
404+
405+
if (ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) {
406+
V.emplace_back(Referrer, Referred);
407+
return true;
408+
}
409+
if (isa<StackArgumentsSpaceRegion>(ReferrerMemSpace) &&
410+
ReferrerStackSpace->getStackFrame() == PoppedFrame && TopFrame) {
411+
// Output parameter of a top-level function
377412
V.emplace_back(Referrer, Referred);
378413
return true;
379414
}
380415
return false;
381416
}
382417

418+
void updateInvalidatedRegions(const MemRegion *Region) {
419+
if (const auto *SymReg = Region->getAs<SymbolicRegion>()) {
420+
SymbolRef Symbol = SymReg->getSymbol();
421+
if (const auto *DerS = dyn_cast<SymbolDerived>(Symbol);
422+
DerS && isa_and_nonnull<SymbolConjured>(DerS->getParentSymbol())) {
423+
InvalidatedRegions.insert(Symbol->getOriginRegion()->getBaseRegion());
424+
}
425+
}
426+
}
427+
383428
public:
384429
SmallVector<std::pair<const MemRegion *, const MemRegion *>, 10> V;
430+
llvm::SmallPtrSet<const MemRegion *, 4> InvalidatedRegions;
385431

386-
CallBack(CheckerContext &CC) : Ctx(CC), PoppedFrame(CC.getStackFrame()) {}
432+
CallBack(CheckerContext &CC, bool TopFrame)
433+
: Ctx(CC), PoppedFrame(CC.getStackFrame()), TopFrame(TopFrame) {}
387434

388435
bool HandleBinding(StoreManager &SMgr, Store S, const MemRegion *Region,
389436
SVal Val) override {
437+
updateInvalidatedRegions(Region);
390438
const MemRegion *VR = Val.getAsRegion();
391439
if (!VR)
392440
return true;
@@ -395,15 +443,16 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
395443
return true;
396444

397445
// Check the globals for the same.
398-
if (!isa<GlobalsSpaceRegion>(Region->getMemorySpace()))
446+
if (!isa_and_nonnull<GlobalsSpaceRegion>(
447+
getStackOrGlobalSpaceRegion(Region)))
399448
return true;
400449
if (VR && VR->hasStackStorage() && !isNotInCurrentFrame(VR, Ctx))
401450
V.emplace_back(Region, VR);
402451
return true;
403452
}
404453
};
405454

406-
CallBack Cb(Ctx);
455+
CallBack Cb(Ctx, ExitingTopFrame);
407456
ProgramStateRef State = Node->getState();
408457
State->getStateManager().getStoreManager().iterBindings(State->getStore(),
409458
Cb);
@@ -424,6 +473,9 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
424473
for (const auto &P : Cb.V) {
425474
const MemRegion *Referrer = P.first->getBaseRegion();
426475
const MemRegion *Referred = P.second;
476+
if (Cb.InvalidatedRegions.contains(getOriginBaseRegion(Referrer))) {
477+
continue;
478+
}
427479

428480
// Generate a report for this bug.
429481
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/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)