-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[analyzer] Remove some false negatives in StackAddrEscapeChecker #125638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
0674909
0a26f83
3ebf222
17bf250
89faf66
f225a2d
5816e91
27c6cf8
b73ad74
13eac11
129777e
adb78d1
2c62960
e43fb34
8c39d66
76493d1
82400a7
39df282
65bee7a
47f3b40
6a2a01f
30ea259
567c8e6
4d4d537
a4ad3cb
8603e15
7953dba
51f8e2e
029d122
62ef423
e69ea53
9d4379e
1d6e363
b679462
571f8ee
6fff293
9125ed8
bb1b70e
16e3dc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,8 +54,8 @@ class StackAddrEscapeChecker | |
| CheckerContext &C) const; | ||
| void checkAsyncExecutedBlockCaptures(const BlockDataRegion &B, | ||
| CheckerContext &C) const; | ||
| void EmitStackError(CheckerContext &C, const MemRegion *R, | ||
| const Expr *RetE) const; | ||
| void EmitReturnLeakError(CheckerContext &C, const MemRegion *LeakedRegion, | ||
| const Expr *RetE) const; | ||
| bool isSemaphoreCaptured(const BlockDecl &B) const; | ||
| static SourceRange genName(raw_ostream &os, const MemRegion *R, | ||
| ASTContext &Ctx); | ||
|
|
@@ -147,21 +147,38 @@ StackAddrEscapeChecker::getCapturedStackRegions(const BlockDataRegion &B, | |
| return Regions; | ||
| } | ||
|
|
||
| void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, | ||
| const MemRegion *R, | ||
| const Expr *RetE) const { | ||
| static void EmitReturnedAsPartOfError(llvm::raw_ostream &OS, SVal ReturnedVal, | ||
| const MemRegion *LeakedRegion) { | ||
| if (const MemRegion *ReturnedRegion = ReturnedVal.getAsRegion()) { | ||
| if (isa<BlockDataRegion>(ReturnedRegion)) { | ||
| OS << " is captured by a returned block"; | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // Generic message | ||
| OS << " returned to caller"; | ||
| } | ||
|
|
||
| void StackAddrEscapeChecker::EmitReturnLeakError(CheckerContext &C, | ||
| const MemRegion *R, | ||
| const Expr *RetE) const { | ||
| ExplodedNode *N = C.generateNonFatalErrorNode(); | ||
| if (!N) | ||
| return; | ||
| if (!BT_returnstack) | ||
| BT_returnstack = std::make_unique<BugType>( | ||
| CheckNames[CK_StackAddrEscapeChecker], | ||
| "Return of address to stack-allocated memory"); | ||
|
|
||
| // Generate a report for this bug. | ||
| SmallString<128> buf; | ||
| llvm::raw_svector_ostream os(buf); | ||
|
|
||
| // Error message formatting | ||
| SourceRange range = genName(os, R, C.getASTContext()); | ||
| os << " returned to caller"; | ||
| EmitReturnedAsPartOfError(os, C.getSVal(RetE), R); | ||
|
|
||
| auto report = | ||
| std::make_unique<PathSensitiveBugReport>(*BT_returnstack, os.str(), N); | ||
| report->addRange(RetE->getSourceRange()); | ||
|
|
@@ -209,30 +226,6 @@ void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures( | |
| } | ||
| } | ||
|
|
||
| void StackAddrEscapeChecker::checkReturnedBlockCaptures( | ||
| const BlockDataRegion &B, CheckerContext &C) const { | ||
| for (const MemRegion *Region : getCapturedStackRegions(B, C)) { | ||
| if (isNotInCurrentFrame(Region, C)) | ||
| continue; | ||
| ExplodedNode *N = C.generateNonFatalErrorNode(); | ||
| if (!N) | ||
| continue; | ||
| if (!BT_capturedstackret) | ||
| BT_capturedstackret = std::make_unique<BugType>( | ||
| CheckNames[CK_StackAddrEscapeChecker], | ||
| "Address of stack-allocated memory is captured"); | ||
| SmallString<128> Buf; | ||
| llvm::raw_svector_ostream Out(Buf); | ||
| SourceRange Range = genName(Out, Region, C.getASTContext()); | ||
| Out << " is captured by a returned block"; | ||
| auto Report = std::make_unique<PathSensitiveBugReport>(*BT_capturedstackret, | ||
| Out.str(), N); | ||
| if (Range.isValid()) | ||
| Report->addRange(Range); | ||
| C.emitReport(std::move(Report)); | ||
| } | ||
| } | ||
|
|
||
| void StackAddrEscapeChecker::checkPreCall(const CallEvent &Call, | ||
| CheckerContext &C) const { | ||
| if (!ChecksEnabled[CK_StackAddrAsyncEscapeChecker]) | ||
|
|
@@ -247,45 +240,134 @@ void StackAddrEscapeChecker::checkPreCall(const CallEvent &Call, | |
| } | ||
| } | ||
|
|
||
| void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, | ||
| CheckerContext &C) const { | ||
| if (!ChecksEnabled[CK_StackAddrEscapeChecker]) | ||
| return; | ||
| /// A visitor made for use with a ScanReachableSymbols scanner, used | ||
| /// for finding stack regions within an SVal that live on the current | ||
| /// stack frame of the given checker context. This visitor excludes | ||
| /// NonParamVarRegion that data is bound to in a BlockDataRegion's | ||
| /// bindings, since these are likely uninteresting, e.g., in case a | ||
| /// temporary is constructed on the stack, but it captures values | ||
| /// that would leak. | ||
| class FindStackRegionsSymbolVisitor final : public SymbolVisitor { | ||
| CheckerContext &Ctxt; | ||
| const StackFrameContext *StackFrameContext; | ||
| SmallVector<const MemRegion *> &EscapingStackRegions; | ||
|
|
||
| const Expr *RetE = RS->getRetValue(); | ||
| if (!RetE) | ||
| return; | ||
| RetE = RetE->IgnoreParens(); | ||
| public: | ||
| explicit FindStackRegionsSymbolVisitor( | ||
| CheckerContext &Ctxt, | ||
| SmallVector<const MemRegion *> &StorageForStackRegions) | ||
| : Ctxt(Ctxt), StackFrameContext(Ctxt.getStackFrame()), | ||
| EscapingStackRegions(StorageForStackRegions) {} | ||
|
|
||
| SVal V = C.getSVal(RetE); | ||
| const MemRegion *R = V.getAsRegion(); | ||
| if (!R) | ||
| return; | ||
| bool VisitSymbol(SymbolRef sym) override { return true; } | ||
|
|
||
| if (const BlockDataRegion *B = dyn_cast<BlockDataRegion>(R)) | ||
| checkReturnedBlockCaptures(*B, C); | ||
| bool VisitMemRegion(const MemRegion *MR) override { | ||
| SaveIfEscapes(MR); | ||
|
|
||
| if (!isa<StackSpaceRegion>(R->getMemorySpace()) || isNotInCurrentFrame(R, C)) | ||
| return; | ||
| if (const BlockDataRegion *BDR = MR->getAs<BlockDataRegion>()) | ||
| return VisitBlockDataRegionCaptures(BDR); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private: | ||
| void SaveIfEscapes(const MemRegion *MR) { | ||
| const StackSpaceRegion *SSR = | ||
| MR->getMemorySpace()->getAs<StackSpaceRegion>(); | ||
| if (SSR && SSR->getStackFrame() == StackFrameContext) | ||
| EscapingStackRegions.push_back(MR); | ||
| } | ||
|
|
||
| bool VisitBlockDataRegionCaptures(const BlockDataRegion *BDR) { | ||
| for (auto Var : BDR->referenced_vars()) { | ||
| SVal Val = Ctxt.getState()->getSVal(Var.getCapturedRegion()); | ||
| const MemRegion *Region = Val.getAsRegion(); | ||
| if (Region) { | ||
| SaveIfEscapes(Region); | ||
| VisitMemRegion(Region); | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| }; | ||
|
|
||
| /// Given some memory regions that are flagged by FindStackRegionsSymbolVisitor, | ||
| /// this function filters out memory regions that are being returned that are | ||
| /// likely not true leaks: | ||
| /// 1. If returning a block data region that has stack memory space | ||
| /// 2. If returning a constructed object that has stack memory space | ||
| static SmallVector<const MemRegion *> | ||
| FilterReturnExpressionLeaks(const SmallVector<const MemRegion *> &MaybeEscaped, | ||
| CheckerContext &C, const Expr *RetE, SVal &RetVal) { | ||
|
|
||
| SmallVector<const MemRegion *> WillEscape; | ||
|
|
||
| const MemRegion *RetRegion = RetVal.getAsRegion(); | ||
|
|
||
| // Returning a record by value is fine. (In this case, the returned | ||
| // expression will be a copy-constructor, possibly wrapped in an | ||
| // ExprWithCleanups node.) | ||
| if (const ExprWithCleanups *Cleanup = dyn_cast<ExprWithCleanups>(RetE)) | ||
| RetE = Cleanup->getSubExpr(); | ||
| if (isa<CXXConstructExpr>(RetE) && RetE->getType()->isRecordType()) | ||
| return; | ||
| bool IsConstructExpr = | ||
| isa<CXXConstructExpr>(RetE) && RetE->getType()->isRecordType(); | ||
|
|
||
| // The CK_CopyAndAutoreleaseBlockObject cast causes the block to be copied | ||
| // so the stack address is not escaping here. | ||
| bool IsCopyAndAutoreleaseBlockObj = false; | ||
| if (const auto *ICE = dyn_cast<ImplicitCastExpr>(RetE)) { | ||
| if (isa<BlockDataRegion>(R) && | ||
| ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) { | ||
| return; | ||
| } | ||
| IsCopyAndAutoreleaseBlockObj = | ||
| isa_and_nonnull<BlockDataRegion>(RetRegion) && | ||
| ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject; | ||
| } | ||
|
|
||
| for (const MemRegion *MR : MaybeEscaped) { | ||
| if (RetRegion == MR && (IsCopyAndAutoreleaseBlockObj || IsConstructExpr)) | ||
| continue; | ||
|
|
||
| // If this is a construct expr of an unelided return value copy, then don't | ||
| // warn about returning a region that currently lives on the stack. | ||
| if (IsConstructExpr && RetVal.getAs<nonloc::LazyCompoundVal>() && | ||
|
||
| isa<CXXTempObjectRegion>(MR)) | ||
| continue; | ||
|
|
||
| WillEscape.push_back(MR); | ||
| } | ||
|
|
||
| EmitStackError(C, R, RetE); | ||
| return WillEscape; | ||
| } | ||
|
|
||
| /// For use in finding regions that live on the checker context's current | ||
| /// stack frame, deep in the SVal representing the return value. | ||
| static SmallVector<const MemRegion *> | ||
| FindEscapingStackRegions(CheckerContext &C, const Expr *RetE, SVal RetVal) { | ||
| SmallVector<const MemRegion *> FoundStackRegions; | ||
|
|
||
| FindStackRegionsSymbolVisitor Finder(C, FoundStackRegions); | ||
| ScanReachableSymbols Scanner(C.getState(), Finder); | ||
| Scanner.scan(RetVal); | ||
|
|
||
| return FilterReturnExpressionLeaks(FoundStackRegions, C, RetE, RetVal); | ||
| } | ||
|
|
||
| void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, | ||
| CheckerContext &C) const { | ||
| if (!ChecksEnabled[CK_StackAddrEscapeChecker]) | ||
| return; | ||
|
|
||
| const Expr *RetE = RS->getRetValue(); | ||
| if (!RetE) | ||
| return; | ||
| RetE = RetE->IgnoreParens(); | ||
|
|
||
| SVal V = C.getSVal(RetE); | ||
|
|
||
| SmallVector<const MemRegion *> EscapedStackRegions = | ||
| FindEscapingStackRegions(C, RetE, V); | ||
|
|
||
| for (const MemRegion *ER : EscapedStackRegions) | ||
| EmitReturnLeakError(C, ER, RetE); | ||
| } | ||
|
|
||
| static const MemSpaceRegion *getStackOrGlobalSpaceRegion(const MemRegion *R) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: we usually take
SmallVectorImplby reference so the function works withSmallVectorusing any size as the inline buffer.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 6fff293.