Skip to content

Commit 5536cdd

Browse files
FlandiniIcohedron
authored andcommitted
[analyzer] Remove some false negatives in StackAddrEscapeChecker (llvm#125638)
Fixes llvm#123459. Previously, when the StackAddrEscapeChecker checked return values, it did not scan into the structure of the return SVal. Now it does, and we can catch some more false negatives that were already mocked out in the tests in addition to those mentioned in llvm#123459. The warning message at the moment for these newly caught leaks is not great. I think they would be better if they had a better trace of why and how the region leaks. If y'all are happy with these changes, I would try to improve these warnings and work on normalizing this SVal checking on the `checkEndFunction` side of the checker also. Two of the stack address leak test cases now have two warnings, one warning from return expression checking and another from` checkEndFunction` `iterBindings` checking. For these two cases, I prefer the warnings from the return expression checking, but I couldn't figure out a way to drop the `checkEndFunction` without breaking other `checkEndFunction` warnings that we do want. Thoughts here?
1 parent 3f8bc23 commit 5536cdd

File tree

5 files changed

+385
-81
lines changed

5 files changed

+385
-81
lines changed

clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp

Lines changed: 129 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ class StackAddrEscapeChecker
5454
CheckerContext &C) const;
5555
void checkAsyncExecutedBlockCaptures(const BlockDataRegion &B,
5656
CheckerContext &C) const;
57-
void EmitStackError(CheckerContext &C, const MemRegion *R,
58-
const Expr *RetE) const;
57+
void EmitReturnLeakError(CheckerContext &C, const MemRegion *LeakedRegion,
58+
const Expr *RetE) const;
5959
bool isSemaphoreCaptured(const BlockDecl &B) const;
6060
static SourceRange genName(raw_ostream &os, const MemRegion *R,
6161
ASTContext &Ctx);
@@ -147,21 +147,38 @@ StackAddrEscapeChecker::getCapturedStackRegions(const BlockDataRegion &B,
147147
return Regions;
148148
}
149149

150-
void StackAddrEscapeChecker::EmitStackError(CheckerContext &C,
151-
const MemRegion *R,
152-
const Expr *RetE) const {
150+
static void EmitReturnedAsPartOfError(llvm::raw_ostream &OS, SVal ReturnedVal,
151+
const MemRegion *LeakedRegion) {
152+
if (const MemRegion *ReturnedRegion = ReturnedVal.getAsRegion()) {
153+
if (isa<BlockDataRegion>(ReturnedRegion)) {
154+
OS << " is captured by a returned block";
155+
return;
156+
}
157+
}
158+
159+
// Generic message
160+
OS << " returned to caller";
161+
}
162+
163+
void StackAddrEscapeChecker::EmitReturnLeakError(CheckerContext &C,
164+
const MemRegion *R,
165+
const Expr *RetE) const {
153166
ExplodedNode *N = C.generateNonFatalErrorNode();
154167
if (!N)
155168
return;
156169
if (!BT_returnstack)
157170
BT_returnstack = std::make_unique<BugType>(
158171
CheckNames[CK_StackAddrEscapeChecker],
159172
"Return of address to stack-allocated memory");
173+
160174
// Generate a report for this bug.
161175
SmallString<128> buf;
162176
llvm::raw_svector_ostream os(buf);
177+
178+
// Error message formatting
163179
SourceRange range = genName(os, R, C.getASTContext());
164-
os << " returned to caller";
180+
EmitReturnedAsPartOfError(os, C.getSVal(RetE), R);
181+
165182
auto report =
166183
std::make_unique<PathSensitiveBugReport>(*BT_returnstack, os.str(), N);
167184
report->addRange(RetE->getSourceRange());
@@ -209,30 +226,6 @@ void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures(
209226
}
210227
}
211228

212-
void StackAddrEscapeChecker::checkReturnedBlockCaptures(
213-
const BlockDataRegion &B, CheckerContext &C) const {
214-
for (const MemRegion *Region : getCapturedStackRegions(B, C)) {
215-
if (isNotInCurrentFrame(Region, C))
216-
continue;
217-
ExplodedNode *N = C.generateNonFatalErrorNode();
218-
if (!N)
219-
continue;
220-
if (!BT_capturedstackret)
221-
BT_capturedstackret = std::make_unique<BugType>(
222-
CheckNames[CK_StackAddrEscapeChecker],
223-
"Address of stack-allocated memory is captured");
224-
SmallString<128> Buf;
225-
llvm::raw_svector_ostream Out(Buf);
226-
SourceRange Range = genName(Out, Region, C.getASTContext());
227-
Out << " is captured by a returned block";
228-
auto Report = std::make_unique<PathSensitiveBugReport>(*BT_capturedstackret,
229-
Out.str(), N);
230-
if (Range.isValid())
231-
Report->addRange(Range);
232-
C.emitReport(std::move(Report));
233-
}
234-
}
235-
236229
void StackAddrEscapeChecker::checkPreCall(const CallEvent &Call,
237230
CheckerContext &C) const {
238231
if (!ChecksEnabled[CK_StackAddrAsyncEscapeChecker])
@@ -247,45 +240,128 @@ void StackAddrEscapeChecker::checkPreCall(const CallEvent &Call,
247240
}
248241
}
249242

250-
void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS,
251-
CheckerContext &C) const {
252-
if (!ChecksEnabled[CK_StackAddrEscapeChecker])
253-
return;
243+
/// A visitor made for use with a ScanReachableSymbols scanner, used
244+
/// for finding stack regions within an SVal that live on the current
245+
/// stack frame of the given checker context. This visitor excludes
246+
/// NonParamVarRegion that data is bound to in a BlockDataRegion's
247+
/// bindings, since these are likely uninteresting, e.g., in case a
248+
/// temporary is constructed on the stack, but it captures values
249+
/// that would leak.
250+
class FindStackRegionsSymbolVisitor final : public SymbolVisitor {
251+
CheckerContext &Ctxt;
252+
const StackFrameContext *StackFrameContext;
253+
SmallVectorImpl<const MemRegion *> &EscapingStackRegions;
254254

255-
const Expr *RetE = RS->getRetValue();
256-
if (!RetE)
257-
return;
258-
RetE = RetE->IgnoreParens();
255+
public:
256+
explicit FindStackRegionsSymbolVisitor(
257+
CheckerContext &Ctxt,
258+
SmallVectorImpl<const MemRegion *> &StorageForStackRegions)
259+
: Ctxt(Ctxt), StackFrameContext(Ctxt.getStackFrame()),
260+
EscapingStackRegions(StorageForStackRegions) {}
259261

260-
SVal V = C.getSVal(RetE);
261-
const MemRegion *R = V.getAsRegion();
262-
if (!R)
263-
return;
262+
bool VisitSymbol(SymbolRef sym) override { return true; }
264263

265-
if (const BlockDataRegion *B = dyn_cast<BlockDataRegion>(R))
266-
checkReturnedBlockCaptures(*B, C);
264+
bool VisitMemRegion(const MemRegion *MR) override {
265+
SaveIfEscapes(MR);
267266

268-
if (!isa<StackSpaceRegion>(R->getMemorySpace()) || isNotInCurrentFrame(R, C))
269-
return;
267+
if (const BlockDataRegion *BDR = MR->getAs<BlockDataRegion>())
268+
return VisitBlockDataRegionCaptures(BDR);
269+
270+
return true;
271+
}
272+
273+
private:
274+
void SaveIfEscapes(const MemRegion *MR) {
275+
const StackSpaceRegion *SSR =
276+
MR->getMemorySpace()->getAs<StackSpaceRegion>();
277+
if (SSR && SSR->getStackFrame() == StackFrameContext)
278+
EscapingStackRegions.push_back(MR);
279+
}
280+
281+
bool VisitBlockDataRegionCaptures(const BlockDataRegion *BDR) {
282+
for (auto Var : BDR->referenced_vars()) {
283+
SVal Val = Ctxt.getState()->getSVal(Var.getCapturedRegion());
284+
const MemRegion *Region = Val.getAsRegion();
285+
if (Region) {
286+
SaveIfEscapes(Region);
287+
VisitMemRegion(Region);
288+
}
289+
}
290+
291+
return false;
292+
}
293+
};
294+
295+
/// Given some memory regions that are flagged by FindStackRegionsSymbolVisitor,
296+
/// this function filters out memory regions that are being returned that are
297+
/// likely not true leaks:
298+
/// 1. If returning a block data region that has stack memory space
299+
/// 2. If returning a constructed object that has stack memory space
300+
static SmallVector<const MemRegion *> FilterReturnExpressionLeaks(
301+
const SmallVectorImpl<const MemRegion *> &MaybeEscaped, CheckerContext &C,
302+
const Expr *RetE, SVal &RetVal) {
303+
304+
SmallVector<const MemRegion *> WillEscape;
305+
306+
const MemRegion *RetRegion = RetVal.getAsRegion();
270307

271308
// Returning a record by value is fine. (In this case, the returned
272309
// expression will be a copy-constructor, possibly wrapped in an
273310
// ExprWithCleanups node.)
274311
if (const ExprWithCleanups *Cleanup = dyn_cast<ExprWithCleanups>(RetE))
275312
RetE = Cleanup->getSubExpr();
276-
if (isa<CXXConstructExpr>(RetE) && RetE->getType()->isRecordType())
277-
return;
313+
bool IsConstructExpr =
314+
isa<CXXConstructExpr>(RetE) && RetE->getType()->isRecordType();
278315

279316
// The CK_CopyAndAutoreleaseBlockObject cast causes the block to be copied
280317
// so the stack address is not escaping here.
318+
bool IsCopyAndAutoreleaseBlockObj = false;
281319
if (const auto *ICE = dyn_cast<ImplicitCastExpr>(RetE)) {
282-
if (isa<BlockDataRegion>(R) &&
283-
ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) {
284-
return;
285-
}
320+
IsCopyAndAutoreleaseBlockObj =
321+
isa_and_nonnull<BlockDataRegion>(RetRegion) &&
322+
ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject;
323+
}
324+
325+
for (const MemRegion *MR : MaybeEscaped) {
326+
if (RetRegion == MR && (IsCopyAndAutoreleaseBlockObj || IsConstructExpr))
327+
continue;
328+
329+
WillEscape.push_back(MR);
286330
}
287331

288-
EmitStackError(C, R, RetE);
332+
return WillEscape;
333+
}
334+
335+
/// For use in finding regions that live on the checker context's current
336+
/// stack frame, deep in the SVal representing the return value.
337+
static SmallVector<const MemRegion *>
338+
FindEscapingStackRegions(CheckerContext &C, const Expr *RetE, SVal RetVal) {
339+
SmallVector<const MemRegion *> FoundStackRegions;
340+
341+
FindStackRegionsSymbolVisitor Finder(C, FoundStackRegions);
342+
ScanReachableSymbols Scanner(C.getState(), Finder);
343+
Scanner.scan(RetVal);
344+
345+
return FilterReturnExpressionLeaks(FoundStackRegions, C, RetE, RetVal);
346+
}
347+
348+
void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS,
349+
CheckerContext &C) const {
350+
if (!ChecksEnabled[CK_StackAddrEscapeChecker])
351+
return;
352+
353+
const Expr *RetE = RS->getRetValue();
354+
if (!RetE)
355+
return;
356+
RetE = RetE->IgnoreParens();
357+
358+
SVal V = C.getSVal(RetE);
359+
360+
SmallVector<const MemRegion *> EscapedStackRegions =
361+
FindEscapingStackRegions(C, RetE, V);
362+
363+
for (const MemRegion *ER : EscapedStackRegions)
364+
EmitReturnLeakError(C, ER, RetE);
289365
}
290366

291367
static const MemSpaceRegion *getStackOrGlobalSpaceRegion(const MemRegion *R) {

clang/test/Analysis/copy-elision.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,20 +154,26 @@ class ClassWithoutDestructor {
154154
void push() { v.push(this); }
155155
};
156156

157+
// Two warnings on no-elide: arg v holds the address of the temporary, and we
158+
// are returning an object which holds v which holds the address of the temporary
157159
ClassWithoutDestructor make1(AddressVector<ClassWithoutDestructor> &v) {
158-
return ClassWithoutDestructor(v);
160+
return ClassWithoutDestructor(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}}
159161
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
160162
object of type 'ClassWithoutDestructor' is still \
161163
referred to by the caller variable 'v' upon returning to the caller}}
162164
}
165+
// Two warnings on no-elide: arg v holds the address of the temporary, and we
166+
// are returning an object which holds v which holds the address of the temporary
163167
ClassWithoutDestructor make2(AddressVector<ClassWithoutDestructor> &v) {
164-
return make1(v);
168+
return make1(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}}
165169
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
166170
object of type 'ClassWithoutDestructor' is still \
167171
referred to by the caller variable 'v' upon returning to the caller}}
168172
}
173+
// Two warnings on no-elide: arg v holds the address of the temporary, and we
174+
// are returning an object which holds v which holds the address of the temporary
169175
ClassWithoutDestructor make3(AddressVector<ClassWithoutDestructor> &v) {
170-
return make2(v);
176+
return make2(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}}
171177
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
172178
object of type 'ClassWithoutDestructor' is still \
173179
referred to by the caller variable 'v' upon returning to the caller}}
@@ -298,21 +304,26 @@ to by the caller variable 'v' upon returning to the caller}}
298304
#endif
299305
}
300306

301-
307+
// Two warnings on no-elide: arg v holds the address of the temporary, and we
308+
// are returning an object which holds v which holds the address of the temporary
302309
ClassWithDestructor make1(AddressVector<ClassWithDestructor> &v) {
303-
return ClassWithDestructor(v);
310+
return ClassWithDestructor(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}}
304311
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
305312
object of type 'ClassWithDestructor' is still referred \
306313
to by the caller variable 'v' upon returning to the caller}}
307314
}
315+
// Two warnings on no-elide: arg v holds the address of the temporary, and we
316+
// are returning an object which holds v which holds the address of the temporary
308317
ClassWithDestructor make2(AddressVector<ClassWithDestructor> &v) {
309-
return make1(v);
318+
return make1(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}}
310319
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
311320
object of type 'ClassWithDestructor' is still referred \
312321
to by the caller variable 'v' upon returning to the caller}}
313322
}
323+
// Two warnings on no-elide: arg v holds the address of the temporary, and we
324+
// are returning an object which holds v which holds the address of the temporary
314325
ClassWithDestructor make3(AddressVector<ClassWithDestructor> &v) {
315-
return make2(v);
326+
return make2(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}}
316327
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
317328
object of type 'ClassWithDestructor' is still referred \
318329
to by the caller variable 'v' upon returning to the caller}}

0 commit comments

Comments
 (0)