[clang] [analyzer] Discard non-live source frames from the current stack (PR #213779)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 5 04:46:07 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/213779
>From 52d7192d8ce997bac8f6e890e633b095986d1c64 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Tue, 4 Aug 2026 00:11:56 +0200
Subject: [PATCH 1/7] [analyzer] Discard non-live source frames from the
current stack
---
.../Checkers/LifetimeModeling.cpp | 34 +++++++++++++++++--
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 4b6d76a09575a..3765f4907c9e7 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -52,9 +52,17 @@ static bool isDanglingStackSource(const MemRegion *Source,
})) {
return false;
}
-
- if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
- return true;
+ // Only a source whose frame is still live on the current stack can
+ // dangle. If that frame is not on the stack then the source outlives
+ // the returned value. The source is still alive when the returned value
+ // is used, so it does not dangle.
+ if (llvm::any_of(C.stackframes(), [&](const StackFrame &Frame) {
+ if (&Frame != SF)
+ return false;
+ return true;
+ }))
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+ return true;
}
return false;
}
@@ -117,7 +125,27 @@ void LifetimeModeling::checkPostCall(const CallEvent &Call,
State = bindSource(State, RetVal, ArgValRegion);
}
}
+ /*
+ auto ViewObj = Call.getReturnValue().getAs<nonloc::LazyCompoundVal>();
+ llvm::errs() << ViewObj << "\n";
+ RetVal.dump();
+ if (!ViewObj)
+ return;
+ llvm::errs() << ViewObj;
+ const MemRegion *LCVRegion = ViewObj->getRegion();
+ if (!LCVRegion)
+ return;
+ llvm::errs() << LCVRegion << "\n";
+ for (const ParmVarDecl *PVD : FD->parameters()) {
+ if (PVD->hasAttr<LifetimeBoundAttr>()) {
+ unsigned Idx = PVD->getFunctionScopeIndex();
+ SVal Arg = Call.getArgSVal(Idx);
+ if (const MemRegion *ArgValRegion = Arg.getAsRegion())
+ State = bindSource(State, RetVal, ArgValRegion);
+ }
+ }
+ */
const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
if (IC && lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion())
>From 29cb487f72b578fa1f093a775e0a2712b9db611a Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Tue, 4 Aug 2026 13:38:31 +0200
Subject: [PATCH 2/7] Removed commented LCV test implementation.
---
.../Checkers/LifetimeModeling.cpp | 21 +------------------
1 file changed, 1 insertion(+), 20 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 3765f4907c9e7..33b36115a6cb0 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -92,6 +92,7 @@ static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
Set = F.add(Set, Source);
State = State->set<LifetimeBoundMap>(RetVal, Set);
+
return State;
}
@@ -125,27 +126,7 @@ void LifetimeModeling::checkPostCall(const CallEvent &Call,
State = bindSource(State, RetVal, ArgValRegion);
}
}
- /*
- auto ViewObj = Call.getReturnValue().getAs<nonloc::LazyCompoundVal>();
- llvm::errs() << ViewObj << "\n";
- RetVal.dump();
- if (!ViewObj)
- return;
- llvm::errs() << ViewObj;
- const MemRegion *LCVRegion = ViewObj->getRegion();
- if (!LCVRegion)
- return;
- llvm::errs() << LCVRegion << "\n";
- for (const ParmVarDecl *PVD : FD->parameters()) {
- if (PVD->hasAttr<LifetimeBoundAttr>()) {
- unsigned Idx = PVD->getFunctionScopeIndex();
- SVal Arg = Call.getArgSVal(Idx);
- if (const MemRegion *ArgValRegion = Arg.getAsRegion())
- State = bindSource(State, RetVal, ArgValRegion);
- }
- }
- */
const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
if (IC && lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion())
>From e82f2a099b072ee4e80c4a54ca1c68f6161cf0d1 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Tue, 4 Aug 2026 14:19:15 +0200
Subject: [PATCH 3/7] Add test case.
---
.../StaticAnalyzer/Checkers/LifetimeModeling.cpp | 6 ++++--
clang/test/Analysis/lifetime-bound.cpp | 14 ++++++++++++++
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 33b36115a6cb0..e9a4ed8559d51 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -56,13 +56,15 @@ static bool isDanglingStackSource(const MemRegion *Source,
// dangle. If that frame is not on the stack then the source outlives
// the returned value. The source is still alive when the returned value
// is used, so it does not dangle.
+ /*
if (llvm::any_of(C.stackframes(), [&](const StackFrame &Frame) {
if (&Frame != SF)
return false;
return true;
}))
- if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
- return true;
+ */
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+ return true;
}
return false;
}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index ef8ffeb87a8dd..8a1030add8ff7 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -378,3 +378,17 @@ CustomStringView dangling_sv() {
char s[] = "dangling";
return CustomStringView(s); // expected-warning {{address of stack memory associated with local variable 's' returned}}
}
+
+struct Chained {
+ Chained &self() [[clang::lifetimebound]] { return *this; }
+ Chained() {
+ self();
+ self(); // no-warning
+ }
+};
+
+void takes_by_value(Chained arg);
+
+void no_dangling_by_value_argument() {
+ takes_by_value(Chained()); // no-warning
+}
>From 3eff8e8af97477b9400549b13b2e7a4e6ada2fe3 Mon Sep 17 00:00:00 2001
From: Benedek Kaibas <82393336+benedekaibas at users.noreply.github.com>
Date: Tue, 4 Aug 2026 15:05:12 +0200
Subject: [PATCH 4/7] Update
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
Co-authored-by: isuckatcs <65320245+isuckatcs at users.noreply.github.com>
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index e9a4ed8559d51..c008443a6ecae 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -58,9 +58,7 @@ static bool isDanglingStackSource(const MemRegion *Source,
// is used, so it does not dangle.
/*
if (llvm::any_of(C.stackframes(), [&](const StackFrame &Frame) {
- if (&Frame != SF)
- return false;
- return true;
+ return &Frame == SF;
}))
*/
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
>From a65c222d404f3e0247b590fad87dd01fee8a6cc5 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Tue, 4 Aug 2026 15:16:05 +0200
Subject: [PATCH 5/7] Applied nits.
---
.../StaticAnalyzer/Checkers/LifetimeModeling.cpp | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index c008443a6ecae..2f1eb9f965310 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -56,13 +56,11 @@ static bool isDanglingStackSource(const MemRegion *Source,
// dangle. If that frame is not on the stack then the source outlives
// the returned value. The source is still alive when the returned value
// is used, so it does not dangle.
- /*
- if (llvm::any_of(C.stackframes(), [&](const StackFrame &Frame) {
- return &Frame == SF;
- }))
- */
- if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
- return true;
+ if (llvm::any_of(C.stackframes(),
+ [&](const StackFrame &Frame) { return &Frame == SF; }))
+
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+ return true;
}
return false;
}
@@ -92,7 +90,6 @@ static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
Set = F.add(Set, Source);
State = State->set<LifetimeBoundMap>(RetVal, Set);
-
return State;
}
>From 30cce49530f3eb7b5731afcd19ddd9529aebed07 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Tue, 4 Aug 2026 20:11:25 +0200
Subject: [PATCH 6/7] Add explanation.
---
clang/test/Analysis/lifetime-bound.cpp | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 8a1030add8ff7..30e779d567545 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -379,16 +379,21 @@ CustomStringView dangling_sv() {
return CustomStringView(s); // expected-warning {{address of stack memory associated with local variable 's' returned}}
}
-struct Chained {
- Chained &self() [[clang::lifetimebound]] { return *this; }
- Chained() {
+// `self()` is annotated [[clang::lifetimebound]], so its return is bound to
+// *this. The BoundToSelf instance is built as a by-value argument temporary,
+// so its frame is not live on the stack when self() returns.
+struct BoundToSelf {
+ BoundToSelf &self() [[clang::lifetimebound]] { return *this; } // no-warning
+ BoundToSelf() {
+ self();
self();
- self(); // no-warning
}
};
-void takes_by_value(Chained arg);
+void takes_by_value(BoundToSelf arg);
void no_dangling_by_value_argument() {
- takes_by_value(Chained()); // no-warning
+ // The BoundToSelf temporary's frame is not live on the stack when `self()` returns.
+ // The returned reference does not dangle.
+ takes_by_value(BoundToSelf());
}
>From ae0ea6cd125c440162cd3089e63fbc640e54aafe Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Wed, 5 Aug 2026 13:45:43 +0200
Subject: [PATCH 7/7] Use is_contained with make_pointer_range instead of
any_of.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 2f1eb9f965310..3dc0df169e4c7 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -56,11 +56,10 @@ static bool isDanglingStackSource(const MemRegion *Source,
// dangle. If that frame is not on the stack then the source outlives
// the returned value. The source is still alive when the returned value
// is used, so it does not dangle.
- if (llvm::any_of(C.stackframes(),
- [&](const StackFrame &Frame) { return &Frame == SF; }))
+ return is_contained(make_pointer_range(C.stackframes()), SF);
- if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
- return true;
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+ return true;
}
return false;
}
More information about the cfe-commits
mailing list