Skip to content

Commit 6ad47e1

Browse files
author
Balazs Benics
committed
[analyzer] Catch leaking stack addresses via stack variables
Not only global variables can hold references to dead stack variables. Consider this example: void write_stack_address_to(char **q) { char local; *q = &local; } void test_stack() { char *p; write_stack_address_to(&p); } The address of 'local' is assigned to 'p', which becomes a dangling pointer after 'write_stack_address_to()' returns. The StackAddrEscapeChecker was looking for bindings in the store which referred to variables of the popped stack frame, but it only considered global variables in this regard. This patch relaxes this, catching stack variable bindings as well. --- This patch also works for temporary objects like: struct Bar { const int &ref; explicit Bar(int y) : ref(y) { // Okay. } // End of the constructor call, `ref` is dangling now. Warning! }; void test() { Bar{33}; // Temporary object, so the corresponding memregion is // *not* a VarRegion. } --- The return value optimization aka. copy-elision might kick in but that is modeled by passing an imaginary CXXThisRegion which refers to the parent stack frame which is supposed to be the 'return slot'. Objects residing in the 'return slot' outlive the scope of the inner call, thus we should expect no warning about them - except if we explicitly disable copy-elision. Reviewed By: NoQ, martong Differential Revision: https://reviews.llvm.org/D107078
1 parent c22bd39 commit 6ad47e1

File tree

5 files changed

+173
-20
lines changed

5 files changed

+173
-20
lines changed

clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313

14-
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
1514
#include "clang/AST/ExprCXX.h"
1615
#include "clang/Basic/SourceManager.h"
16+
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
1717
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
1818
#include "clang/StaticAnalyzer/Core/Checker.h"
1919
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -303,21 +303,53 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
303303
class CallBack : public StoreManager::BindingsHandler {
304304
private:
305305
CheckerContext &Ctx;
306-
const StackFrameContext *CurSFC;
306+
const StackFrameContext *PoppedFrame;
307+
308+
/// Look for stack variables referring to popped stack variables.
309+
/// Returns true only if it found some dangling stack variables
310+
/// referred by an other stack variable from different stack frame.
311+
bool checkForDanglingStackVariable(const MemRegion *Referrer,
312+
const MemRegion *Referred) {
313+
const auto *ReferrerMemSpace =
314+
Referrer->getMemorySpace()->getAs<StackSpaceRegion>();
315+
const auto *ReferredMemSpace =
316+
Referred->getMemorySpace()->getAs<StackSpaceRegion>();
317+
318+
if (!ReferrerMemSpace || !ReferredMemSpace)
319+
return false;
320+
321+
const auto *ReferrerFrame = ReferrerMemSpace->getStackFrame();
322+
const auto *ReferredFrame = ReferredMemSpace->getStackFrame();
323+
324+
if (ReferrerMemSpace && ReferredMemSpace) {
325+
if (ReferredFrame == PoppedFrame &&
326+
ReferrerFrame->isParentOf(PoppedFrame)) {
327+
V.emplace_back(Referrer, Referred);
328+
return true;
329+
}
330+
}
331+
return false;
332+
}
307333

308334
public:
309335
SmallVector<std::pair<const MemRegion *, const MemRegion *>, 10> V;
310336

311-
CallBack(CheckerContext &CC) : Ctx(CC), CurSFC(CC.getStackFrame()) {}
337+
CallBack(CheckerContext &CC) : Ctx(CC), PoppedFrame(CC.getStackFrame()) {}
312338

313339
bool HandleBinding(StoreManager &SMgr, Store S, const MemRegion *Region,
314340
SVal Val) override {
341+
const MemRegion *VR = Val.getAsRegion();
342+
if (!VR)
343+
return true;
344+
345+
if (checkForDanglingStackVariable(Region, VR))
346+
return true;
315347

348+
// Check the globals for the same.
316349
if (!isa<GlobalsSpaceRegion>(Region->getMemorySpace()))
317350
return true;
318-
const MemRegion *VR = Val.getAsRegion();
319-
if (VR && isa<StackSpaceRegion>(VR->getMemorySpace()) &&
320-
!isArcManagedBlock(VR, Ctx) && !isNotInCurrentFrame(VR, Ctx))
351+
if (VR && VR->hasStackStorage() && !isArcManagedBlock(VR, Ctx) &&
352+
!isNotInCurrentFrame(VR, Ctx))
321353
V.emplace_back(Region, VR);
322354
return true;
323355
}
@@ -344,19 +376,41 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
344376
"invalid after returning from the function");
345377

346378
for (const auto &P : Cb.V) {
379+
const MemRegion *Referrer = P.first;
380+
const MemRegion *Referred = P.second;
381+
347382
// Generate a report for this bug.
383+
const StringRef CommonSuffix =
384+
"upon returning to the caller. This will be a dangling reference";
348385
SmallString<128> Buf;
349386
llvm::raw_svector_ostream Out(Buf);
350-
SourceRange Range = genName(Out, P.second, Ctx.getASTContext());
351-
Out << " is still referred to by the ";
352-
if (isa<StaticGlobalSpaceRegion>(P.first->getMemorySpace()))
353-
Out << "static";
354-
else
355-
Out << "global";
356-
Out << " variable '";
357-
const VarRegion *VR = cast<VarRegion>(P.first->getBaseRegion());
358-
Out << *VR->getDecl()
359-
<< "' upon returning to the caller. This will be a dangling reference";
387+
const SourceRange Range = genName(Out, Referred, Ctx.getASTContext());
388+
389+
if (isa<CXXTempObjectRegion>(Referrer)) {
390+
Out << " is still referred to by a temporary object on the stack "
391+
<< CommonSuffix;
392+
auto Report =
393+
std::make_unique<PathSensitiveBugReport>(*BT_stackleak, Out.str(), N);
394+
Ctx.emitReport(std::move(Report));
395+
return;
396+
}
397+
398+
const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) {
399+
if (isa<StaticGlobalSpaceRegion>(Space))
400+
return "static";
401+
if (isa<GlobalsSpaceRegion>(Space))
402+
return "global";
403+
assert(isa<StackSpaceRegion>(Space));
404+
return "stack";
405+
}(Referrer->getMemorySpace());
406+
407+
// This cast supposed to succeed.
408+
const VarRegion *ReferrerVar = cast<VarRegion>(Referrer->getBaseRegion());
409+
const std::string ReferrerVarName =
410+
ReferrerVar->getDecl()->getDeclName().getAsString();
411+
412+
Out << " is still referred to by the " << ReferrerMemorySpace
413+
<< " variable '" << ReferrerVarName << "' " << CommonSuffix;
360414
auto Report =
361415
std::make_unique<PathSensitiveBugReport>(*BT_stackleak, Out.str(), N);
362416
if (Range.isValid())

clang/test/Analysis/copy-elision.cpp

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 -verify -analyzer-config eagerly-assume=false %s
2-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify -analyzer-config eagerly-assume=false %s
3-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 -analyzer-config elide-constructors=false -DNO_ELIDE_FLAG -verify -analyzer-config eagerly-assume=false %s
4-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -analyzer-config elide-constructors=false -DNO_ELIDE_FLAG -verify -analyzer-config eagerly-assume=false %s
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 \
2+
// RUN: -analyzer-config eagerly-assume=false -verify %s
3+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 \
4+
// RUN: -analyzer-config eagerly-assume=false -verify %s
5+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 \
6+
// RUN: -analyzer-config elide-constructors=false -DNO_ELIDE_FLAG \
7+
// RUN: -analyzer-config eagerly-assume=false -verify=expected,no-elide %s
8+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 \
9+
// RUN: -analyzer-config elide-constructors=false \
10+
// RUN: -analyzer-config eagerly-assume=false -verify %s
511

612
// Copy elision always occurs in C++17, otherwise it's under
713
// an on-by-default flag.
@@ -149,12 +155,21 @@ class ClassWithoutDestructor {
149155

150156
ClassWithoutDestructor make1(AddressVector<ClassWithoutDestructor> &v) {
151157
return ClassWithoutDestructor(v);
158+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
159+
object of type 'address_vector_tests::ClassWithoutDestructor' is still \
160+
referred to by the stack variable 'v' upon returning to the caller}}
152161
}
153162
ClassWithoutDestructor make2(AddressVector<ClassWithoutDestructor> &v) {
154163
return make1(v);
164+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
165+
object of type 'address_vector_tests::ClassWithoutDestructor' is still \
166+
referred to by the stack variable 'v' upon returning to the caller}}
155167
}
156168
ClassWithoutDestructor make3(AddressVector<ClassWithoutDestructor> &v) {
157169
return make2(v);
170+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
171+
object of type 'address_vector_tests::ClassWithoutDestructor' is still \
172+
referred to by the stack variable 'v' upon returning to the caller}}
158173
}
159174

160175
void testMultipleReturns() {
@@ -176,6 +191,9 @@ void testMultipleReturns() {
176191

177192
void consume(ClassWithoutDestructor c) {
178193
c.push();
194+
// expected-warning@-1 {{Address of stack memory associated with local \
195+
variable 'c' is still referred to by the stack variable 'v' upon returning \
196+
to the caller}}
179197
}
180198

181199
void testArgumentConstructorWithoutDestructor() {
@@ -246,6 +264,9 @@ struct TestCtorInitializer {
246264
ClassWithDestructor c;
247265
TestCtorInitializer(AddressVector<ClassWithDestructor> &v)
248266
: c(ClassWithDestructor(v)) {}
267+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
268+
object of type 'address_vector_tests::ClassWithDestructor' is still referred \
269+
to by the stack variable 'v' upon returning to the caller}}
249270
};
250271

251272
void testCtorInitializer() {
@@ -279,12 +300,21 @@ void testCtorInitializer() {
279300

280301
ClassWithDestructor make1(AddressVector<ClassWithDestructor> &v) {
281302
return ClassWithDestructor(v);
303+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
304+
object of type 'address_vector_tests::ClassWithDestructor' is still referred \
305+
to by the stack variable 'v' upon returning to the caller}}
282306
}
283307
ClassWithDestructor make2(AddressVector<ClassWithDestructor> &v) {
284308
return make1(v);
309+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
310+
object of type 'address_vector_tests::ClassWithDestructor' is still referred \
311+
to by the stack variable 'v' upon returning to the caller}}
285312
}
286313
ClassWithDestructor make3(AddressVector<ClassWithDestructor> &v) {
287314
return make2(v);
315+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
316+
object of type 'address_vector_tests::ClassWithDestructor' is still referred \
317+
to by the stack variable 'v' upon returning to the caller}}
288318
}
289319

290320
void testMultipleReturnsWithDestructors() {
@@ -328,6 +358,9 @@ void testMultipleReturnsWithDestructors() {
328358

329359
void consume(ClassWithDestructor c) {
330360
c.push();
361+
// expected-warning@-1 {{Address of stack memory associated with local \
362+
variable 'c' is still referred to by the stack variable 'v' upon returning \
363+
to the caller}}
331364
}
332365

333366
void testArgumentConstructorWithDestructor() {
@@ -364,4 +397,24 @@ void testArgumentConstructorWithDestructor() {
364397
#endif
365398
}
366399

400+
struct Foo {
401+
Foo(Foo **q) {
402+
*q = this;
403+
}
404+
};
405+
406+
Foo make1(Foo **r) {
407+
return Foo(r);
408+
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
409+
object of type 'address_vector_tests::Foo' is still referred to by the stack \
410+
variable 'z' upon returning to the caller}}
411+
}
412+
413+
void test_copy_elision() {
414+
Foo *z;
415+
// If the copy elided, 'z' points to 'tmp', otherwise it's a dangling pointer.
416+
Foo tmp = make1(&z);
417+
(void)tmp;
418+
}
419+
367420
} // namespace address_vector_tests

clang/test/Analysis/cxx-uninitialized-object-ptr-ref.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ struct UntypedAllocaTest {
7171
void *allocaPtr;
7272
int dontGetFilteredByNonPedanticMode = 0;
7373

74+
// expected-warning-re@+3 {{Address of stack memory allocated by call to \
75+
alloca() on line {{[0-9]+}} is still referred to by a temporary object on the \
76+
stack upon returning to the caller. This will be a dangling reference}}
7477
UntypedAllocaTest() : allocaPtr(__builtin_alloca(sizeof(int))) {
7578
// All good!
7679
}
@@ -86,6 +89,9 @@ struct TypedAllocaTest1 {
8689

8790
TypedAllocaTest1() // expected-warning{{1 uninitialized field}}
8891
: allocaPtr(static_cast<int *>(__builtin_alloca(sizeof(int)))) {}
92+
// expected-warning-re@-2 {{Address of stack memory allocated by call to \
93+
alloca() on line {{[0-9]+}} is still referred to by a temporary object on the \
94+
stack upon returning to the caller. This will be a dangling reference}}
8995
};
9096

9197
void fTypedAllocaTest1() {
@@ -96,6 +102,9 @@ struct TypedAllocaTest2 {
96102
int *allocaPtr;
97103
int dontGetFilteredByNonPedanticMode = 0;
98104

105+
// expected-warning-re@+5 {{Address of stack memory allocated by call to \
106+
alloca() on line {{[0-9]+}} is still referred to by a temporary object on the \
107+
stack upon returning to the caller. This will be a dangling reference}}
99108
TypedAllocaTest2()
100109
: allocaPtr(static_cast<int *>(__builtin_alloca(sizeof(int)))) {
101110
*allocaPtr = 55555;
@@ -341,6 +350,9 @@ class VoidPointerRRefTest1 {
341350
void *&&vptrrref; // expected-note {{here}}
342351

343352
public:
353+
// expected-warning@+3 {{Address of stack memory associated with local \
354+
variable 'vptr' is still referred to by a temporary object on the stack \
355+
upon returning to the caller. This will be a dangling reference}}
344356
VoidPointerRRefTest1(void *vptr, char) : vptrrref(static_cast<void *&&>(vptr)) { // expected-warning {{binding reference member 'vptrrref' to stack allocated parameter 'vptr'}}
345357
// All good!
346358
}
@@ -355,6 +367,9 @@ class VoidPointerRRefTest2 {
355367
void **&&vpptrrref; // expected-note {{here}}
356368

357369
public:
370+
// expected-warning@+3 {{Address of stack memory associated with local \
371+
variable 'vptr' is still referred to by a temporary object on the stack \
372+
upon returning to the caller. This will be a dangling reference}}
358373
VoidPointerRRefTest2(void **vptr, char) : vpptrrref(static_cast<void **&&>(vptr)) { // expected-warning {{binding reference member 'vpptrrref' to stack allocated parameter 'vptr'}}
359374
// All good!
360375
}
@@ -369,6 +384,9 @@ class VoidPointerLRefTest {
369384
void *&vptrrref; // expected-note {{here}}
370385

371386
public:
387+
// expected-warning@+3 {{Address of stack memory associated with local \
388+
variable 'vptr' is still referred to by a temporary object on the stack \
389+
upon returning to the caller. This will be a dangling reference}}
372390
VoidPointerLRefTest(void *vptr, char) : vptrrref(static_cast<void *&>(vptr)) { // expected-warning {{binding reference member 'vptrrref' to stack allocated parameter 'vptr'}}
373391
// All good!
374392
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ void clang_analyzer_eval(int);
55
void callee(void **p) {
66
int x;
77
*p = &x;
8+
// expected-warning@-1 {{Address of stack memory associated with local \
9+
variable 'x' is still referred to by the stack variable 'arr' upon \
10+
returning to the caller}}
811
}
912

1013
void loop() {

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,28 @@ namespace rdar13296133 {
137137
}
138138
}
139139

140+
void write_stack_address_to(char **q) {
141+
char local;
142+
*q = &local;
143+
// expected-warning@-1 {{Address of stack memory associated with local \
144+
variable 'local' is still referred to by the stack variable 'p' upon \
145+
returning to the caller}}
146+
}
147+
148+
void test_stack() {
149+
char *p;
150+
write_stack_address_to(&p);
151+
}
152+
153+
struct C {
154+
~C() {} // non-trivial class
155+
};
156+
157+
C make1() {
158+
C c;
159+
return c; // no-warning
160+
}
161+
162+
void test_copy_elision() {
163+
C c1 = make1();
164+
}

0 commit comments

Comments
 (0)