[analyzer] Reapply recent stack addr escape checker changes + buildbot fix by ashfordium · Pull Request #126620 · llvm/llvm-project (original) (raw)

@llvm/pr-subscribers-clang-static-analyzer-1

@llvm/pr-subscribers-clang

Author: Michael Flanders (Flandini)

Changes

Reapplying changes from #125638 after buildbot failures.

Buildbot failures fixed in 029e7e9, latest commit on this PR. It was a problem with a declared class member with same name as its type. Sorry!


Patch is 27.61 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/126620.diff

5 Files Affected:

diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index f4de3b500499c48..c9df15ceb3b4092 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -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,9 +147,22 @@ 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(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; @@ -157,11 +170,15 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, BT_returnstack = std::make_unique( 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(*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( - 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(*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,128 @@ 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 *PoppedStackFrame; + SmallVectorImpl<const MemRegion *> &EscapingStackRegions; - const Expr *RetE = RS->getRetValue(); - if (!RetE) - return; - RetE = RetE->IgnoreParens(); +public: + explicit FindStackRegionsSymbolVisitor( + CheckerContext &Ctxt, + SmallVectorImpl<const MemRegion *> &StorageForStackRegions) + : Ctxt(Ctxt), PoppedStackFrame(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(R)) - checkReturnedBlockCaptures(*B, C); + bool VisitMemRegion(const MemRegion *MR) override { + SaveIfEscapes(MR); - if (!isa(R->getMemorySpace()) || isNotInCurrentFrame(R, C)) - return; + if (const BlockDataRegion *BDR = MR->getAs()) + return VisitBlockDataRegionCaptures(BDR); + + return true; + } + +private: + void SaveIfEscapes(const MemRegion *MR) { + const StackSpaceRegion *SSR = + MR->getMemorySpace()->getAs(); + if (SSR && SSR->getStackFrame() == PoppedStackFrame) + 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 SmallVectorImpl<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(RetE)) RetE = Cleanup->getSubExpr(); - if (isa(RetE) && RetE->getType()->isRecordType()) - return; + bool IsConstructExpr = + isa(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(RetE)) { - if (isa(R) && - ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) { - return; - } + IsCopyAndAutoreleaseBlockObj = + isa_and_nonnull(RetRegion) && + ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject; + } + + for (const MemRegion *MR : MaybeEscaped) { + if (RetRegion == MR && (IsCopyAndAutoreleaseBlockObj || IsConstructExpr)) + 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) { diff --git a/clang/test/Analysis/copy-elision.cpp b/clang/test/Analysis/copy-elision.cpp index cd941854fc7f196..e69f52f351f1bc0 100644 --- a/clang/test/Analysis/copy-elision.cpp +++ b/clang/test/Analysis/copy-elision.cpp @@ -154,20 +154,26 @@ class ClassWithoutDestructor { void push() { v.push(this); } }; +// Two warnings on no-elide: arg v holds the address of the temporary, and we +// are returning an object which holds v which holds the address of the temporary ClassWithoutDestructor make1(AddressVector &v) { - return ClassWithoutDestructor(v); + return ClassWithoutDestructor(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}} // no-elide-warning@-1 {{Address of stack memory associated with temporary
object of type 'ClassWithoutDestructor' is still
referred to by the caller variable 'v' upon returning to the caller}} } +// Two warnings on no-elide: arg v holds the address of the temporary, and we +// are returning an object which holds v which holds the address of the temporary ClassWithoutDestructor make2(AddressVector &v) { - return make1(v); + return make1(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}} // no-elide-warning@-1 {{Address of stack memory associated with temporary
object of type 'ClassWithoutDestructor' is still
referred to by the caller variable 'v' upon returning to the caller}} } +// Two warnings on no-elide: arg v holds the address of the temporary, and we +// are returning an object which holds v which holds the address of the temporary ClassWithoutDestructor make3(AddressVector &v) { - return make2(v); + return make2(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithoutDestructor' returned to caller}} // no-elide-warning@-1 {{Address of stack memory associated with temporary
object of type 'ClassWithoutDestructor' is still
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}} #endif }

+// Two warnings on no-elide: arg v holds the address of the temporary, and we +// are returning an object which holds v which holds the address of the temporary ClassWithDestructor make1(AddressVector &v) {

+int *return_assign_expr_leak() {

@@ -152,6 +164,18 @@ namespace rdar13296133 { } } // namespace rdar13296133

+void* ret_cpp_static_cast(short x) {

+C&& return_bind_rvalue_reference_to_temporary() {

void lambda_to_context_direct_pointer_lifetime_extended() { @@ -340,6 +369,13 @@ void param_ptr_to_ptr_to_ptr_callee(void*** ppp) { **ppp = &local; // expected-warning{{local variable 'local' is still referred to by the caller variable 'pp'}} }

+void ***param_ptr_to_ptr_to_ptr_return(void ***ppp) {

void** returned_arr_of_ptr_callee() { int local = 42; int* p = &local; void** arr = new void*[2]; arr[1] = p;

void returned_arr_of_ptr_caller() { void** arr = returned_arr_of_ptr_callee(); @@ -466,16 +502,16 @@ void** returned_arr_of_ptr_top(int idx) { int* p = &local; void** arr = new void*[2]; arr[idx] = p;

void** returned_arr_of_ptr_callee(int idx) { int local = 42; int* p = &local; void** arr = new void*[2]; arr[idx] = p;

void returned_arr_of_ptr_caller(int idx) { void** arr = returned_arr_of_ptr_callee(idx); @@ -525,14 +561,25 @@ S returned_struct_with_ptr_top() { int local = 42; S s; s.p = &local;

S returned_struct_with_ptr_callee() { int local = 42; S s; s.p = &local;

void returned_struct_with_ptr_caller() { @@ -555,6 +602,30 @@ void static_struct_with_ptr() { } } // namespace leaking_via_struct_with_ptr

+namespace leaking_via_nested_structs_with_ptr { +struct Inner {