[clang] [analyzer] Fix false positive in UseAFterLifetimeEnd checker for bound sources called in destructor (PR #207472)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Sat Jul 4 03:43:30 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/207472
>From ed9afadd50a70193aa48fa4d6082409b445a91d6 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 24 Jun 2026 12:05:46 +0200
Subject: [PATCH 01/21] [analyzer] Implement LifetimeAnnotations checker.
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 11 +
.../StaticAnalyzer/Checkers/CMakeLists.txt | 2 +
.../Checkers/LifetimeAnnotations.cpp | 305 ++++++++++++++++++
clang/test/Analysis/debug-lifetime-bound.cpp | 10 +
clang/test/Analysis/lifetime-bound.cpp | 171 ++++++++++
5 files changed, 499 insertions(+)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
create mode 100644 clang/test/Analysis/debug-lifetime-bound.cpp
create mode 100644 clang/test/Analysis/lifetime-bound.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index d02c3195069f3..5ba220ab6d60e 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -788,6 +788,11 @@ def SmartPtrChecker: Checker<"SmartPtr">,
Dependencies<[SmartPtrModeling]>,
Documentation<HasDocumentation>;
+def LifetimeAnnotations : Checker<"LifetimeAnnotations">,
+ HelpText<"Check for lifetime violations by incorporating lifetime "
+ "annotations into the analysis">,
+ Documentation<NotDocumented>;
+
} // end: "alpha.cplusplus"
//===----------------------------------------------------------------------===//
@@ -1576,6 +1581,12 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">,
HelpText<"Defines an empty checker callback for all possible handlers.">,
Documentation<NotDocumented>;
+def DebugLifetimeAnnotations : Checker<"DebugLifetimeAnnotations">,
+ HelpText<"Prints the bindings recorded by the LifetimeAnnotations checker. "
+ "Use with clang_analyzer_lifetime_bound().">,
+ WeakDependencies<[LifetimeAnnotations]>,
+ Documentation<NotDocumented>;
+
} // end "debug"
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 8a0621077b977..8363f345f4cc8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -55,6 +55,7 @@ add_clang_library(clangStaticAnalyzerCheckers
IteratorModeling.cpp
IteratorRangeChecker.cpp
IvarInvalidationChecker.cpp
+ LifetimeAnnotations.cpp
LLVMConventionsChecker.cpp
LocalizationChecker.cpp
MacOSKeychainAPIChecker.cpp
@@ -146,6 +147,7 @@ add_clang_library(clangStaticAnalyzerCheckers
clangAST
clangASTMatchers
clangAnalysis
+ clangAnalysisLifetimeSafety
clangBasic
clangLex
clangStaticAnalyzerCore
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
new file mode 100644
index 0000000000000..e25d076dd0bd5
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -0,0 +1,305 @@
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/AST/Attrs.inc"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace ento;
+
+REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
+
+REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
+
+namespace {
+class LifetimeAnnotations
+ : public Checker<check::PostCall, check::EndFunction, check::Location,
+ check::DeadSymbols> {
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+ const char *Sep) const override;
+ void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
+ CheckerContext &C) const;
+ void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N,
+ CheckerContext &C) const;
+
+ void checkReturnedBorrower(SVal Val, ProgramStateRef State,
+ CheckerContext &C) const;
+ void reportDanglingBorrower(const LifetimeSourceSet *Sources,
+ CheckerContext &C) const;
+ void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
+ void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+ CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+
+ const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
+};
+
+} // namespace
+
+static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
+ const MemRegion *Source) {
+ LifetimeSourceSet::Factory &F =
+ State->getStateManager().get_context<LifetimeSourceSet>();
+
+ const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
+ LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
+ Set = F.add(Set, Source);
+ State = State->set<LifetimeBoundMap>(RetVal, Set);
+ return State;
+}
+
+void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
+ if (!FC)
+ return;
+
+ const FunctionDecl *FD = FC->getDecl();
+ if (!FD)
+ return;
+
+ SVal RetVal = Call.getReturnValue();
+
+ 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 = bindValues(State, RetVal, ArgValRegion);
+ }
+ }
+
+ if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+ if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
+ if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
+ State = bindValues(State, RetVal, AttrRegion);
+ }
+ }
+ }
+ C.addTransition(State);
+}
+
+static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
+ CheckerContext &C) {
+ // FIXME: The checker currently handles stack-region sources. Other
+ // region kinds require separate methodology. For example, heap
+ // regions do not go out of scope at the end of a stack frame, so
+ // in order to detect those type of dangling sources the function
+ // needs to be expanded to an event-driven approach as well.
+ if (const auto *StackSpace =
+ Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+ const StackFrame *SF = StackSpace->getStackFrame();
+ const StackFrame *CurrentSF = C.getStackFrame();
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+ return true;
+ }
+ return false;
+}
+
+void LifetimeAnnotations::checkReturnedBorrower(SVal Val, ProgramStateRef State,
+ CheckerContext &C) const {
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ for (const MemRegion *Region : *SourceSet) {
+ if (hasDanglingSource(Region, State, C))
+ reportDanglingSource(Region, N, C);
+ }
+ }
+ }
+}
+
+void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
+ CheckerContext &C) const {
+ if (!RS)
+ return;
+
+ ProgramStateRef State = C.getState();
+ auto LBMap = State->get<LifetimeBoundMap>();
+
+ if (LBMap.isEmpty())
+ return;
+
+ const Expr *RetExpr = RS->getRetValue();
+ if (!RetExpr)
+ return;
+
+ RetExpr = RetExpr->IgnoreParens();
+ SVal RetVal = C.getSVal(RetExpr);
+ checkReturnedBorrower(RetVal, State, C);
+}
+
+void LifetimeAnnotations::reportDanglingBorrower(
+ const LifetimeSourceSet *Sources, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
+ for (const MemRegion *Source : *Sources) {
+ if (State->contains<DeallocatedSourceSet>(Source)) {
+ reportUseAfterScope(Source, N, C);
+ }
+ }
+}
+
+void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ auto LBMap = State->get<LifetimeBoundMap>();
+
+ if (LBMap.isEmpty())
+ return;
+
+ // FIXME: If a borrower has multiple bound sources the callback
+ // warns if any of the sources have died. PathDiagnosticVisitor
+ // should be used to trace and identify which annotated parameter
+ // recorded the binding. Attaching this information as path notes
+ // would make the diagnostics more useful to the user.
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(Loc))
+ reportDanglingBorrower(SourceSet, C);
+}
+
+void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
+ ExplodedNode *N,
+ CheckerContext &C) const {
+ auto BR = std::make_unique<PathSensitiveBugReport>(
+ BugMsg,
+ (llvm::Twine("Returning value bound to '") + Region->getString() +
+ "' that will go out of scope")
+ .str(),
+ N);
+ C.emitReport(std::move(BR));
+}
+
+void LifetimeAnnotations::reportUseAfterScope(const MemRegion *Region,
+ ExplodedNode *N,
+ CheckerContext &C) const {
+ auto BR = std::make_unique<PathSensitiveBugReport>(
+ BugMsg,
+ (llvm::Twine("Use of '") + Region->getString() +
+ "' after its lifetime ended.")
+ .str(),
+ N);
+ C.emitReport(std::move(BR));
+}
+
+void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
+
+ DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
+
+ for (SVal Val : llvm::make_first_range(LBMap)) {
+ if (const MemRegion *ValRegion = Val.getAsRegion()) {
+ if (!SymReaper.isLiveRegion(ValRegion))
+ State = State->remove<LifetimeBoundMap>(Val);
+ } else if (SymbolRef ValRef =
+ Val.getAsSymbol(/*IncludeBaseRegions=*/true)) {
+ if (!SymReaper.isLive(ValRef))
+ State = State->remove<LifetimeBoundMap>(Val);
+ }
+ }
+
+ for (const MemRegion *Region : Sources) {
+ if (!SymReaper.isLiveRegion(Region))
+ State = State->remove<DeallocatedSourceSet>(Region);
+ }
+
+ C.addTransition(State);
+}
+
+void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+ auto LBMap = State->get<LifetimeBoundMap>();
+
+ if (LBMap.isEmpty())
+ return;
+
+ Out << Sep << "LifetimeBound bindings:" << NL;
+ for (auto &&[OriginSym, SourceSet] : LBMap) {
+ for (const auto *Region : SourceSet)
+ Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
+ }
+}
+
+namespace {
+class DebugLifetimeAnnotations : public Checker<eval::Call> {
+public:
+ bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+ void analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const;
+
+ const BugType BugMsg{this, "DebugLifetimeAnnotations", "DebugLifetimeBound"};
+ using FnCheck = void (DebugLifetimeAnnotations::*)(const CallEvent &Call,
+ CheckerContext &C) const;
+
+ const CallDescriptionMap<FnCheck> Callbacks = {
+ {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
+ &DebugLifetimeAnnotations::analyzerLifetimeBound},
+ };
+};
+
+} // namespace
+
+bool DebugLifetimeAnnotations::evalCall(const CallEvent &Call,
+ CheckerContext &C) const {
+
+ const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
+ if (!CE)
+ return false;
+
+ const FnCheck *Handler = Callbacks.lookup(Call);
+ if (!Handler)
+ return false;
+
+ (this->*(*Handler))(Call, C);
+ return true;
+}
+
+void DebugLifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
+ CheckerContext &C) const {
+
+ ProgramStateRef State = C.getState();
+ unsigned int ArgCount = Call.getNumArgs();
+ if (ArgCount != 1)
+ return;
+
+ SVal ArgSVal = Call.getArgSVal(0);
+
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(ArgSVal)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ for (const auto *Region : *SourceSet) {
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+ OS << " Origin " << ArgSVal << " bound to " << Region;
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+ C.emitReport(std::move(BR));
+ }
+ }
+ }
+}
+
+void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
+ mgr.registerChecker<LifetimeAnnotations>();
+}
+
+bool ento::shouldRegisterLifetimeAnnotations(const CheckerManager &mgr) {
+ return true;
+}
+
+void ento::registerDebugLifetimeAnnotations(CheckerManager &mgr) {
+ mgr.registerChecker<DebugLifetimeAnnotations>();
+}
+
+bool ento::shouldRegisterDebugLifetimeAnnotations(const CheckerManager &mgr) {
+ return true;
+}
diff --git a/clang/test/Analysis/debug-lifetime-bound.cpp b/clang/test/Analysis/debug-lifetime-bound.cpp
new file mode 100644
index 0000000000000..e62c51ae6bc53
--- /dev/null
+++ b/clang/test/Analysis/debug-lifetime-bound.cpp
@@ -0,0 +1,10 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations -verify %s
+
+// expected-no-diagnostics
+
+void clang_analyzer_lifetime_bound(int);
+
+void test() {
+ int x = 5;
+ clang_analyzer_lifetime_bound(x); // no-warning: verifies debug checker does not crash standalone
+}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
new file mode 100644
index 0000000000000..ca97419b63e53
--- /dev/null
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -0,0 +1,171 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \
+// RUN: -analyzer-config cfg-lifetime=true -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \
+// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s
+
+struct A {};
+
+void clang_analyzer_lifetime_bound(int*);
+void clang_analyzer_lifetime_bound(int&);
+void clang_analyzer_lifetime_bound(A*);
+void clang_analyzer_lifetime_bound(A&);
+
+// These are the cases when the result of function calls are MemRegions.
+
+// Ref type parameter annotated case
+struct X {
+ int& choose(int& a [[clang::lifetimebound]]) { return a; }
+};
+
+void caller() {
+ int v = 0;
+ X obj;
+ int& r = obj.choose(v);
+ clang_analyzer_lifetime_bound(r); // expected-warning {{Origin &v bound to v}}
+}
+
+// Obj ref type function return annotated case
+struct Y {
+ A a;
+ A& getA() [[clang::lifetimebound]] { return a; }
+};
+
+void caller_two() {
+ // Return statement is annotated case.
+ Y y;
+ A& f = y.getA();
+ clang_analyzer_lifetime_bound(f); // expected-warning {{Origin &y.a bound to y}}
+}
+
+// Obj ptr type function return annotated case
+struct Z {
+ A a;
+ A* getA() [[clang::lifetimebound]] { return &a; }
+};
+
+void caller_three() {
+ Z z;
+ A* func = z.getA();
+ clang_analyzer_lifetime_bound(func); // expected-warning {{Origin &z.a bound to z}}
+}
+
+// Free function with annotated param and ref return
+int& foo(int& num [[clang::lifetimebound]]) { return num; }
+
+void caller_four() {
+ int num = 5;
+ int& s = foo(num);
+ clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &num bound to num}}
+}
+
+// Free function with annotated param and ptr return
+int* boo(int* num [[clang::lifetimebound]]) { return num; }
+
+void caller_five() {
+ int n = 55;
+ int* n_ptr = &n;
+ int* s = boo(n_ptr);
+
+ clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &n bound to n}}
+}
+
+// Free function with both annotated and non-annotated parameters.
+int& fn(int& f, int& s [[clang::lifetimebound]]) { return s; }
+
+void caller_six() {
+ int even = 50;
+ int odd = 55;
+ int& s = fn(even, odd);
+
+ clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &odd bound to odd}}
+}
+
+
+
+// These are the cases when the result of function calls are SymbolRefs.
+
+// Function returns ptr and has an annotated parameter
+int* foo(int* n [[clang::lifetimebound]]);
+
+void caller_seven() {
+ int y = 15;
+ int* y_ptr = &y;
+ auto* bind = foo(y_ptr);
+
+ clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}}
+}
+
+// Function returns a reference and has an annotated parameter
+int& func(int& some_number [[clang::lifetimebound]]);
+
+void caller_eight() {
+ int f = 15;
+ auto& bind = func(f);
+
+ clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}}
+}
+
+// Function returns a reference and has two annotated parameters.
+int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]);
+
+void caller_nine() {
+ int first_num = 1;
+ int second_num = 2;
+ int& numbers = f(first_num, second_num);
+
+ clang_analyzer_lifetime_bound(numbers);
+ // expected-warning-re at -1 {{Origin &SymRegion{{.*}} bound to first_num}}
+ // expected-warning-re at -2 {{Origin &SymRegion{{.*}} bound to second_num}}
+}
+
+struct View {
+ int* p;
+};
+View makeView(int& x [[clang::lifetimebound]]);
+
+void clang_analyzer_lifetime_bound(View);
+
+void caller_view() {
+ int v = 42;
+ View w = makeView(v);
+ // FIXME: Currently none of the maps cover LazyCompoundVal
+ clang_analyzer_lifetime_bound(w); // no-warning
+}
+
+
+
+// These are the test cases for testing the correctness of the emitted warning from the LifetimeAnnotations checker.
+
+// Return value bound to annotated param cases
+int *test_func(int *p [[clang::lifetimebound]]);
+
+
+int *direct_return() {
+ int i = 5;
+ return test_func(&i);
+ // expected-warning at -1 {{Returning value bound to 'i' that will go out of scope}}
+ // expected-warning at -2 {{address of stack memory associated with local variable 'i' returned}}
+}
+
+int *variable_return() {
+ int y = 5;
+ int *p = test_func(&y);
+ return p; // expected-warning {{Returning value bound to 'y' that will go out of scope}}
+}
+
+int *borrow_from_caller(int *b [[clang::lifetimebound]]) {
+ return test_func(b); // no-warning
+}
+
+void no_return() {
+ int i = 5;
+ int *p = test_func(&i);
+ (void)p; // no-warning
+}
+
+int* g() {
+ int i = 5;
+ int* p = test_func(&i);
+ (void)p;
+ return nullptr; // no-warning
+}
>From ea22d128673996408833b7a30ecd97513df19591 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 24 Jun 2026 23:36:37 +0200
Subject: [PATCH 02/21] Changed checker naming and resolved comments.
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 10 +-
.../StaticAnalyzer/Checkers/CMakeLists.txt | 2 +-
.../Checkers/UseAfterLifetimeEnd.cpp | 317 ++++++++++++++++++
clang/test/Analysis/debug-lifetime-bound.cpp | 7 +-
clang/test/Analysis/lifetime-bound.cpp | 57 ++--
5 files changed, 355 insertions(+), 38 deletions(-)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 5ba220ab6d60e..2c59fddc89ca1 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -788,9 +788,9 @@ def SmartPtrChecker: Checker<"SmartPtr">,
Dependencies<[SmartPtrModeling]>,
Documentation<HasDocumentation>;
-def LifetimeAnnotations : Checker<"LifetimeAnnotations">,
- HelpText<"Check for lifetime violations by incorporating lifetime "
- "annotations into the analysis">,
+def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">,
+ HelpText<"Check for uses of references or pointers that "
+ "outlive their bound object">,
Documentation<NotDocumented>;
} // end: "alpha.cplusplus"
@@ -1581,10 +1581,10 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">,
HelpText<"Defines an empty checker callback for all possible handlers.">,
Documentation<NotDocumented>;
-def DebugLifetimeAnnotations : Checker<"DebugLifetimeAnnotations">,
+def DebugUseAfterLifetimeEnd : Checker<"DebugUseAfterLifetimeEnd">,
HelpText<"Prints the bindings recorded by the LifetimeAnnotations checker. "
"Use with clang_analyzer_lifetime_bound().">,
- WeakDependencies<[LifetimeAnnotations]>,
+ WeakDependencies<[UseAfterLifetimeEnd]>,
Documentation<NotDocumented>;
} // end "debug"
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 8363f345f4cc8..46c0c36fda736 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -55,7 +55,6 @@ add_clang_library(clangStaticAnalyzerCheckers
IteratorModeling.cpp
IteratorRangeChecker.cpp
IvarInvalidationChecker.cpp
- LifetimeAnnotations.cpp
LLVMConventionsChecker.cpp
LocalizationChecker.cpp
MacOSKeychainAPIChecker.cpp
@@ -127,6 +126,7 @@ add_clang_library(clangStaticAnalyzerCheckers
UninitializedObject/UninitializedPointee.cpp
UnixAPIChecker.cpp
UnreachableCodeChecker.cpp
+ UseAfterLifetimeEnd.cpp
VforkChecker.cpp
VLASizeChecker.cpp
VAListChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
new file mode 100644
index 0000000000000..81a7aabd6f7a4
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -0,0 +1,317 @@
+#include "clang/AST/Attr.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace ento;
+
+REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
+
+REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
+
+namespace {
+class UseAfterLifetimeEnd
+ : public Checker<check::PostCall, check::EndFunction, check::Location,
+ check::DeadSymbols> {
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+ const char *Sep) const override;
+ void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
+ CheckerContext &C) const;
+ void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N,
+ CheckerContext &C) const;
+
+ void checkReturnedBorrower(SVal Val, ProgramStateRef State,
+ CheckerContext &C) const;
+ void reportDanglingBorrower(const LifetimeSourceSet *Sources,
+ CheckerContext &C) const;
+ void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
+ void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+ CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+
+ const BugType BugMsg{this, "UseAfterLifetimeEnd", "LifetimeBound"};
+};
+
+} // namespace
+
+static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
+ const MemRegion *Source) {
+ LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
+
+ const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
+ LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
+ Set = F.add(Set, Source);
+ State = State->set<LifetimeBoundMap>(RetVal, Set);
+ return State;
+}
+
+void UseAfterLifetimeEnd::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
+ if (!FC)
+ return;
+
+ const FunctionDecl *FD = FC->getDecl();
+ if (!FD)
+ return;
+
+ SVal RetVal = Call.getReturnValue();
+
+ 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 = bindValues(State, RetVal, ArgValRegion);
+ }
+ }
+
+ if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+ if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
+ if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
+ State = bindValues(State, RetVal, AttrRegion);
+ }
+ }
+ }
+ C.addTransition(State);
+}
+
+static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
+ CheckerContext &C) {
+ // FIXME: The checker currently handles stack-region sources. Other
+ // region kinds require separate methodology. For example, heap
+ // regions do not go out of scope at the end of a stack frame, so
+ // in order to detect those type of dangling sources the function
+ // needs to be expanded to an event-driven approach as well.
+ if (const auto *StackSpace =
+ Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+ const StackFrame *SF = StackSpace->getStackFrame();
+ const StackFrame *CurrentSF = C.getStackFrame();
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+ return true;
+ }
+ return false;
+}
+
+void UseAfterLifetimeEnd::checkReturnedBorrower(SVal Val, ProgramStateRef State,
+ CheckerContext &C) const {
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ for (const MemRegion *Region : *SourceSet) {
+ if (hasDanglingSource(Region, State, C))
+ reportDanglingSource(Region, N, C);
+ }
+ }
+ }
+}
+
+void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
+ CheckerContext &C) const {
+ if (!RS)
+ return;
+
+ ProgramStateRef State = C.getState();
+ auto LBMap = State->get<LifetimeBoundMap>();
+
+ if (LBMap.isEmpty())
+ return;
+
+ const Expr *RetExpr = RS->getRetValue();
+ if (!RetExpr)
+ return;
+
+ RetExpr = RetExpr->IgnoreParens();
+ SVal RetVal = C.getSVal(RetExpr);
+ checkReturnedBorrower(RetVal, State, C);
+}
+
+void UseAfterLifetimeEnd::reportDanglingBorrower(
+ const LifetimeSourceSet *Sources, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
+ for (const MemRegion *Source : *Sources) {
+ if (State->contains<DeallocatedSourceSet>(Source)) {
+ reportUseAfterScope(Source, N, C);
+ }
+ }
+}
+
+void UseAfterLifetimeEnd::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ auto LBMap = State->get<LifetimeBoundMap>();
+
+ if (LBMap.isEmpty())
+ return;
+
+ // FIXME: If a borrower has multiple bound sources the callback
+ // warns if any of the sources have died. PathDiagnosticVisitor
+ // should be used to trace and identify which annotated parameter
+ // recorded the binding. Attaching this information as path notes
+ // would make the diagnostics more useful to the user.
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(Loc))
+ reportDanglingBorrower(SourceSet, C);
+}
+
+void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region,
+ ExplodedNode *N,
+ CheckerContext &C) const {
+ auto BR = std::make_unique<PathSensitiveBugReport>(
+ BugMsg,
+ (llvm::Twine("Returning value bound to '") + Region->getString() +
+ "' that will go out of scope"),
+ N);
+ C.emitReport(std::move(BR));
+}
+
+void UseAfterLifetimeEnd::reportUseAfterScope(const MemRegion *Region,
+ ExplodedNode *N,
+ CheckerContext &C) const {
+ auto BR = std::make_unique<PathSensitiveBugReport>(
+ BugMsg,
+ (llvm::Twine("Use of '") + Region->getString() +
+ "' after its lifetime ended."),
+ N);
+ C.emitReport(std::move(BR));
+}
+
+void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
+
+ DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
+
+ for (SVal Val : llvm::make_first_range(LBMap)) {
+ if (const MemRegion *ValRegion = Val.getAsRegion()) {
+ if (!SymReaper.isLiveRegion(ValRegion))
+ State = State->remove<LifetimeBoundMap>(Val);
+ } else if (SymbolRef ValRef =
+ Val.getAsSymbol(/*IncludeBaseRegions=*/true)) {
+ if (!SymReaper.isLive(ValRef))
+ State = State->remove<LifetimeBoundMap>(Val);
+ }
+ }
+
+ for (const MemRegion *Region : Sources) {
+ if (!SymReaper.isLiveRegion(Region))
+ State = State->remove<DeallocatedSourceSet>(Region);
+ }
+
+ C.addTransition(State);
+}
+
+void UseAfterLifetimeEnd::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+ auto LBMap = State->get<LifetimeBoundMap>();
+
+ if (LBMap.isEmpty())
+ return;
+
+ Out << Sep << "LifetimeBound bindings:" << NL;
+ for (auto &&[OriginSym, SourceSet] : LBMap) {
+ for (const auto *Region : SourceSet)
+ Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
+ }
+}
+
+namespace {
+class DebugUseAfterLifetimeEnd : public Checker<eval::Call> {
+public:
+ bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+ void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
+ CheckerContext &C) const;
+
+ const BugType BugMsg{this, "DebugUseAfterLifetimeEnd",
+ "DebugUseAfterLifetimeEnd"};
+ using FnCheck = void (DebugUseAfterLifetimeEnd::*)(const CallEvent &Call,
+ CheckerContext &C) const;
+
+ const CallDescriptionMap<FnCheck> Callbacks = {
+ {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
+ &DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf},
+ };
+};
+
+} // namespace
+
+bool DebugUseAfterLifetimeEnd::evalCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
+ if (!CE)
+ return false;
+
+ const FnCheck *Handler = Callbacks.lookup(Call);
+ if (!Handler)
+ return false;
+
+ (this->*(*Handler))(Call, C);
+ return true;
+}
+
+void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf(
+ const CallEvent &Call, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ if (Call.getNumArgs() != 1) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ auto BR = std::make_unique<PathSensitiveBugReport>(
+ BugMsg,
+ "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
+ N);
+ C.emitReport(std::move(BR));
+ }
+ return;
+ }
+
+ SVal ArgSVal = Call.getArgSVal(0);
+ const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal);
+
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+ OS << " Origin " << ArgSVal << " bound to ";
+
+ if (!SourceSet)
+ return;
+
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ bool First = true;
+ for (const MemRegion *Region : *SourceSet) {
+ if (!First)
+ OS << ", ";
+ OS << Region;
+ First = false;
+ }
+ C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N));
+ }
+}
+
+void ento::registerUseAfterLifetimeEnd(CheckerManager &Mgr) {
+ Mgr.registerChecker<UseAfterLifetimeEnd>();
+}
+
+bool ento::shouldRegisterUseAfterLifetimeEnd(const CheckerManager &Mgr) {
+ return true;
+}
+
+void ento::registerDebugUseAfterLifetimeEnd(CheckerManager &Mgr) {
+ Mgr.registerChecker<DebugUseAfterLifetimeEnd>();
+}
+
+bool ento::shouldRegisterDebugUseAfterLifetimeEnd(const CheckerManager &Mgr) {
+ return true;
+}
diff --git a/clang/test/Analysis/debug-lifetime-bound.cpp b/clang/test/Analysis/debug-lifetime-bound.cpp
index e62c51ae6bc53..8ef704195dcc6 100644
--- a/clang/test/Analysis/debug-lifetime-bound.cpp
+++ b/clang/test/Analysis/debug-lifetime-bound.cpp
@@ -1,10 +1,11 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd -verify %s
// expected-no-diagnostics
-void clang_analyzer_lifetime_bound(int);
+void clang_analyzer_dumpLifetimeOriginsOf(int);
void test() {
int x = 5;
- clang_analyzer_lifetime_bound(x); // no-warning: verifies debug checker does not crash standalone
+ clang_analyzer_dumpLifetimeOriginsOf(x); // no-warning
}
+
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index ca97419b63e53..1f870a94293cf 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -1,18 +1,18 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd \
// RUN: -analyzer-config cfg-lifetime=true -verify %s
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.LifetimeAnnotations,debug.DebugLifetimeAnnotations \
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd \
// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s
struct A {};
-void clang_analyzer_lifetime_bound(int*);
-void clang_analyzer_lifetime_bound(int&);
-void clang_analyzer_lifetime_bound(A*);
-void clang_analyzer_lifetime_bound(A&);
+void clang_analyzer_dumpLifetimeOriginsOf(int*);
+void clang_analyzer_dumpLifetimeOriginsOf(int&);
+void clang_analyzer_dumpLifetimeOriginsOf(A*);
+void clang_analyzer_dumpLifetimeOriginsOf(A&);
// These are the cases when the result of function calls are MemRegions.
-// Ref type parameter annotated case
+// Ref type parameter annotated case.
struct X {
int& choose(int& a [[clang::lifetimebound]]) { return a; }
};
@@ -21,10 +21,10 @@ void caller() {
int v = 0;
X obj;
int& r = obj.choose(v);
- clang_analyzer_lifetime_bound(r); // expected-warning {{Origin &v bound to v}}
+ clang_analyzer_dumpLifetimeOriginsOf(r); // expected-warning {{Origin &v bound to v}}
}
-// Obj ref type function return annotated case
+// Obj ref type function return annotated case.
struct Y {
A a;
A& getA() [[clang::lifetimebound]] { return a; }
@@ -34,10 +34,10 @@ void caller_two() {
// Return statement is annotated case.
Y y;
A& f = y.getA();
- clang_analyzer_lifetime_bound(f); // expected-warning {{Origin &y.a bound to y}}
+ clang_analyzer_dumpLifetimeOriginsOf(f); // expected-warning {{Origin &y.a bound to y}}
}
-// Obj ptr type function return annotated case
+// Obj ptr type function return annotated case.
struct Z {
A a;
A* getA() [[clang::lifetimebound]] { return &a; }
@@ -46,19 +46,19 @@ struct Z {
void caller_three() {
Z z;
A* func = z.getA();
- clang_analyzer_lifetime_bound(func); // expected-warning {{Origin &z.a bound to z}}
+ clang_analyzer_dumpLifetimeOriginsOf(func); // expected-warning {{Origin &z.a bound to z}}
}
-// Free function with annotated param and ref return
+// Free function with annotated param and ref return.
int& foo(int& num [[clang::lifetimebound]]) { return num; }
void caller_four() {
int num = 5;
int& s = foo(num);
- clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &num bound to num}}
+ clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &num bound to num}}
}
-// Free function with annotated param and ptr return
+// Free function with annotated param and ptr return.
int* boo(int* num [[clang::lifetimebound]]) { return num; }
void caller_five() {
@@ -66,7 +66,7 @@ void caller_five() {
int* n_ptr = &n;
int* s = boo(n_ptr);
- clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &n bound to n}}
+ clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &n bound to n}}
}
// Free function with both annotated and non-annotated parameters.
@@ -77,14 +77,14 @@ void caller_six() {
int odd = 55;
int& s = fn(even, odd);
- clang_analyzer_lifetime_bound(s); // expected-warning {{Origin &odd bound to odd}}
+ clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &odd bound to odd}}
}
// These are the cases when the result of function calls are SymbolRefs.
-// Function returns ptr and has an annotated parameter
+// Function returns ptr and has an annotated parameter.
int* foo(int* n [[clang::lifetimebound]]);
void caller_seven() {
@@ -92,17 +92,17 @@ void caller_seven() {
int* y_ptr = &y;
auto* bind = foo(y_ptr);
- clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}}
+ clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}}
}
-// Function returns a reference and has an annotated parameter
+// Function returns a reference and has an annotated parameter.
int& func(int& some_number [[clang::lifetimebound]]);
void caller_eight() {
int f = 15;
auto& bind = func(f);
- clang_analyzer_lifetime_bound(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}}
+ clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}}
}
// Function returns a reference and has two annotated parameters.
@@ -113,9 +113,7 @@ void caller_nine() {
int second_num = 2;
int& numbers = f(first_num, second_num);
- clang_analyzer_lifetime_bound(numbers);
- // expected-warning-re at -1 {{Origin &SymRegion{{.*}} bound to first_num}}
- // expected-warning-re at -2 {{Origin &SymRegion{{.*}} bound to second_num}}
+ clang_analyzer_dumpLifetimeOriginsOf(numbers); // expected-warning-re {{Origin &SymRegion{{.*}} bound to first_num, second_num}}
}
struct View {
@@ -123,20 +121,20 @@ struct View {
};
View makeView(int& x [[clang::lifetimebound]]);
-void clang_analyzer_lifetime_bound(View);
+void clang_analyzer_dumpLifetimeOriginsOf(View);
void caller_view() {
int v = 42;
View w = makeView(v);
- // FIXME: Currently none of the maps cover LazyCompoundVal
- clang_analyzer_lifetime_bound(w); // no-warning
+ // FIXME: Currently none of the maps cover LazyCompoundVal.
+ clang_analyzer_dumpLifetimeOriginsOf(w); // no-warning
}
-// These are the test cases for testing the correctness of the emitted warning from the LifetimeAnnotations checker.
+// These are the test cases for testing the correctness of the emitted warning from the UseAfterLifetimeEnd checker.
-// Return value bound to annotated param cases
+// Return value bound to annotated param cases.
int *test_func(int *p [[clang::lifetimebound]]);
@@ -169,3 +167,4 @@ int* g() {
(void)p;
return nullptr; // no-warning
}
+
>From 3d04217388bb20497786031a9c38964b24a25bca Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 25 Jun 2026 15:25:21 +0200
Subject: [PATCH 03/21] Removed dead codes.
---
.../Checkers/LifetimeAnnotations.cpp | 305 ------------------
.../Checkers/UseAfterLifetimeEnd.cpp | 81 +----
2 files changed, 12 insertions(+), 374 deletions(-)
delete mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
deleted file mode 100644
index e25d076dd0bd5..0000000000000
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ /dev/null
@@ -1,305 +0,0 @@
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
-#include "clang/AST/Attrs.inc"
-#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
-#include "clang/StaticAnalyzer/Core/Checker.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
-#include "llvm/Support/raw_ostream.h"
-
-using namespace clang;
-using namespace ento;
-
-REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
-REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
-
-REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
-
-namespace {
-class LifetimeAnnotations
- : public Checker<check::PostCall, check::EndFunction, check::Location,
- check::DeadSymbols> {
-public:
- void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
- const char *Sep) const override;
- void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
- CheckerContext &C) const;
- void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N,
- CheckerContext &C) const;
-
- void checkReturnedBorrower(SVal Val, ProgramStateRef State,
- CheckerContext &C) const;
- void reportDanglingBorrower(const LifetimeSourceSet *Sources,
- CheckerContext &C) const;
- void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
- void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
- CheckerContext &C) const;
- void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
-
- const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
-};
-
-} // namespace
-
-static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
- const MemRegion *Source) {
- LifetimeSourceSet::Factory &F =
- State->getStateManager().get_context<LifetimeSourceSet>();
-
- const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
- LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
- Set = F.add(Set, Source);
- State = State->set<LifetimeBoundMap>(RetVal, Set);
- return State;
-}
-
-void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
-
- const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
- if (!FC)
- return;
-
- const FunctionDecl *FD = FC->getDecl();
- if (!FD)
- return;
-
- SVal RetVal = Call.getReturnValue();
-
- 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 = bindValues(State, RetVal, ArgValRegion);
- }
- }
-
- if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
- if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
- if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
- State = bindValues(State, RetVal, AttrRegion);
- }
- }
- }
- C.addTransition(State);
-}
-
-static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
- CheckerContext &C) {
- // FIXME: The checker currently handles stack-region sources. Other
- // region kinds require separate methodology. For example, heap
- // regions do not go out of scope at the end of a stack frame, so
- // in order to detect those type of dangling sources the function
- // needs to be expanded to an event-driven approach as well.
- if (const auto *StackSpace =
- Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
- const StackFrame *SF = StackSpace->getStackFrame();
- const StackFrame *CurrentSF = C.getStackFrame();
- if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
- return true;
- }
- return false;
-}
-
-void LifetimeAnnotations::checkReturnedBorrower(SVal Val, ProgramStateRef State,
- CheckerContext &C) const {
- if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- for (const MemRegion *Region : *SourceSet) {
- if (hasDanglingSource(Region, State, C))
- reportDanglingSource(Region, N, C);
- }
- }
- }
-}
-
-void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
- CheckerContext &C) const {
- if (!RS)
- return;
-
- ProgramStateRef State = C.getState();
- auto LBMap = State->get<LifetimeBoundMap>();
-
- if (LBMap.isEmpty())
- return;
-
- const Expr *RetExpr = RS->getRetValue();
- if (!RetExpr)
- return;
-
- RetExpr = RetExpr->IgnoreParens();
- SVal RetVal = C.getSVal(RetExpr);
- checkReturnedBorrower(RetVal, State, C);
-}
-
-void LifetimeAnnotations::reportDanglingBorrower(
- const LifetimeSourceSet *Sources, CheckerContext &C) const {
- ProgramStateRef State = C.getState();
-
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
-
- for (const MemRegion *Source : *Sources) {
- if (State->contains<DeallocatedSourceSet>(Source)) {
- reportUseAfterScope(Source, N, C);
- }
- }
-}
-
-void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
- auto LBMap = State->get<LifetimeBoundMap>();
-
- if (LBMap.isEmpty())
- return;
-
- // FIXME: If a borrower has multiple bound sources the callback
- // warns if any of the sources have died. PathDiagnosticVisitor
- // should be used to trace and identify which annotated parameter
- // recorded the binding. Attaching this information as path notes
- // would make the diagnostics more useful to the user.
- if (auto *SourceSet = State->get<LifetimeBoundMap>(Loc))
- reportDanglingBorrower(SourceSet, C);
-}
-
-void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
- ExplodedNode *N,
- CheckerContext &C) const {
- auto BR = std::make_unique<PathSensitiveBugReport>(
- BugMsg,
- (llvm::Twine("Returning value bound to '") + Region->getString() +
- "' that will go out of scope")
- .str(),
- N);
- C.emitReport(std::move(BR));
-}
-
-void LifetimeAnnotations::reportUseAfterScope(const MemRegion *Region,
- ExplodedNode *N,
- CheckerContext &C) const {
- auto BR = std::make_unique<PathSensitiveBugReport>(
- BugMsg,
- (llvm::Twine("Use of '") + Region->getString() +
- "' after its lifetime ended.")
- .str(),
- N);
- C.emitReport(std::move(BR));
-}
-
-void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
- LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
-
- DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
-
- for (SVal Val : llvm::make_first_range(LBMap)) {
- if (const MemRegion *ValRegion = Val.getAsRegion()) {
- if (!SymReaper.isLiveRegion(ValRegion))
- State = State->remove<LifetimeBoundMap>(Val);
- } else if (SymbolRef ValRef =
- Val.getAsSymbol(/*IncludeBaseRegions=*/true)) {
- if (!SymReaper.isLive(ValRef))
- State = State->remove<LifetimeBoundMap>(Val);
- }
- }
-
- for (const MemRegion *Region : Sources) {
- if (!SymReaper.isLiveRegion(Region))
- State = State->remove<DeallocatedSourceSet>(Region);
- }
-
- C.addTransition(State);
-}
-
-void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
- const char *NL, const char *Sep) const {
- auto LBMap = State->get<LifetimeBoundMap>();
-
- if (LBMap.isEmpty())
- return;
-
- Out << Sep << "LifetimeBound bindings:" << NL;
- for (auto &&[OriginSym, SourceSet] : LBMap) {
- for (const auto *Region : SourceSet)
- Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
- }
-}
-
-namespace {
-class DebugLifetimeAnnotations : public Checker<eval::Call> {
-public:
- bool evalCall(const CallEvent &Call, CheckerContext &C) const;
- void analyzerLifetimeBound(const CallEvent &Call, CheckerContext &C) const;
-
- const BugType BugMsg{this, "DebugLifetimeAnnotations", "DebugLifetimeBound"};
- using FnCheck = void (DebugLifetimeAnnotations::*)(const CallEvent &Call,
- CheckerContext &C) const;
-
- const CallDescriptionMap<FnCheck> Callbacks = {
- {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
- &DebugLifetimeAnnotations::analyzerLifetimeBound},
- };
-};
-
-} // namespace
-
-bool DebugLifetimeAnnotations::evalCall(const CallEvent &Call,
- CheckerContext &C) const {
-
- const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
- if (!CE)
- return false;
-
- const FnCheck *Handler = Callbacks.lookup(Call);
- if (!Handler)
- return false;
-
- (this->*(*Handler))(Call, C);
- return true;
-}
-
-void DebugLifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call,
- CheckerContext &C) const {
-
- ProgramStateRef State = C.getState();
- unsigned int ArgCount = Call.getNumArgs();
- if (ArgCount != 1)
- return;
-
- SVal ArgSVal = Call.getArgSVal(0);
-
- if (auto *SourceSet = State->get<LifetimeBoundMap>(ArgSVal)) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- for (const auto *Region : *SourceSet) {
- llvm::SmallString<128> Str;
- llvm::raw_svector_ostream OS(Str);
- OS << " Origin " << ArgSVal << " bound to " << Region;
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
- C.emitReport(std::move(BR));
- }
- }
- }
-}
-
-void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
- mgr.registerChecker<LifetimeAnnotations>();
-}
-
-bool ento::shouldRegisterLifetimeAnnotations(const CheckerManager &mgr) {
- return true;
-}
-
-void ento::registerDebugLifetimeAnnotations(CheckerManager &mgr) {
- mgr.registerChecker<DebugLifetimeAnnotations>();
-}
-
-bool ento::shouldRegisterDebugLifetimeAnnotations(const CheckerManager &mgr) {
- return true;
-}
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index 81a7aabd6f7a4..1f11becd39046 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -13,30 +13,19 @@ using namespace ento;
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
-REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
-
namespace {
class UseAfterLifetimeEnd
- : public Checker<check::PostCall, check::EndFunction, check::Location,
- check::DeadSymbols> {
+ : public Checker<check::PostCall, check::EndFunction, check::DeadSymbols> {
public:
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
CheckerContext &C) const;
- void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N,
- CheckerContext &C) const;
-
void checkReturnedBorrower(SVal Val, ProgramStateRef State,
CheckerContext &C) const;
- void reportDanglingBorrower(const LifetimeSourceSet *Sources,
- CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
- void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
- CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
-
const BugType BugMsg{this, "UseAfterLifetimeEnd", "LifetimeBound"};
};
@@ -106,10 +95,14 @@ static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
void UseAfterLifetimeEnd::checkReturnedBorrower(SVal Val, ProgramStateRef State,
CheckerContext &C) const {
if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- for (const MemRegion *Region : *SourceSet) {
- if (hasDanglingSource(Region, State, C))
- reportDanglingSource(Region, N, C);
+ ExplodedNode *N = nullptr;
+ for (const MemRegion *Region : *SourceSet) {
+ if (hasDanglingSource(Region, State, C)) {
+ if (!N)
+ N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+ reportDanglingSource(Region, N, C);
}
}
}
@@ -135,38 +128,6 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
checkReturnedBorrower(RetVal, State, C);
}
-void UseAfterLifetimeEnd::reportDanglingBorrower(
- const LifetimeSourceSet *Sources, CheckerContext &C) const {
- ProgramStateRef State = C.getState();
-
- ExplodedNode *N = C.generateNonFatalErrorNode();
- if (!N)
- return;
-
- for (const MemRegion *Source : *Sources) {
- if (State->contains<DeallocatedSourceSet>(Source)) {
- reportUseAfterScope(Source, N, C);
- }
- }
-}
-
-void UseAfterLifetimeEnd::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
- auto LBMap = State->get<LifetimeBoundMap>();
-
- if (LBMap.isEmpty())
- return;
-
- // FIXME: If a borrower has multiple bound sources the callback
- // warns if any of the sources have died. PathDiagnosticVisitor
- // should be used to trace and identify which annotated parameter
- // recorded the binding. Attaching this information as path notes
- // would make the diagnostics more useful to the user.
- if (auto *SourceSet = State->get<LifetimeBoundMap>(Loc))
- reportDanglingBorrower(SourceSet, C);
-}
-
void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region,
ExplodedNode *N,
CheckerContext &C) const {
@@ -178,24 +139,11 @@ void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region,
C.emitReport(std::move(BR));
}
-void UseAfterLifetimeEnd::reportUseAfterScope(const MemRegion *Region,
- ExplodedNode *N,
- CheckerContext &C) const {
- auto BR = std::make_unique<PathSensitiveBugReport>(
- BugMsg,
- (llvm::Twine("Use of '") + Region->getString() +
- "' after its lifetime ended."),
- N);
- C.emitReport(std::move(BR));
-}
-
void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
- DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
-
for (SVal Val : llvm::make_first_range(LBMap)) {
if (const MemRegion *ValRegion = Val.getAsRegion()) {
if (!SymReaper.isLiveRegion(ValRegion))
@@ -207,11 +155,6 @@ void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper,
}
}
- for (const MemRegion *Region : Sources) {
- if (!SymReaper.isLiveRegion(Region))
- State = State->remove<DeallocatedSourceSet>(Region);
- }
-
C.addTransition(State);
}
@@ -281,13 +224,13 @@ void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf(
SVal ArgSVal = Call.getArgSVal(0);
const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal);
+ if (!SourceSet)
+ return;
+
llvm::SmallString<128> Str;
llvm::raw_svector_ostream OS(Str);
OS << " Origin " << ArgSVal << " bound to ";
- if (!SourceSet)
- return;
-
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
bool First = true;
for (const MemRegion *Region : *SourceSet) {
>From 8b106907b2d8b305f76e05a9d635dbbe92f338dc Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 25 Jun 2026 16:24:18 +0200
Subject: [PATCH 04/21] Corrected HelpText in the DebugUseAfterLifetimeEnd
checker.
---
clang/include/clang/StaticAnalyzer/Checkers/Checkers.td | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 2c59fddc89ca1..b565481d28fdb 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -1582,8 +1582,8 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">,
Documentation<NotDocumented>;
def DebugUseAfterLifetimeEnd : Checker<"DebugUseAfterLifetimeEnd">,
- HelpText<"Prints the bindings recorded by the LifetimeAnnotations checker. "
- "Use with clang_analyzer_lifetime_bound().">,
+ HelpText<"Prints the bindings recorded by the UseAfterLifetimeEnd checker. "
+ "Use with clang_analyzer_dumpLifetimeOriginsOf().">,
WeakDependencies<[UseAfterLifetimeEnd]>,
Documentation<NotDocumented>;
>From 7dddf8fd14e911d672f83873b82b163af6be674e Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 26 Jun 2026 01:09:44 +0200
Subject: [PATCH 05/21] Implemented the Modeling checker.
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 6 ++
.../Checkers/LifetimeModeling.h | 18 ++++
.../StaticAnalyzer/Checkers/CMakeLists.txt | 1 +
.../Checkers/LifetimeModeling.cpp | 101 ++++++++++++++++++
4 files changed, 126 insertions(+)
create mode 100644 clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
create mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index b565481d28fdb..7ae7734cf6a4c 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -788,9 +788,15 @@ def SmartPtrChecker: Checker<"SmartPtr">,
Dependencies<[SmartPtrModeling]>,
Documentation<HasDocumentation>;
+def LifetimeModeling : Checker<"LifetimeModeling">,
+ HelpText<"Model [[clang::lifetimebound]] annotations for lifetime analysis">,
+ Documentation<NotDocumented>,
+ Hidden;
+
def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">,
HelpText<"Check for uses of references or pointers that "
"outlive their bound object">,
+ Dependencies<[LifetimeModeling]>,
Documentation<NotDocumented>;
} // end: "alpha.cplusplus"
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
new file mode 100644
index 0000000000000..671d5ce1ec5da
--- /dev/null
+++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -0,0 +1,18 @@
+#ifndef LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
+#define LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
+
+#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
+#include <vector>
+
+namespace clang {
+namespace ento {
+namespace lifetimemodeling {
+
+std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal);
+
+} // namespace lifetimemodeling
+} // namespace ento
+} // namespace clang
+
+#endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 46c0c36fda736..8f61b7d88e8d2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -55,6 +55,7 @@ add_clang_library(clangStaticAnalyzerCheckers
IteratorModeling.cpp
IteratorRangeChecker.cpp
IvarInvalidationChecker.cpp
+ LifetimeModeling.cpp
LLVMConventionsChecker.cpp
LocalizationChecker.cpp
MacOSKeychainAPIChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
new file mode 100644
index 0000000000000..65aa071a3284a
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -0,0 +1,101 @@
+#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
+#include "clang/AST/Attr.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+using namespace lifetimemodeling;
+
+REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
+
+REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
+
+namespace {
+
+class LifetimeModeling : public Checker<check::PostCall, check::LifetimeEnd> {
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
+};
+
+} // namespace
+
+std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef State,
+ SVal Val) {
+ std::vector<const MemRegion *> StoreRegion;
+ if (const auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
+ for (const MemRegion *Region : *SourceSet)
+ StoreRegion.push_back(Region);
+ return StoreRegion;
+ }
+ return StoreRegion;
+}
+
+static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
+ const MemRegion *Source) {
+ LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
+ const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
+
+ LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
+ Set = F.add(Set, Source);
+ State = State->set<LifetimeBoundMap>(RetVal, Set);
+ return State;
+}
+
+void LifetimeModeling::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
+ if (!FC)
+ return;
+
+ const FunctionDecl *FD = FC->getDecl();
+ if (!FD)
+ return;
+
+ SVal RetVal = Call.getReturnValue();
+
+ 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 = bindValues(State, RetVal, ArgValRegion);
+ }
+ }
+
+ if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+ if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
+ if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion())
+ State = bindValues(State, RetVal, AttrRegion);
+ }
+ }
+ C.addTransition(State);
+}
+
+void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ if (!VD)
+ return;
+
+ SVal SourceVal = State->getLValue(VD, C.getStackFrame());
+ if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) {
+ State = State->add<DeallocatedSourceSet>(SourceValRegion);
+ C.addTransition(State);
+ }
+}
+
+void ento::registerLifetimeModeling(CheckerManager &Mgr) {
+ Mgr.registerChecker<LifetimeModeling>();
+}
+
+bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) {
+ return true;
+}
>From 3d75b5e72d07e8aa0b7310c0e8b1d6f0df8f152e Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 29 Jun 2026 10:54:21 +0200
Subject: [PATCH 06/21] Add isDeallocated API to the modeling checker.
---
clang/include/clang/StaticAnalyzer/Checkers/Checkers.td | 8 ++++++++
.../clang/StaticAnalyzer/Checkers/LifetimeModeling.h | 1 +
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 5 ++++-
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 7ae7734cf6a4c..ef476e60752e0 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -799,6 +799,14 @@ def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">,
Dependencies<[LifetimeModeling]>,
Documentation<NotDocumented>;
+<<<<<<< Updated upstream
+=======
+def ReportDanglingPtrDeref : Checker<"ReportDanglingPtrDeref">,
+ HelpText<"Check for dereferences of a dangling pointer">,
+ Dependencies<[LifetimeModeling]>,
+ Documentation<NotDocumented>;
+
+>>>>>>> Stashed changes
} // end: "alpha.cplusplus"
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
index 671d5ce1ec5da..e9eb4979e05d2 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -10,6 +10,7 @@ namespace ento {
namespace lifetimemodeling {
std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal);
+bool isDeallocated(ProgramStateRef, const MemRegion *);
} // namespace lifetimemodeling
} // namespace ento
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 65aa071a3284a..1355c4218f921 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -31,11 +31,14 @@ std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef State,
if (const auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet)
StoreRegion.push_back(Region);
- return StoreRegion;
}
return StoreRegion;
}
+bool isDeallocated(ProgramStateRef State, const MemRegion *Region) {
+ return State->contains<DeallocatedSourceSet>(Region);
+}
+
static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
const MemRegion *Source) {
LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
>From a4c62a54bc4542d72cfb1c245d8d0f6324d60a3f Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 29 Jun 2026 20:35:51 +0200
Subject: [PATCH 07/21] Cleaned up the Modeling checker.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 1355c4218f921..01293daa22497 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -8,7 +8,6 @@
using namespace clang;
using namespace ento;
-using namespace lifetimemodeling;
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
@@ -25,8 +24,8 @@ class LifetimeModeling : public Checker<check::PostCall, check::LifetimeEnd> {
} // namespace
-std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef State,
- SVal Val) {
+std::vector<const MemRegion *>
+lifetimemodeling::getLifetimeSourceSet(ProgramStateRef State, SVal Val) {
std::vector<const MemRegion *> StoreRegion;
if (const auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet)
@@ -35,7 +34,8 @@ std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef State,
return StoreRegion;
}
-bool isDeallocated(ProgramStateRef State, const MemRegion *Region) {
+bool lifetimemodeling::isDeallocated(ProgramStateRef State,
+ const MemRegion *Region) {
return State->contains<DeallocatedSourceSet>(Region);
}
>From db46aeeff420a16bb0b4c8983de313be44fac87e Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 29 Jun 2026 20:40:41 +0200
Subject: [PATCH 08/21] Resolved updated upstream conflict.
---
clang/include/clang/StaticAnalyzer/Checkers/Checkers.td | 8 --------
1 file changed, 8 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index ef476e60752e0..7ae7734cf6a4c 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -799,14 +799,6 @@ def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">,
Dependencies<[LifetimeModeling]>,
Documentation<NotDocumented>;
-<<<<<<< Updated upstream
-=======
-def ReportDanglingPtrDeref : Checker<"ReportDanglingPtrDeref">,
- HelpText<"Check for dereferences of a dangling pointer">,
- Dependencies<[LifetimeModeling]>,
- Documentation<NotDocumented>;
-
->>>>>>> Stashed changes
} // end: "alpha.cplusplus"
//===----------------------------------------------------------------------===//
>From 5e1feee2645231e30559edc5efdc50a961035efd Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 30 Jun 2026 00:15:11 +0200
Subject: [PATCH 09/21] Cleaned up modeling checker.
---
.../Checkers/LifetimeModeling.h | 2 +
.../Checkers/LifetimeModeling.cpp | 34 ++++++
.../Checkers/UseAfterLifetimeEnd.cpp | 111 ++----------------
3 files changed, 46 insertions(+), 101 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
index e9eb4979e05d2..14c6275812f7b 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -3,6 +3,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
#include <vector>
namespace clang {
@@ -11,6 +12,7 @@ namespace lifetimemodeling {
std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal);
bool isDeallocated(ProgramStateRef, const MemRegion *);
+ProgramStateRef removeDeadBindings(ProgramStateRef, SymbolReaper &);
} // namespace lifetimemodeling
} // namespace ento
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 01293daa22497..6ba89fca8e7a8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -5,6 +5,7 @@
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace ento;
@@ -18,6 +19,8 @@ namespace {
class LifetimeModeling : public Checker<check::PostCall, check::LifetimeEnd> {
public:
+ void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+ const char *Sep) const override;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
};
@@ -95,6 +98,37 @@ void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
}
}
+ProgramStateRef lifetimemodeling::removeDeadBindings(ProgramStateRef State,
+ SymbolReaper &SymReaper) {
+ LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
+
+ for (SVal Val : llvm::make_first_range(LBMap)) {
+ if (const MemRegion *ValRegion = Val.getAsRegion()) {
+ if (!SymReaper.isLiveRegion(ValRegion))
+ State = State->remove<LifetimeBoundMap>(Val);
+ } else if (SymbolRef ValRef =
+ Val.getAsSymbol(/*IncludeBaseRegions=*/true)) {
+ if (!SymReaper.isLive(ValRef))
+ State = State->remove<LifetimeBoundMap>(Val);
+ }
+ }
+ return State;
+}
+
+void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+ auto LBMap = State->get<LifetimeBoundMap>();
+
+ if (LBMap.isEmpty())
+ return;
+
+ Out << Sep << "LifetimeBound bindings:" << NL;
+ for (auto &&[OriginSym, SourceSet] : LBMap) {
+ for (const auto *Region : SourceSet)
+ Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
+ }
+}
+
void ento::registerLifetimeModeling(CheckerManager &Mgr) {
Mgr.registerChecker<LifetimeModeling>();
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index a1f754b319141..ad9c7980039cc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -1,6 +1,7 @@
#include "clang/AST/Attr.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
@@ -10,16 +11,10 @@
using namespace clang;
using namespace ento;
-REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
-REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
-
namespace {
class UseAfterLifetimeEnd
- : public Checker<check::PostCall, check::EndFunction, check::DeadSymbols> {
+ : public Checker<check::EndFunction, check::DeadSymbols> {
public:
- void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
- const char *Sep) const override;
void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
CheckerContext &C) const;
void checkReturnedBorrower(SVal Val, ProgramStateRef State,
@@ -31,50 +26,6 @@ class UseAfterLifetimeEnd
} // namespace
-static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
- const MemRegion *Source) {
- LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
-
- const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
- LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
- Set = F.add(Set, Source);
- State = State->set<LifetimeBoundMap>(RetVal, Set);
- return State;
-}
-
-void UseAfterLifetimeEnd::checkPostCall(const CallEvent &Call,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
-
- const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
- if (!FC)
- return;
-
- const FunctionDecl *FD = FC->getDecl();
- if (!FD)
- return;
-
- SVal RetVal = Call.getReturnValue();
-
- 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 = bindValues(State, RetVal, ArgValRegion);
- }
- }
-
- if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
- if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
- if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
- State = bindValues(State, RetVal, AttrRegion);
- }
- }
- }
- C.addTransition(State);
-}
-
static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
CheckerContext &C) {
// FIXME: The checker currently handles stack-region sources. Other
@@ -94,9 +45,10 @@ static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
void UseAfterLifetimeEnd::checkReturnedBorrower(SVal Val, ProgramStateRef State,
CheckerContext &C) const {
- if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
+ auto SourceSet = lifetimemodeling::getLifetimeSourceSet(State, Val);
+ if (!SourceSet.empty()) {
ExplodedNode *N = nullptr;
- for (const MemRegion *Region : *SourceSet) {
+ for (const MemRegion *Region : SourceSet) {
if (hasDanglingSource(Region, State, C)) {
if (!N)
N = C.generateNonFatalErrorNode();
@@ -114,10 +66,6 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
return;
ProgramStateRef State = C.getState();
- auto LBMap = State->get<LifetimeBoundMap>();
-
- if (LBMap.isEmpty())
- return;
const Expr *RetExpr = RS->getRetValue();
if (!RetExpr)
@@ -141,37 +89,11 @@ void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region,
void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
- ProgramStateRef State = C.getState();
- LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
-
- for (SVal Val : llvm::make_first_range(LBMap)) {
- if (const MemRegion *ValRegion = Val.getAsRegion()) {
- if (!SymReaper.isLiveRegion(ValRegion))
- State = State->remove<LifetimeBoundMap>(Val);
- } else if (SymbolRef ValRef =
- Val.getAsSymbol(/*IncludeBaseRegions=*/true)) {
- if (!SymReaper.isLive(ValRef))
- State = State->remove<LifetimeBoundMap>(Val);
- }
- }
-
+ ProgramStateRef State =
+ lifetimemodeling::removeDeadBindings(C.getState(), SymReaper);
C.addTransition(State);
}
-void UseAfterLifetimeEnd::printState(raw_ostream &Out, ProgramStateRef State,
- const char *NL, const char *Sep) const {
- auto LBMap = State->get<LifetimeBoundMap>();
-
- if (LBMap.isEmpty())
- return;
-
- Out << Sep << "LifetimeBound bindings:" << NL;
- for (auto &&[OriginSym, SourceSet] : LBMap) {
- for (const auto *Region : SourceSet)
- Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
- }
-}
-
namespace {
class DebugUseAfterLifetimeEnd : public Checker<eval::Call> {
public:
@@ -222,27 +144,14 @@ void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf(
}
SVal ArgSVal = Call.getArgSVal(0);
- const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal);
+ auto SourceSet = lifetimemodeling::getLifetimeSourceSet(State, ArgSVal);
- if (!SourceSet)
+ if (SourceSet.empty())
return;
- llvm::SmallString<128> Str;
- llvm::raw_svector_ostream OS(Str);
- OS << " Origin " << ArgSVal << " bound to ";
-
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- bool First = true;
- for (const MemRegion *Region : *SourceSet) {
- if (!First)
- OS << ", ";
- OS << Region;
- First = false;
- }
-
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
llvm::SmallVector<std::string> RegionNames =
- to_vector(map_range(llvm::make_pointee_range(*SourceSet),
+ to_vector(map_range(llvm::make_pointee_range(SourceSet),
std::mem_fn(&MemRegion::getString)));
llvm::sort(RegionNames);
>From 21f7fc32a87a13d23e125a1695677fa3d27a506e Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 30 Jun 2026 00:19:44 +0200
Subject: [PATCH 10/21] Added Checkers.td with the correct declaration of the
checkers.
---
clang/include/clang/StaticAnalyzer/Checkers/Checkers.td | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 336a14a66851a..7ae7734cf6a4c 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -797,6 +797,7 @@ def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">,
HelpText<"Check for uses of references or pointers that "
"outlive their bound object">,
Dependencies<[LifetimeModeling]>,
+ Documentation<NotDocumented>;
} // end: "alpha.cplusplus"
>From 8f00a122a3ba4f0897bbe4431d04faef6bea0bff Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 30 Jun 2026 11:54:03 +0200
Subject: [PATCH 11/21] Fix pointer/reference declarator style.
---
clang/test/Analysis/lifetime-bound.cpp | 49 +++++++++++++-------------
1 file changed, 25 insertions(+), 24 deletions(-)
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index d295ccfc55b63..4b0dbb2fea3a0 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -14,68 +14,68 @@ void clang_analyzer_dumpLifetimeOriginsOf(A&);
// Ref type parameter annotated case.
struct X {
- int& choose(int& a [[clang::lifetimebound]]) { return a; }
+ int &choose(int &a [[clang::lifetimebound]]) { return a; }
};
void caller() {
int v = 0;
X obj;
- int& r = obj.choose(v);
+ int &r = obj.choose(v);
clang_analyzer_dumpLifetimeOriginsOf(r); // expected-warning {{Origin &v bound to v}}
}
// Obj ref type function return annotated case.
struct Y {
A a;
- A& getA() [[clang::lifetimebound]] { return a; }
+ A &getA() [[clang::lifetimebound]] { return a; }
};
void caller_two() {
// Return statement is annotated case.
Y y;
- A& f = y.getA();
+ A &f = y.getA();
clang_analyzer_dumpLifetimeOriginsOf(f); // expected-warning {{Origin &y.a bound to y}}
}
// Obj ptr type function return annotated case.
struct Z {
A a;
- A* getA() [[clang::lifetimebound]] { return &a; }
+ A *getA() [[clang::lifetimebound]] { return &a; }
};
void caller_three() {
Z z;
- A* func = z.getA();
+ A *func = z.getA();
clang_analyzer_dumpLifetimeOriginsOf(func); // expected-warning {{Origin &z.a bound to z}}
}
// Free function with annotated param and ref return.
-int& foo(int& num [[clang::lifetimebound]]) { return num; }
+int &foo(int &num [[clang::lifetimebound]]) { return num; }
void caller_four() {
int num = 5;
- int& s = foo(num);
+ int &s = foo(num);
clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &num bound to num}}
}
// Free function with annotated param and ptr return.
-int* boo(int* num [[clang::lifetimebound]]) { return num; }
+int *boo(int *num [[clang::lifetimebound]]) { return num; }
void caller_five() {
int n = 55;
- int* n_ptr = &n;
- int* s = boo(n_ptr);
+ int *n_ptr = &n;
+ int *s = boo(n_ptr);
clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &n bound to n}}
}
// Free function with both annotated and non-annotated parameters.
-int& fn(int& f, int& s [[clang::lifetimebound]]) { return s; }
+int &fn(int &f, int &s [[clang::lifetimebound]]) { return s; }
void caller_six() {
int even = 50;
int odd = 55;
- int& s = fn(even, odd);
+ int &s = fn(even, odd);
clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &odd bound to odd}}
}
@@ -85,41 +85,41 @@ void caller_six() {
// These are the cases when the result of function calls are SymbolRefs.
// Function returns ptr and has an annotated parameter.
-int* foo(int* n [[clang::lifetimebound]]);
+int *foo(int *n [[clang::lifetimebound]]);
void caller_seven() {
int y = 15;
- int* y_ptr = &y;
- auto* bind = foo(y_ptr);
+ int *y_ptr = &y;
+ auto *bind = foo(y_ptr);
clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}}
}
// Function returns a reference and has an annotated parameter.
-int& func(int& some_number [[clang::lifetimebound]]);
+int &func(int &some_number [[clang::lifetimebound]]);
void caller_eight() {
int f = 15;
- auto& bind = func(f);
+ auto &bind = func(f);
clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}}
}
// Function returns a reference and has two annotated parameters.
-int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]);
+int &f(int &a [[clang::lifetimebound]], int &b [[clang::lifetimebound]]);
void caller_nine() {
int first_num = 1;
int second_num = 2;
- int& numbers = f(first_num, second_num);
+ int &numbers = f(first_num, second_num);
clang_analyzer_dumpLifetimeOriginsOf(numbers); // expected-warning-re {{Origin &SymRegion{{.*}} bound to first_num, second_num}}
}
struct View {
- int* p;
+ int *p;
};
-View makeView(int& x [[clang::lifetimebound]]);
+View makeView(int &x [[clang::lifetimebound]]);
void clang_analyzer_dumpLifetimeOriginsOf(View);
@@ -161,9 +161,10 @@ void no_return() {
(void)p; // no-warning
}
-int* g() {
+int *g() {
int i = 5;
- int* p = test_func(&i);
+ int *p = test_func(&i);
(void)p;
return nullptr; // no-warning
}
+
>From 954f485a3526c0cf413423acd75cd9eb37a1a684 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 30 Jun 2026 20:23:38 +0200
Subject: [PATCH 12/21] Clean up modeling and reporting checker to separate
functionalities.
---
.../Checkers/LifetimeModeling.h | 20 ++--
.../Checkers/LifetimeModeling.cpp | 64 ++++++++++---
.../Checkers/UseAfterLifetimeEnd.cpp | 91 +++++--------------
3 files changed, 81 insertions(+), 94 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
index 14c6275812f7b..b7e70b8f5101b 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -3,19 +3,19 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include <vector>
-namespace clang {
-namespace ento {
-namespace lifetimemodeling {
+namespace clang::ento::lifetimemodeling {
+/// Returns true if the lifetime of a region has ended.
+bool isDeallocated(ProgramStateRef State, const MemRegion *Region);
-std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal);
-bool isDeallocated(ProgramStateRef, const MemRegion *);
-ProgramStateRef removeDeadBindings(ProgramStateRef, SymbolReaper &);
+/// Returns the set of of lifetime sources bound to \p Source that are dangling stack regions.
+const std::vector<const MemRegion *> checkReturnedBorrower(SVal Source, ProgramStateRef State, CheckerContext &C);
-} // namespace lifetimemodeling
-} // namespace ento
-} // namespace clang
+/// Writes the lifetime sources bound to Source to OS.
+void dumpLifetimeSources(ProgramStateRef State, SVal Source, raw_ostream &OS);
+
+} // namespace clang::ento::lifetimemodeling
#endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 6ba89fca8e7a8..fdb7d76ddc9ae 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -17,31 +17,49 @@ REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
namespace {
-class LifetimeModeling : public Checker<check::PostCall, check::LifetimeEnd> {
+class LifetimeModeling : public Checker<check::PostCall, check::LifetimeEnd, check::DeadSymbols> {
public:
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
};
} // namespace
-std::vector<const MemRegion *>
-lifetimemodeling::getLifetimeSourceSet(ProgramStateRef State, SVal Val) {
- std::vector<const MemRegion *> StoreRegion;
- if (const auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
- for (const MemRegion *Region : *SourceSet)
- StoreRegion.push_back(Region);
- }
- return StoreRegion;
-}
-
bool lifetimemodeling::isDeallocated(ProgramStateRef State,
const MemRegion *Region) {
return State->contains<DeallocatedSourceSet>(Region);
}
+static bool getDanglingStackFrame(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) {
+ // FIXME: The checker currently handles stack-region sources. Other
+ // region kinds require separate methodology. For example, heap
+ // regions do not go out of scope at the end of a stack frame, so
+ // in order to detect those type of dangling sources the function
+ // needs to be expanded to an event-driven approach as well.
+ if (const auto *StackSpace = Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+ const StackFrame *SF = StackSpace->getStackFrame();
+ const StackFrame *CurrentSF = C.getStackFrame();
+ if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+ return true;
+ }
+ return false;
+}
+
+const std::vector<const MemRegion *> lifetimemodeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
+ CheckerContext &C) {
+ std::vector<const MemRegion *> Regions;
+ if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
+ for (const MemRegion *Region : *SourceSet) {
+ if (getDanglingStackFrame(Region, State, C))
+ Regions.push_back(Region);
+ }
+ }
+ return Regions;
+}
+
static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
const MemRegion *Source) {
LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
@@ -98,8 +116,8 @@ void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
}
}
-ProgramStateRef lifetimemodeling::removeDeadBindings(ProgramStateRef State,
- SymbolReaper &SymReaper) {
+void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
for (SVal Val : llvm::make_first_range(LBMap)) {
@@ -112,7 +130,25 @@ ProgramStateRef lifetimemodeling::removeDeadBindings(ProgramStateRef State,
State = State->remove<LifetimeBoundMap>(Val);
}
}
- return State;
+
+ const auto Sources = State->get<DeallocatedSourceSet>();
+ for (const auto *Source : Sources) {
+ if (!SymReaper.isLiveRegion(Source))
+ State = State->remove<DeallocatedSourceSet>(Source);
+ }
+ C.addTransition(State);
+}
+
+void lifetimemodeling::dumpLifetimeSources(ProgramStateRef State, SVal Source, raw_ostream &OS) {
+ const auto *SourceSet = State->get<LifetimeBoundMap>(Source);
+ if (!SourceSet)
+ return;
+
+ llvm::SmallVector<std::string> RegionNames = to_vector(map_range(llvm::make_pointee_range(*SourceSet), std::mem_fn(&MemRegion::getString)));
+ llvm::sort(RegionNames);
+
+ OS << " Origin " << Source << " bound to ";
+ llvm::interleaveComma(RegionNames, OS);
}
void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index ad9c7980039cc..8037cd43be390 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -1,5 +1,3 @@
-#include "clang/AST/Attr.h"
-#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -13,53 +11,16 @@ using namespace ento;
namespace {
class UseAfterLifetimeEnd
- : public Checker<check::EndFunction, check::DeadSymbols> {
+ : public Checker<check::EndFunction> {
public:
- void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
+ void reportDanglingSource(const MemRegion *Source, ExplodedNode *N,
CheckerContext &C) const;
- void checkReturnedBorrower(SVal Val, ProgramStateRef State,
- CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
- void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
const BugType BugMsg{this, "UseAfterLifetimeEnd", "LifetimeBound"};
};
} // namespace
-static bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
- CheckerContext &C) {
- // FIXME: The checker currently handles stack-region sources. Other
- // region kinds require separate methodology. For example, heap
- // regions do not go out of scope at the end of a stack frame, so
- // in order to detect those type of dangling sources the function
- // needs to be expanded to an event-driven approach as well.
- if (const auto *StackSpace =
- Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
- const StackFrame *SF = StackSpace->getStackFrame();
- const StackFrame *CurrentSF = C.getStackFrame();
- if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
- return true;
- }
- return false;
-}
-
-void UseAfterLifetimeEnd::checkReturnedBorrower(SVal Val, ProgramStateRef State,
- CheckerContext &C) const {
- auto SourceSet = lifetimemodeling::getLifetimeSourceSet(State, Val);
- if (!SourceSet.empty()) {
- ExplodedNode *N = nullptr;
- for (const MemRegion *Region : SourceSet) {
- if (hasDanglingSource(Region, State, C)) {
- if (!N)
- N = C.generateNonFatalErrorNode();
- if (!N)
- return;
- reportDanglingSource(Region, N, C);
- }
- }
- }
-}
-
void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
CheckerContext &C) const {
if (!RS)
@@ -73,27 +34,26 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
RetExpr = RetExpr->IgnoreParens();
SVal RetVal = C.getSVal(RetExpr);
- checkReturnedBorrower(RetVal, State, C);
+ ExplodedNode *N = nullptr;
+
+ std::vector<const MemRegion *> RetValRegion = lifetimemodeling::checkReturnedBorrower(RetVal, State, C);
+ for (const MemRegion *Region : RetValRegion) {
+ if (!N)
+ N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
+ reportDanglingSource(Region, N, C);
+ }
}
-void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Region,
+void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Source,
ExplodedNode *N,
CheckerContext &C) const {
- auto BR = std::make_unique<PathSensitiveBugReport>(
- BugMsg,
- (llvm::Twine("Returning value bound to '") + Region->getString() +
- "' that will go out of scope"),
- N);
+ auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, (llvm::Twine("Returning value bound to '") + Source->getString() + "' that will go out of scope"), N);
C.emitReport(std::move(BR));
}
-void UseAfterLifetimeEnd::checkDeadSymbols(SymbolReaper &SymReaper,
- CheckerContext &C) const {
- ProgramStateRef State =
- lifetimemodeling::removeDeadBindings(C.getState(), SymReaper);
- C.addTransition(State);
-}
-
namespace {
class DebugUseAfterLifetimeEnd : public Checker<eval::Call> {
public:
@@ -144,22 +104,13 @@ void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf(
}
SVal ArgSVal = Call.getArgSVal(0);
- auto SourceSet = lifetimemodeling::getLifetimeSourceSet(State, ArgSVal);
-
- if (SourceSet.empty())
- return;
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+ lifetimemodeling::dumpLifetimeSources(State, ArgSVal, OS);
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- llvm::SmallVector<std::string> RegionNames =
- to_vector(map_range(llvm::make_pointee_range(SourceSet),
- std::mem_fn(&MemRegion::getString)));
- llvm::sort(RegionNames);
-
- llvm::SmallString<128> Str;
- llvm::raw_svector_ostream OS(Str);
- OS << " Origin " << ArgSVal << " bound to ";
- llvm::interleaveComma(RegionNames, OS);
- C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N));
+ if (!Str.empty()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode())
+ C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N));
}
}
>From 3441d06db73a08baf37db189ef44311dcceb4735 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 30 Jun 2026 22:58:21 +0200
Subject: [PATCH 13/21] Moved debug checker to the modeling checker and
resolved nits.
---
.../Checkers/LifetimeModeling.h | 15 +-
.../Checkers/LifetimeModeling.cpp | 129 ++++++++++++------
.../Checkers/UseAfterLifetimeEnd.cpp | 84 ++----------
clang/test/Analysis/debug-lifetime-bound.cpp | 2 +-
clang/test/Analysis/lifetime-bound.cpp | 53 ++++++-
5 files changed, 152 insertions(+), 131 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
index b7e70b8f5101b..38b8937ed6c43 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -1,21 +1,20 @@
#ifndef LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
#define LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include <vector>
-namespace clang::ento::lifetimemodeling {
+namespace clang::ento::lifetime_modeling {
/// Returns true if the lifetime of a region has ended.
bool isDeallocated(ProgramStateRef State, const MemRegion *Region);
-/// Returns the set of of lifetime sources bound to \p Source that are dangling stack regions.
-const std::vector<const MemRegion *> checkReturnedBorrower(SVal Source, ProgramStateRef State, CheckerContext &C);
-
-/// Writes the lifetime sources bound to Source to OS.
-void dumpLifetimeSources(ProgramStateRef State, SVal Source, raw_ostream &OS);
+/// Returns the set of lifetime sources bound to \p Source that are dangling
+/// stack regions.
+const std::vector<const MemRegion *>
+checkReturnedBorrower(SVal Source, ProgramStateRef State, CheckerContext &C);
-} // namespace clang::ento::lifetimemodeling
+} // namespace clang::ento::lifetime_modeling
#endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index fdb7d76ddc9ae..6276d7b8960c1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -3,6 +3,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "llvm/Support/raw_ostream.h"
@@ -13,33 +14,27 @@ using namespace ento;
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
-REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
-
namespace {
-class LifetimeModeling : public Checker<check::PostCall, check::LifetimeEnd, check::DeadSymbols> {
+class LifetimeModeling : public Checker<check::PostCall, check::DeadSymbols> {
public:
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
};
} // namespace
-bool lifetimemodeling::isDeallocated(ProgramStateRef State,
- const MemRegion *Region) {
- return State->contains<DeallocatedSourceSet>(Region);
-}
-
-static bool getDanglingStackFrame(const MemRegion *Source, ProgramStateRef State, CheckerContext &C) {
+static bool getDanglingStackFrame(const MemRegion *Source,
+ ProgramStateRef State, CheckerContext &C) {
// FIXME: The checker currently handles stack-region sources. Other
// region kinds require separate methodology. For example, heap
// regions do not go out of scope at the end of a stack frame, so
// in order to detect those type of dangling sources the function
// needs to be expanded to an event-driven approach as well.
- if (const auto *StackSpace = Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+ if (const auto *StackSpace =
+ Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
@@ -48,8 +43,9 @@ static bool getDanglingStackFrame(const MemRegion *Source, ProgramStateRef State
return false;
}
-const std::vector<const MemRegion *> lifetimemodeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
- CheckerContext &C) {
+const std::vector<const MemRegion *>
+lifetime_modeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
+ CheckerContext &C) {
std::vector<const MemRegion *> Regions;
if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet) {
@@ -103,21 +99,9 @@ void LifetimeModeling::checkPostCall(const CallEvent &Call,
C.addTransition(State);
}
-void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
+void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
- if (!VD)
- return;
-
- SVal SourceVal = State->getLValue(VD, C.getStackFrame());
- if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) {
- State = State->add<DeallocatedSourceSet>(SourceValRegion);
- C.addTransition(State);
- }
-}
-
-void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
- ProgramStateRef State = C.getState();
LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
for (SVal Val : llvm::make_first_range(LBMap)) {
@@ -130,27 +114,9 @@ void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext
State = State->remove<LifetimeBoundMap>(Val);
}
}
-
- const auto Sources = State->get<DeallocatedSourceSet>();
- for (const auto *Source : Sources) {
- if (!SymReaper.isLiveRegion(Source))
- State = State->remove<DeallocatedSourceSet>(Source);
- }
C.addTransition(State);
}
-void lifetimemodeling::dumpLifetimeSources(ProgramStateRef State, SVal Source, raw_ostream &OS) {
- const auto *SourceSet = State->get<LifetimeBoundMap>(Source);
- if (!SourceSet)
- return;
-
- llvm::SmallVector<std::string> RegionNames = to_vector(map_range(llvm::make_pointee_range(*SourceSet), std::mem_fn(&MemRegion::getString)));
- llvm::sort(RegionNames);
-
- OS << " Origin " << Source << " bound to ";
- llvm::interleaveComma(RegionNames, OS);
-}
-
void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
auto LBMap = State->get<LifetimeBoundMap>();
@@ -165,6 +131,73 @@ void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
}
}
+namespace {
+class DebugLifetimeModeling : public Checker<eval::Call> {
+public:
+ bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+ void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
+ CheckerContext &C) const;
+ const BugType BugMsg{this, "DebugLifetimeModeling", "DebugLifetimeModeling"};
+ using FnCheck = void (DebugLifetimeModeling::*)(const CallEvent &Call,
+ CheckerContext &C) const;
+
+ const CallDescriptionMap<FnCheck> Callbacks = {
+ {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
+ &DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf},
+ };
+};
+
+} // namespace
+
+bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
+ if (!CE)
+ return false;
+
+ const FnCheck *Handler = Callbacks.lookup(Call);
+ if (!Handler)
+ return false;
+
+ (this->*(*Handler))(Call, C);
+ return true;
+}
+
+void DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf(
+ const CallEvent &Call, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ if (Call.getNumArgs() != 1) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ auto BR = std::make_unique<PathSensitiveBugReport>(
+ BugMsg,
+ "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
+ N);
+ C.emitReport(std::move(BR));
+ }
+ return;
+ }
+
+ SVal ArgSVal = Call.getArgSVal(0);
+ const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal);
+
+ if (!SourceSet)
+ return;
+
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ llvm::SmallVector<std::string> RegionNames =
+ to_vector(map_range(llvm::make_pointee_range(*SourceSet),
+ std::mem_fn(&MemRegion::getString)));
+ llvm::sort(RegionNames);
+
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+ OS << " Origin " << ArgSVal << " bound to ";
+ llvm::interleaveComma(RegionNames, OS);
+ C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N));
+ }
+}
+
void ento::registerLifetimeModeling(CheckerManager &Mgr) {
Mgr.registerChecker<LifetimeModeling>();
}
@@ -172,3 +205,11 @@ void ento::registerLifetimeModeling(CheckerManager &Mgr) {
bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) {
return true;
}
+
+void ento::registerDebugLifetimeModeling(CheckerManager &Mgr) {
+ Mgr.registerChecker<DebugLifetimeModeling>();
+}
+
+bool ento::shouldRegisterDebugLifetimeModeling(const CheckerManager &Mgr) {
+ return true;
+}
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index 8037cd43be390..550e7f9949694 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -1,17 +1,12 @@
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
-#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace ento;
namespace {
-class UseAfterLifetimeEnd
- : public Checker<check::EndFunction> {
+class UseAfterLifetimeEnd : public Checker<check::EndFunction> {
public:
void reportDanglingSource(const MemRegion *Source, ExplodedNode *N,
CheckerContext &C) const;
@@ -36,7 +31,8 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
SVal RetVal = C.getSVal(RetExpr);
ExplodedNode *N = nullptr;
- std::vector<const MemRegion *> RetValRegion = lifetimemodeling::checkReturnedBorrower(RetVal, State, C);
+ std::vector<const MemRegion *> RetValRegion =
+ lifetime_modeling::checkReturnedBorrower(RetVal, State, C);
for (const MemRegion *Region : RetValRegion) {
if (!N)
N = C.generateNonFatalErrorNode();
@@ -50,70 +46,14 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Source,
ExplodedNode *N,
CheckerContext &C) const {
- auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, (llvm::Twine("Returning value bound to '") + Source->getString() + "' that will go out of scope"), N);
+ auto BR = std::make_unique<PathSensitiveBugReport>(
+ BugMsg,
+ (llvm::Twine("Returning value bound to '") + Source->getString() +
+ "' that will go out of scope"),
+ N);
C.emitReport(std::move(BR));
}
-namespace {
-class DebugUseAfterLifetimeEnd : public Checker<eval::Call> {
-public:
- bool evalCall(const CallEvent &Call, CheckerContext &C) const;
- void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
- CheckerContext &C) const;
-
- const BugType BugMsg{this, "DebugUseAfterLifetimeEnd",
- "DebugUseAfterLifetimeEnd"};
- using FnCheck = void (DebugUseAfterLifetimeEnd::*)(const CallEvent &Call,
- CheckerContext &C) const;
-
- const CallDescriptionMap<FnCheck> Callbacks = {
- {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
- &DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf},
- };
-};
-
-} // namespace
-
-bool DebugUseAfterLifetimeEnd::evalCall(const CallEvent &Call,
- CheckerContext &C) const {
- const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
- if (!CE)
- return false;
-
- const FnCheck *Handler = Callbacks.lookup(Call);
- if (!Handler)
- return false;
-
- (this->*(*Handler))(Call, C);
- return true;
-}
-
-void DebugUseAfterLifetimeEnd::analyzerDumpLifetimeOriginsOf(
- const CallEvent &Call, CheckerContext &C) const {
- ProgramStateRef State = C.getState();
-
- if (Call.getNumArgs() != 1) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- auto BR = std::make_unique<PathSensitiveBugReport>(
- BugMsg,
- "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
- N);
- C.emitReport(std::move(BR));
- }
- return;
- }
-
- SVal ArgSVal = Call.getArgSVal(0);
- llvm::SmallString<128> Str;
- llvm::raw_svector_ostream OS(Str);
- lifetimemodeling::dumpLifetimeSources(State, ArgSVal, OS);
-
- if (!Str.empty()) {
- if (ExplodedNode *N = C.generateNonFatalErrorNode())
- C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N));
- }
-}
-
void ento::registerUseAfterLifetimeEnd(CheckerManager &Mgr) {
Mgr.registerChecker<UseAfterLifetimeEnd>();
}
@@ -121,11 +61,3 @@ void ento::registerUseAfterLifetimeEnd(CheckerManager &Mgr) {
bool ento::shouldRegisterUseAfterLifetimeEnd(const CheckerManager &Mgr) {
return true;
}
-
-void ento::registerDebugUseAfterLifetimeEnd(CheckerManager &Mgr) {
- Mgr.registerChecker<DebugUseAfterLifetimeEnd>();
-}
-
-bool ento::shouldRegisterDebugUseAfterLifetimeEnd(const CheckerManager &Mgr) {
- return true;
-}
diff --git a/clang/test/Analysis/debug-lifetime-bound.cpp b/clang/test/Analysis/debug-lifetime-bound.cpp
index 8ef704195dcc6..b52d23c88071d 100644
--- a/clang/test/Analysis/debug-lifetime-bound.cpp
+++ b/clang/test/Analysis/debug-lifetime-bound.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugLifetimeModeling -verify %s
// expected-no-diagnostics
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 4b0dbb2fea3a0..f8519b76798fd 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -1,6 +1,6 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd \
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugLifetimeModeling \
// RUN: -analyzer-config cfg-lifetime=true -verify %s
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugUseAfterLifetimeEnd \
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugLifetimeModeling \
// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s
struct A {};
@@ -168,3 +168,52 @@ int *g() {
return nullptr; // no-warning
}
+int &multi_param_test_ref(int &a [[clang::lifetimebound]], int &b [[clang::lifetimebound]]);
+
+// Return value bound to annotated parameters (two dangling sources).
+int &dangling_sources_ref() {
+ int x = 1, y = 2;
+ return multi_param_test_ref(x, y);
+ // expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
+ // expected-warning at -2 {{Returning value bound to 'y' that will go out of scope}}
+ // expected-warning at -3 {{reference to stack memory associated with local variable 'x' returned}}
+ // expected-warning at -4 {{reference to stack memory associated with local variable 'y' returned}}
+}
+
+// Return value bound to annotated parameters (no dangling sources).
+int &no_dangling_sources_ref(int &a [[clang::lifetimebound]], int &b [[clang::lifetimebound]]) {
+ return multi_param_test_ref(a, b); // no-warning
+}
+
+// Return value bound to annotated parameters (one dangling source).
+int &one_dangling_source_ref(int &a [[clang::lifetimebound]]) {
+ int x = 1;
+ return multi_param_test_ref(a, x);
+ // expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
+ // expected-warning at -2 {{reference to stack memory associated with local variable 'x' returned}}
+}
+
+int *multi_param_test_ptr(int *a [[clang::lifetimebound]], int *b [[clang::lifetimebound]]);
+
+// Return value bound to annotated parameters (two dangling sources).
+int *dangling_sources_ptr() {
+ int x = 1, y = 2;
+ int *x_ptr = &x;
+ int *y_ptr = &y;
+ return multi_param_test_ptr(x_ptr, y_ptr);
+ // expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
+ // expected-warning at -2 {{Returning value bound to 'y' that will go out of scope}}
+}
+
+// Return value bound to annotated parameters (no dangling sources).
+int *no_dangling_sources_ptr(int *a [[clang::lifetimebound]], int *b [[clang::lifetimebound]]) {
+ return multi_param_test_ptr(a, b); // no-warning
+}
+
+// Return value bound to annotated parameters (one dangling source).
+int *one_dangling_source_ptr(int *a [[clang::lifetimebound]]) {
+ int x = 1;
+ int *x_ptr = &x;
+ return multi_param_test_ptr(a, x_ptr); // expected-warning {{Returning value bound to 'x' that will go out of scope}}
+}
+
>From 19f830a36915b3b82e43c015f7a78acd88c2dad3 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 30 Jun 2026 23:21:38 +0200
Subject: [PATCH 14/21] Commit Checkers.td to the branch.
---
clang/include/clang/StaticAnalyzer/Checkers/Checkers.td | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 7ae7734cf6a4c..41292d910cbe7 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -1587,10 +1587,10 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">,
HelpText<"Defines an empty checker callback for all possible handlers.">,
Documentation<NotDocumented>;
-def DebugUseAfterLifetimeEnd : Checker<"DebugUseAfterLifetimeEnd">,
- HelpText<"Prints the bindings recorded by the UseAfterLifetimeEnd checker. "
+def DebugLifetimeModeling : Checker<"DebugLifetimeModeling">,
+ HelpText<"Prints the bindings recorded by the LifetimeModeling checker. "
"Use with clang_analyzer_dumpLifetimeOriginsOf().">,
- WeakDependencies<[UseAfterLifetimeEnd]>,
+ Dependencies<[LifetimeModeling]>,
Documentation<NotDocumented>;
} // end "debug"
>From 8f48c7cfe943149f377998fd6f7e3919747da4f0 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Wed, 1 Jul 2026 20:21:14 +0200
Subject: [PATCH 15/21] Implemented BugReporterVisitor to better understand
where the lifetime of the object ended.
---
.../Checkers/LifetimeModeling.h | 4 +-
.../Checkers/LifetimeModeling.cpp | 5 ++
.../Checkers/UseAfterLifetimeEnd.cpp | 69 +++++++++++++++++--
clang/test/Analysis/lifetime-bound.cpp | 63 +++++++++++++----
4 files changed, 122 insertions(+), 19 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
index 38b8937ed6c43..724d9ec2e24be 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -7,14 +7,14 @@
#include <vector>
namespace clang::ento::lifetime_modeling {
-/// Returns true if the lifetime of a region has ended.
-bool isDeallocated(ProgramStateRef State, const MemRegion *Region);
/// Returns the set of lifetime sources bound to \p Source that are dangling
/// stack regions.
const std::vector<const MemRegion *>
checkReturnedBorrower(SVal Source, ProgramStateRef State, CheckerContext &C);
+/// Returns true if the SVal key is present in the map.
+bool isBoundToLifetimeSource(SVal Val, ProgramStateRef State);
} // namespace clang::ento::lifetime_modeling
#endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 6276d7b8960c1..ecf52e013af43 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -56,6 +56,11 @@ lifetime_modeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
return Regions;
}
+bool lifetime_modeling::isBoundToLifetimeSource(SVal Val,
+ ProgramStateRef State) {
+ return State->get<LifetimeBoundMap>(Val) != nullptr;
+}
+
static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
const MemRegion *Source) {
LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index 550e7f9949694..28c61fac6efb1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -1,5 +1,7 @@
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
using namespace clang;
@@ -8,7 +10,30 @@ using namespace ento;
namespace {
class UseAfterLifetimeEnd : public Checker<check::EndFunction> {
public:
- void reportDanglingSource(const MemRegion *Source, ExplodedNode *N,
+ class UseAfterLifetimeEndBRVisitor : public BugReporterVisitor {
+ SVal BoundRegion;
+ const MemRegion *SourceRegion;
+
+ public:
+ UseAfterLifetimeEndBRVisitor(SVal Region, const MemRegion *Source)
+ : BoundRegion(Region), SourceRegion(Source) {}
+
+ void Profile(llvm::FoldingSetNodeID &ID) const override {
+ static int X = 0;
+ ID.AddPointer(&X);
+ BoundRegion.Profile(ID);
+ SourceRegion->Profile(ID);
+ }
+
+ PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
+ BugReporterContext &BRC,
+ PathSensitiveBugReport &BR) override;
+ PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC,
+ const ExplodedNode *N,
+ PathSensitiveBugReport &BR) override;
+ };
+
+ void reportDanglingSource(const MemRegion *Source, SVal Val, ExplodedNode *N,
CheckerContext &C) const;
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
const BugType BugMsg{this, "UseAfterLifetimeEnd", "LifetimeBound"};
@@ -33,27 +58,63 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
std::vector<const MemRegion *> RetValRegion =
lifetime_modeling::checkReturnedBorrower(RetVal, State, C);
- for (const MemRegion *Region : RetValRegion) {
+ for (const auto *Region : RetValRegion) {
if (!N)
N = C.generateNonFatalErrorNode();
if (!N)
return;
- reportDanglingSource(Region, N, C);
+ reportDanglingSource(Region, RetVal, N, C);
}
}
void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Source,
- ExplodedNode *N,
+ SVal Val, ExplodedNode *N,
CheckerContext &C) const {
auto BR = std::make_unique<PathSensitiveBugReport>(
BugMsg,
(llvm::Twine("Returning value bound to '") + Source->getString() +
"' that will go out of scope"),
N);
+ BR->addVisitor(std::make_unique<UseAfterLifetimeEndBRVisitor>(Val, Source));
C.emitReport(std::move(BR));
}
+PathDiagnosticPieceRef
+UseAfterLifetimeEnd::UseAfterLifetimeEndBRVisitor::VisitNode(
+ const ExplodedNode *N, BugReporterContext &BRC,
+ PathSensitiveBugReport &BR) {
+ if (!lifetime_modeling::isBoundToLifetimeSource(BoundRegion, N->getState()) ||
+ lifetime_modeling::isBoundToLifetimeSource(BoundRegion,
+ N->getFirstPred()->getState()))
+ return nullptr;
+
+ const Stmt *S = N->getStmtForDiagnostics();
+ if (!S)
+ return nullptr;
+
+ SmallString<256> Str;
+ llvm::raw_svector_ostream OS(Str);
+ OS << "Value bound to '" << SourceRegion->getString() << "' here";
+ PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getStackFrame());
+ return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
+}
+
+PathDiagnosticPieceRef
+UseAfterLifetimeEnd::UseAfterLifetimeEndBRVisitor::getEndPath(
+ BugReporterContext &BRC, const ExplodedNode *N,
+ PathSensitiveBugReport &BR) {
+ const Stmt *S = N->getStmtForDiagnostics();
+ if (!S)
+ return nullptr;
+
+ SmallString<256> Str;
+ llvm::raw_svector_ostream OS(Str);
+ OS << "Lifetime of '" << SourceRegion->getString() << "' ended here";
+ PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getStackFrame());
+ return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
+}
+
void ento::registerUseAfterLifetimeEnd(CheckerManager &Mgr) {
Mgr.registerChecker<UseAfterLifetimeEnd>();
}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index f8519b76798fd..e16ec284887c8 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugLifetimeModeling \
-// RUN: -analyzer-config cfg-lifetime=true -verify %s
+// RUN: -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugLifetimeModeling \
-// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s
+// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s
struct A {};
@@ -21,7 +21,9 @@ void caller() {
int v = 0;
X obj;
int &r = obj.choose(v);
- clang_analyzer_dumpLifetimeOriginsOf(r); // expected-warning {{Origin &v bound to v}}
+ clang_analyzer_dumpLifetimeOriginsOf(r);
+ // expected-warning at -1 {{Origin &v bound to v}}
+ // expected-note at -2 {{Origin &v bound to v}}
}
// Obj ref type function return annotated case.
@@ -34,7 +36,9 @@ void caller_two() {
// Return statement is annotated case.
Y y;
A &f = y.getA();
- clang_analyzer_dumpLifetimeOriginsOf(f); // expected-warning {{Origin &y.a bound to y}}
+ clang_analyzer_dumpLifetimeOriginsOf(f);
+ // expected-warning at -1 {{Origin &y.a bound to y}}
+ // expected-note at -2 {{Origin &y.a bound to y}}
}
// Obj ptr type function return annotated case.
@@ -46,7 +50,9 @@ struct Z {
void caller_three() {
Z z;
A *func = z.getA();
- clang_analyzer_dumpLifetimeOriginsOf(func); // expected-warning {{Origin &z.a bound to z}}
+ clang_analyzer_dumpLifetimeOriginsOf(func);
+ // expected-warning at -1 {{Origin &z.a bound to z}}
+ // expected-note at -2 {{Origin &z.a bound to z}}
}
// Free function with annotated param and ref return.
@@ -55,7 +61,9 @@ int &foo(int &num [[clang::lifetimebound]]) { return num; }
void caller_four() {
int num = 5;
int &s = foo(num);
- clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &num bound to num}}
+ clang_analyzer_dumpLifetimeOriginsOf(s);
+ // expected-warning at -1 {{Origin &num bound to num}}
+ // expected-note at -2 {{Origin &num bound to num}}
}
// Free function with annotated param and ptr return.
@@ -66,7 +74,9 @@ void caller_five() {
int *n_ptr = &n;
int *s = boo(n_ptr);
- clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &n bound to n}}
+ clang_analyzer_dumpLifetimeOriginsOf(s);
+ // expected-warning at -1 {{Origin &n bound to n}}
+ // expected-note at -2 {{Origin &n bound to n}}
}
// Free function with both annotated and non-annotated parameters.
@@ -77,7 +87,9 @@ void caller_six() {
int odd = 55;
int &s = fn(even, odd);
- clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &odd bound to odd}}
+ clang_analyzer_dumpLifetimeOriginsOf(s);
+ // expected-warning at -1 {{Origin &odd bound to odd}}
+ // expected-note at -2 {{Origin &odd bound to odd}}
}
@@ -92,7 +104,9 @@ void caller_seven() {
int *y_ptr = &y;
auto *bind = foo(y_ptr);
- clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to y}}
+ clang_analyzer_dumpLifetimeOriginsOf(bind);
+ // expected-warning-re at -1 {{Origin &SymRegion{{.*}} bound to y}}
+ // expected-note-re at -2 {{Origin &SymRegion{{.*}} bound to y}}
}
// Function returns a reference and has an annotated parameter.
@@ -102,7 +116,9 @@ void caller_eight() {
int f = 15;
auto &bind = func(f);
- clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin &SymRegion{{.*}} bound to f}}
+ clang_analyzer_dumpLifetimeOriginsOf(bind);
+ // expected-warning-re at -1 {{Origin &SymRegion{{.*}} bound to f}}
+ // expected-note-re at -2 {{Origin &SymRegion{{.*}} bound to f}}
}
// Function returns a reference and has two annotated parameters.
@@ -113,7 +129,9 @@ void caller_nine() {
int second_num = 2;
int &numbers = f(first_num, second_num);
- clang_analyzer_dumpLifetimeOriginsOf(numbers); // expected-warning-re {{Origin &SymRegion{{.*}} bound to first_num, second_num}}
+ clang_analyzer_dumpLifetimeOriginsOf(numbers);
+ // expected-warning-re at -1 {{Origin &SymRegion{{.*}} bound to first_num, second_num}}
+ // expected-note-re at -2 {{Origin &SymRegion{{.*}} bound to first_num, second_num}}
}
struct View {
@@ -143,12 +161,17 @@ int *direct_return() {
return test_func(&i);
// expected-warning at -1 {{Returning value bound to 'i' that will go out of scope}}
// expected-warning at -2 {{address of stack memory associated with local variable 'i' returned}}
+ // expected-note at -3 {{Value bound to 'i' here}}
+ // expected-note at -4 {{Lifetime of 'i' ended here}}
}
int *variable_return() {
int y = 5;
int *p = test_func(&y);
- return p; // expected-warning {{Returning value bound to 'y' that will go out of scope}}
+ return p;
+ // expected-warning at -1 {{Returning value bound to 'y' that will go out of scope}}
+ // expected-note at -3 {{Value bound to 'y' here}}
+ // expected-note at -3 {{Lifetime of 'y' ended here}}
}
int *borrow_from_caller(int *b [[clang::lifetimebound]]) {
@@ -178,6 +201,10 @@ int &dangling_sources_ref() {
// expected-warning at -2 {{Returning value bound to 'y' that will go out of scope}}
// expected-warning at -3 {{reference to stack memory associated with local variable 'x' returned}}
// expected-warning at -4 {{reference to stack memory associated with local variable 'y' returned}}
+ // expected-note at -5 {{Value bound to 'x' here}}
+ // expected-note at -6 {{Lifetime of 'x' ended here}}
+ // expected-note at -7 {{Value bound to 'y' here}}
+ // expected-note at -8 {{Lifetime of 'y' ended here}}
}
// Return value bound to annotated parameters (no dangling sources).
@@ -191,6 +218,8 @@ int &one_dangling_source_ref(int &a [[clang::lifetimebound]]) {
return multi_param_test_ref(a, x);
// expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
// expected-warning at -2 {{reference to stack memory associated with local variable 'x' returned}}
+ // expected-note at -3 {{Value bound to 'x' here}}
+ // expected-note at -4 {{Lifetime of 'x' ended here}}
}
int *multi_param_test_ptr(int *a [[clang::lifetimebound]], int *b [[clang::lifetimebound]]);
@@ -203,6 +232,11 @@ int *dangling_sources_ptr() {
return multi_param_test_ptr(x_ptr, y_ptr);
// expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
// expected-warning at -2 {{Returning value bound to 'y' that will go out of scope}}
+ // expected-note at -3 {{Value bound to 'x' here}}
+ // expected-note at -4 {{Lifetime of 'x' ended here}}
+ // expected-note at -5 {{Value bound to 'y' here}}
+ // expected-note at -6 {{Lifetime of 'y' ended here}}
+
}
// Return value bound to annotated parameters (no dangling sources).
@@ -214,6 +248,9 @@ int *no_dangling_sources_ptr(int *a [[clang::lifetimebound]], int *b [[clang::li
int *one_dangling_source_ptr(int *a [[clang::lifetimebound]]) {
int x = 1;
int *x_ptr = &x;
- return multi_param_test_ptr(a, x_ptr); // expected-warning {{Returning value bound to 'x' that will go out of scope}}
+ return multi_param_test_ptr(a, x_ptr);
+ // expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
+ // expected-note at -2 {{Value bound to 'x' here}}
+ // expected-note at -3 {{Lifetime of 'x' ended here}}
}
>From 3c1d40995f83af002c34271a0aeec3bc8018336b Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Fri, 3 Jul 2026 08:57:45 +0200
Subject: [PATCH 16/21] Use llvm::Twine on PathDiagnosticEventPiece.
---
.../Checkers/UseAfterLifetimeEnd.cpp | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index 28c61fac6efb1..af731660a099d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -93,11 +93,12 @@ UseAfterLifetimeEnd::UseAfterLifetimeEndBRVisitor::VisitNode(
if (!S)
return nullptr;
- SmallString<256> Str;
- llvm::raw_svector_ostream OS(Str);
- OS << "Value bound to '" << SourceRegion->getString() << "' here";
PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getStackFrame());
- return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
+ return std::make_shared<PathDiagnosticEventPiece>(
+ Pos,
+ (llvm::Twine("Value bound to '") + SourceRegion->getString() + "' here")
+ .str(),
+ true);
}
PathDiagnosticPieceRef
@@ -108,11 +109,13 @@ UseAfterLifetimeEnd::UseAfterLifetimeEndBRVisitor::getEndPath(
if (!S)
return nullptr;
- SmallString<256> Str;
- llvm::raw_svector_ostream OS(Str);
- OS << "Lifetime of '" << SourceRegion->getString() << "' ended here";
PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getStackFrame());
- return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
+ return std::make_shared<PathDiagnosticEventPiece>(
+ Pos,
+ llvm::Twine(("Lifetime of '") + SourceRegion->getString() +
+ "' ended here")
+ .str(),
+ true);
}
void ento::registerUseAfterLifetimeEnd(CheckerManager &Mgr) {
>From ccf5ccf788cd2007ac7adf505871c8edab91db99 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Sat, 4 Jul 2026 00:36:01 +0200
Subject: [PATCH 17/21] Skip warning when bound source is in destructor.
---
.../Checkers/LifetimeModeling.cpp | 12 ++++++++++--
clang/test/Analysis/lifetime-bound.cpp | 18 ++++++++++++++++++
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index ecf52e013af43..4a95b11eba9f1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -26,7 +26,7 @@ class LifetimeModeling : public Checker<check::PostCall, check::DeadSymbols> {
} // namespace
-static bool getDanglingStackFrame(const MemRegion *Source,
+static bool isDanglingStackSource(const MemRegion *Source,
ProgramStateRef State, CheckerContext &C) {
// FIXME: The checker currently handles stack-region sources. Other
// region kinds require separate methodology. For example, heap
@@ -37,6 +37,14 @@ static bool getDanglingStackFrame(const MemRegion *Source,
Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
+
+ for (const StackFrame *DtorSF = CurrentSF; DtorSF;
+ DtorSF = DtorSF->getParent()) {
+ const auto *DDec = dyn_cast<CXXDestructorDecl>(DtorSF->getDecl());
+ if (DDec)
+ return false;
+ }
+
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
return true;
}
@@ -49,7 +57,7 @@ lifetime_modeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
std::vector<const MemRegion *> Regions;
if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet) {
- if (getDanglingStackFrame(Region, State, C))
+ if (isDanglingStackSource(Region, State, C))
Regions.push_back(Region);
}
}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index e16ec284887c8..385d23e7dc2a5 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -254,3 +254,21 @@ int *one_dangling_source_ptr(int *a [[clang::lifetimebound]]) {
// expected-note at -3 {{Lifetime of 'x' ended here}}
}
+// Testing destructors
+
+struct S {
+ int x;
+ int* get() [[clang::lifetimebound]] { return &x; }
+ S() = default;
+ S(S&& other) { (void)other.get(); }
+ ~S() { get(); }
+};
+
+S make() { return S(); }
+
+S passThrough(S param) { return param; }
+
+void outer() {
+ auto f = passThrough(make());
+ (void)f; // no-warning
+}
>From 62d2605021a3d6cde4191e652ab3b889650def19 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Sat, 4 Jul 2026 00:44:33 +0200
Subject: [PATCH 18/21] Revert "Skip warning when bound source is in
destructor."
This reverts commit ccf5ccf788cd2007ac7adf505871c8edab91db99.
---
.../Checkers/LifetimeModeling.cpp | 12 ++----------
clang/test/Analysis/lifetime-bound.cpp | 18 ------------------
2 files changed, 2 insertions(+), 28 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 4a95b11eba9f1..ecf52e013af43 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -26,7 +26,7 @@ class LifetimeModeling : public Checker<check::PostCall, check::DeadSymbols> {
} // namespace
-static bool isDanglingStackSource(const MemRegion *Source,
+static bool getDanglingStackFrame(const MemRegion *Source,
ProgramStateRef State, CheckerContext &C) {
// FIXME: The checker currently handles stack-region sources. Other
// region kinds require separate methodology. For example, heap
@@ -37,14 +37,6 @@ static bool isDanglingStackSource(const MemRegion *Source,
Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
-
- for (const StackFrame *DtorSF = CurrentSF; DtorSF;
- DtorSF = DtorSF->getParent()) {
- const auto *DDec = dyn_cast<CXXDestructorDecl>(DtorSF->getDecl());
- if (DDec)
- return false;
- }
-
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
return true;
}
@@ -57,7 +49,7 @@ lifetime_modeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
std::vector<const MemRegion *> Regions;
if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet) {
- if (isDanglingStackSource(Region, State, C))
+ if (getDanglingStackFrame(Region, State, C))
Regions.push_back(Region);
}
}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index 385d23e7dc2a5..e16ec284887c8 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -254,21 +254,3 @@ int *one_dangling_source_ptr(int *a [[clang::lifetimebound]]) {
// expected-note at -3 {{Lifetime of 'x' ended here}}
}
-// Testing destructors
-
-struct S {
- int x;
- int* get() [[clang::lifetimebound]] { return &x; }
- S() = default;
- S(S&& other) { (void)other.get(); }
- ~S() { get(); }
-};
-
-S make() { return S(); }
-
-S passThrough(S param) { return param; }
-
-void outer() {
- auto f = passThrough(make());
- (void)f; // no-warning
-}
>From d2aa48e98950f463cdc714b3309f200ae3167f3a Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Sat, 4 Jul 2026 00:47:56 +0200
Subject: [PATCH 19/21] Do not emit warning if the bound source is in the
destructor.
---
.../Checkers/LifetimeModeling.cpp | 12 ++++++++++--
clang/test/Analysis/lifetime-bound.cpp | 18 ++++++++++++++++++
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index ecf52e013af43..4a95b11eba9f1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -26,7 +26,7 @@ class LifetimeModeling : public Checker<check::PostCall, check::DeadSymbols> {
} // namespace
-static bool getDanglingStackFrame(const MemRegion *Source,
+static bool isDanglingStackSource(const MemRegion *Source,
ProgramStateRef State, CheckerContext &C) {
// FIXME: The checker currently handles stack-region sources. Other
// region kinds require separate methodology. For example, heap
@@ -37,6 +37,14 @@ static bool getDanglingStackFrame(const MemRegion *Source,
Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
+
+ for (const StackFrame *DtorSF = CurrentSF; DtorSF;
+ DtorSF = DtorSF->getParent()) {
+ const auto *DDec = dyn_cast<CXXDestructorDecl>(DtorSF->getDecl());
+ if (DDec)
+ return false;
+ }
+
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
return true;
}
@@ -49,7 +57,7 @@ lifetime_modeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
std::vector<const MemRegion *> Regions;
if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet) {
- if (getDanglingStackFrame(Region, State, C))
+ if (isDanglingStackSource(Region, State, C))
Regions.push_back(Region);
}
}
diff --git a/clang/test/Analysis/lifetime-bound.cpp b/clang/test/Analysis/lifetime-bound.cpp
index e16ec284887c8..385d23e7dc2a5 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -254,3 +254,21 @@ int *one_dangling_source_ptr(int *a [[clang::lifetimebound]]) {
// expected-note at -3 {{Lifetime of 'x' ended here}}
}
+// Testing destructors
+
+struct S {
+ int x;
+ int* get() [[clang::lifetimebound]] { return &x; }
+ S() = default;
+ S(S&& other) { (void)other.get(); }
+ ~S() { get(); }
+};
+
+S make() { return S(); }
+
+S passThrough(S param) { return param; }
+
+void outer() {
+ auto f = passThrough(make());
+ (void)f; // no-warning
+}
>From ead8980b19296dbb0b46b7d1b0bde4f6a7a0d5ce Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sat, 4 Jul 2026 12:38:22 +0200
Subject: [PATCH 20/21] Remove change from isDanglingStackSource.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 7 -------
1 file changed, 7 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 4a95b11eba9f1..97bc51922a6cc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -38,13 +38,6 @@ static bool isDanglingStackSource(const MemRegion *Source,
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
- for (const StackFrame *DtorSF = CurrentSF; DtorSF;
- DtorSF = DtorSF->getParent()) {
- const auto *DDec = dyn_cast<CXXDestructorDecl>(DtorSF->getDecl());
- if (DDec)
- return false;
- }
-
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
return true;
}
>From 3f15597782d3d3628e7f039408a45c0659b908e7 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sat, 4 Jul 2026 12:42:55 +0200
Subject: [PATCH 21/21] Logic implemented for not emitting warning when bound
source is in destructor.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 97bc51922a6cc..4a95b11eba9f1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -38,6 +38,13 @@ static bool isDanglingStackSource(const MemRegion *Source,
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
+ for (const StackFrame *DtorSF = CurrentSF; DtorSF;
+ DtorSF = DtorSF->getParent()) {
+ const auto *DDec = dyn_cast<CXXDestructorDecl>(DtorSF->getDecl());
+ if (DDec)
+ return false;
+ }
+
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
return true;
}
More information about the cfe-commits
mailing list