Skip to content

[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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0674909
wip
Flandini Jan 27, 2025
0a26f83
wip
Flandini Jan 29, 2025
3ebf222
Changes to checkPreStmt
Flandini Jan 30, 2025
17bf250
start separating checkEndFunction and checkPreStmt
Flandini Jan 30, 2025
89faf66
changes to checkEndFunction
Flandini Jan 30, 2025
f225a2d
wip
Flandini Jan 30, 2025
5816e91
wip, fixed lambdas test failures
Flandini Feb 3, 2025
27c6cf8
formatting
Flandini Feb 3, 2025
b73ad74
some cleanup
Flandini Feb 3, 2025
13eac11
emit returned by part added
Flandini Feb 3, 2025
129777e
add back IsConstructExpr bool in filtering
Flandini Feb 3, 2025
adb78d1
wip
Flandini Feb 3, 2025
2c62960
cleanup
Flandini Feb 3, 2025
e43fb34
fix objc stack-allocated-capturing block return
Flandini Feb 3, 2025
8c39d66
why does this work
Flandini Feb 4, 2025
76493d1
double up these test expectations
Flandini Feb 4, 2025
82400a7
check everytime visitor adds to escaped region
Flandini Feb 4, 2025
39df282
fix comments
Flandini Feb 4, 2025
65bee7a
formatting
Flandini Feb 4, 2025
47f3b40
remove old includes
Flandini Feb 4, 2025
6a2a01f
restore whitespace at end of file
Flandini Feb 4, 2025
30ea259
add basic int* and int return assign expr tests
Flandini Feb 4, 2025
567c8e6
return comma-separated expression
Flandini Feb 4, 2025
4d4d537
Add expected warning to copy-elision.cpp no-elide tests
Flandini Feb 7, 2025
a4ad3cb
Add simple true negative test case from code review
Flandini Feb 7, 2025
8603e15
Remove temp object check in return expr checking
Flandini Feb 7, 2025
7953dba
Add newline end of file
Flandini Feb 7, 2025
51f8e2e
Add testcase: return of unknown symbol on stack
Flandini Feb 7, 2025
029d122
Add testcase of false negative returning symbolic arg
Flandini Feb 7, 2025
62ef423
Add testcase: bind rval ref to temporary
Flandini Feb 8, 2025
e69ea53
Add testcases: return argptr, conditional expressions, comparison exp…
Flandini Feb 9, 2025
9d4379e
Add testscases from SemaCXX/return-stack-addr.cpp
Flandini Feb 10, 2025
1d6e363
Add lifetimebound attribute test case
Flandini Feb 10, 2025
b679462
Add TODO on testcase for musttail attribute
Flandini Feb 10, 2025
571f8ee
Add false negative comments
Flandini Feb 10, 2025
6fff293
Take references to SmallVectorImpl rather than SmallVector
Flandini Feb 10, 2025
9125ed8
formatting
Flandini Feb 10, 2025
bb1b70e
Add newline at end of file
Flandini Feb 10, 2025
16e3dc2
Add newline at end of file
Flandini Feb 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 129 additions & 53 deletions clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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])
Expand All @@ -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 *StackFrameContext;
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), 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 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<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;

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) {
Expand Down
25 changes: 18 additions & 7 deletions clang/test/Analysis/copy-elision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClassWithoutDestructor> &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<ClassWithoutDestructor> &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<ClassWithoutDestructor> &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}}
Expand Down Expand Up @@ -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<ClassWithDestructor> &v) {
return ClassWithDestructor(v);
return ClassWithDestructor(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}}
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
object of type 'ClassWithDestructor' 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
ClassWithDestructor make2(AddressVector<ClassWithDestructor> &v) {
return make1(v);
return make1(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}}
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
object of type 'ClassWithDestructor' 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
ClassWithDestructor make3(AddressVector<ClassWithDestructor> &v) {
return make2(v);
return make2(v); // no-elide-warning{{Address of stack memory associated with temporary object of type 'ClassWithDestructor' returned to caller}}
// no-elide-warning@-1 {{Address of stack memory associated with temporary \
object of type 'ClassWithDestructor' is still referred \
to by the caller variable 'v' upon returning to the caller}}
Expand Down
Loading