https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/213779
>From 52d7192d8ce997bac8f6e890e633b095986d1c64 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> Date: Tue, 4 Aug 2026 15:05:12 +0200 Subject: [PATCH 4/7] Update clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp Co-authored-by: isuckatcs <[email protected]> --- 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
