Skip to content

[analyzer] Reapply of recent StackAddrEscapeChecker changes after buildbot failures #126618

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5bf5270
wip
Flandini Jan 27, 2025
ff3478d
wip
Flandini Jan 29, 2025
9d58c46
Changes to checkPreStmt
Flandini Jan 30, 2025
e9e0ae8
start separating checkEndFunction and checkPreStmt
Flandini Jan 30, 2025
4edaa02
changes to checkEndFunction
Flandini Jan 30, 2025
78be69d
wip
Flandini Jan 30, 2025
21e5c9d
wip, fixed lambdas test failures
Flandini Feb 3, 2025
a0bcc4d
formatting
Flandini Feb 3, 2025
a94ccd6
some cleanup
Flandini Feb 3, 2025
b72b98c
emit returned by part added
Flandini Feb 3, 2025
b78b96c
add back IsConstructExpr bool in filtering
Flandini Feb 3, 2025
a078753
wip
Flandini Feb 3, 2025
891f5fd
cleanup
Flandini Feb 3, 2025
6a7cb96
fix objc stack-allocated-capturing block return
Flandini Feb 3, 2025
6d29efb
why does this work
Flandini Feb 4, 2025
296555e
double up these test expectations
Flandini Feb 4, 2025
932c14f
check everytime visitor adds to escaped region
Flandini Feb 4, 2025
9d8fc1a
fix comments
Flandini Feb 4, 2025
f909ae1
formatting
Flandini Feb 4, 2025
ef79492
remove old includes
Flandini Feb 4, 2025
cb702c9
restore whitespace at end of file
Flandini Feb 4, 2025
d5c123d
add basic int* and int return assign expr tests
Flandini Feb 4, 2025
19cdb1b
return comma-separated expression
Flandini Feb 4, 2025
cf0a5f9
Add expected warning to copy-elision.cpp no-elide tests
Flandini Feb 7, 2025
02b24e8
Add simple true negative test case from code review
Flandini Feb 7, 2025
e3b9ec9
Remove temp object check in return expr checking
Flandini Feb 7, 2025
5b67313
Add newline end of file
Flandini Feb 7, 2025
6c2425f
Add testcase: return of unknown symbol on stack
Flandini Feb 7, 2025
84ce3a1
Add testcase of false negative returning symbolic arg
Flandini Feb 7, 2025
d187007
Add testcase: bind rval ref to temporary
Flandini Feb 8, 2025
abac2ed
Add testcases: return argptr, conditional expressions, comparison exp…
Flandini Feb 9, 2025
e63d78a
Add testscases from SemaCXX/return-stack-addr.cpp
Flandini Feb 10, 2025
628fd52
Add lifetimebound attribute test case
Flandini Feb 10, 2025
f938dd5
Add TODO on testcase for musttail attribute
Flandini Feb 10, 2025
a778a19
Add false negative comments
Flandini Feb 10, 2025
8bf192d
Take references to SmallVectorImpl rather than SmallVector
Flandini Feb 10, 2025
f911a26
formatting
Flandini Feb 10, 2025
4dca614
Add newline at end of file
Flandini Feb 10, 2025
9ac7689
Add newline at end of file
Flandini Feb 10, 2025
f73a7f5
[analyzer] Fix StackFrameContext define in stack addr escape checker
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 *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<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() == 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<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