[clang] [analyzer] Implement LifetimeModeling checker and refactor UseAfterLifetimeEnd (PR #205951)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 9 01:09:48 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/205951
>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/19] [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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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 d83c44c52db66f9fecb944379e782b0118c837d4 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Mon, 6 Jul 2026 23:32:08 +0200
Subject: [PATCH 15/19] [analyzer] Corrected names and removed const on return
type.
---
.../clang/StaticAnalyzer/Checkers/LifetimeModeling.h | 5 +++--
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 9 ++++-----
.../lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp | 2 +-
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
index 38b8937ed6c43..b3df0bbc08620 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -12,8 +12,9 @@ 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);
+std::vector<const MemRegion *>
+getDanglingRegionsAfterReturn(SVal Source, ProgramStateRef State,
+ CheckerContext &C);
} // namespace clang::ento::lifetime_modeling
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 6276d7b8960c1..85e3e1c678de4 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
@@ -43,13 +43,12 @@ static bool getDanglingStackFrame(const MemRegion *Source,
return false;
}
-const std::vector<const MemRegion *>
-lifetime_modeling::checkReturnedBorrower(SVal Val, ProgramStateRef State,
- CheckerContext &C) {
+std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn(
+ 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))
+ if (isDanglingStackSource(Region, State, C))
Regions.push_back(Region);
}
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index 550e7f9949694..fb190659ea356 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -32,7 +32,7 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
ExplodedNode *N = nullptr;
std::vector<const MemRegion *> RetValRegion =
- lifetime_modeling::checkReturnedBorrower(RetVal, State, C);
+ lifetime_modeling::getDanglingRegionsAfterReturn(RetVal, State, C);
for (const MemRegion *Region : RetValRegion) {
if (!N)
N = C.generateNonFatalErrorNode();
>From 3a162799157deab6e7a879bb33766cf61a294514 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 7 Jul 2026 17:29:07 +0200
Subject: [PATCH 16/19] [analyzer] Improved HelpText and applied nit changes
for the checkers.
---
.../clang/StaticAnalyzer/Checkers/Checkers.td | 5 ++-
.../Checkers/LifetimeModeling.cpp | 32 +++++++++----------
.../Checkers/LifetimeModeling.h | 9 ++----
.../Checkers/UseAfterLifetimeEnd.cpp | 2 +-
4 files changed, 21 insertions(+), 27 deletions(-)
rename clang/{include/clang => lib}/StaticAnalyzer/Checkers/LifetimeModeling.h (61%)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 41292d910cbe7..66d1cbee1dc3e 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -789,7 +789,7 @@ def SmartPtrChecker: Checker<"SmartPtr">,
Documentation<HasDocumentation>;
def LifetimeModeling : Checker<"LifetimeModeling">,
- HelpText<"Model [[clang::lifetimebound]] annotations for lifetime analysis">,
+ HelpText<"Model lifetime annotations for other checkers">,
Documentation<NotDocumented>,
Hidden;
@@ -1588,8 +1588,7 @@ def CheckerDocumentationChecker : Checker<"CheckerDocumentation">,
Documentation<NotDocumented>;
def DebugLifetimeModeling : Checker<"DebugLifetimeModeling">,
- HelpText<"Prints the bindings recorded by the LifetimeModeling checker. "
- "Use with clang_analyzer_dumpLifetimeOriginsOf().">,
+ HelpText<"Dump the set of regions the bound variable originates from">,
Dependencies<[LifetimeModeling]>,
Documentation<NotDocumented>;
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 85e3e1c678de4..da4f3d97d9c16 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -1,4 +1,4 @@
-#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
+#include "LifetimeModeling.h"
#include "clang/AST/Attr.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
@@ -55,7 +55,7 @@ std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn(
return Regions;
}
-static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
+static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
const MemRegion *Source) {
LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
@@ -85,15 +85,14 @@ void LifetimeModeling::checkPostCall(const CallEvent &Call,
unsigned Idx = PVD->getFunctionScopeIndex();
SVal Arg = Call.getArgSVal(Idx);
if (const MemRegion *ArgValRegion = Arg.getAsRegion())
- State = bindValues(State, RetVal, ArgValRegion);
+ State = bindSource(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);
- }
+ const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
+ if (IC && lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
+ if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion())
+ State = bindSource(State, RetVal, ThisRegion);
}
C.addTransition(State);
}
@@ -104,16 +103,15 @@ void LifetimeModeling::checkDeadSymbols(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);
- }
+ if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(R))
+ continue;
+
+ if (SymbolRef S = Val.getAsSymbol(/*IncludeBaseRegions=*/true);
+ S && SymReaper.isLive(S))
+ continue;
+
+ State = State->remove<LifetimeBoundMap>(Val);
}
- C.addTransition(State);
}
void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
similarity index 61%
rename from clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
rename to clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
index b3df0bbc08620..e13942eb1856b 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -1,5 +1,5 @@
-#ifndef LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
-#define LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
@@ -7,9 +7,6 @@
#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.
std::vector<const MemRegion *>
@@ -18,4 +15,4 @@ getDanglingRegionsAfterReturn(SVal Source, ProgramStateRef State,
} // namespace clang::ento::lifetime_modeling
-#endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
+#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index fb190659ea356..3b3c2f04887c8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -1,5 +1,5 @@
+#include "LifetimeModeling.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
-#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
using namespace clang;
>From a222956c912947df5ad6f4bcb5d16fd99231e9ad Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Tue, 7 Jul 2026 21:26:25 +0200
Subject: [PATCH 17/19] [analyzer] Implement is_and_nonnull and
getPredecessor().
---
.../StaticAnalyzer/Checkers/LifetimeModeling.cpp | 3 +--
.../StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp | 13 ++++++-------
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index da4f3d97d9c16..56006db8b8fb5 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -148,8 +148,7 @@ class DebugLifetimeModeling : public Checker<eval::Call> {
bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
CheckerContext &C) const {
- const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
- if (!CE)
+ if (!llvm::isa_and_nonnull<CallExpr>(Call.getOriginExpr()))
return false;
const FnCheck *Handler = Callbacks.lookup(Call);
diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index 3b3c2f04887c8..6a2933900c01f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -29,17 +29,16 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt *RS,
RetExpr = RetExpr->IgnoreParens();
SVal RetVal = C.getSVal(RetExpr);
- ExplodedNode *N = nullptr;
std::vector<const MemRegion *> RetValRegion =
lifetime_modeling::getDanglingRegionsAfterReturn(RetVal, State, C);
- for (const MemRegion *Region : RetValRegion) {
- if (!N)
- N = C.generateNonFatalErrorNode();
- if (!N)
- return;
+ if (RetValRegion.empty())
+ return;
- reportDanglingSource(Region, N, C);
+ if (ExplodedNode *N =
+ C.generateNonFatalErrorNode(State, C.getPredecessor())) {
+ for (const MemRegion *R : RetValRegion)
+ reportDanglingSource(R, N, C);
}
}
>From a4e190d4f2e0738ddae742b88b2e96e38acae008 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 9 Jul 2026 09:30:05 +0200
Subject: [PATCH 18/19] [analyzer] Added FIXME comment and removed namespace.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 56006db8b8fb5..087e0a27f013a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -128,6 +128,8 @@ void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
}
}
+// FIXME: Eventually move the debug checker to its own source file once
+// it has more functionality.
namespace {
class DebugLifetimeModeling : public Checker<eval::Call> {
public:
@@ -148,7 +150,7 @@ class DebugLifetimeModeling : public Checker<eval::Call> {
bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
CheckerContext &C) const {
- if (!llvm::isa_and_nonnull<CallExpr>(Call.getOriginExpr()))
+ if (isa_and_nonnull<CallExpr>(Call.getOriginExpr()))
return false;
const FnCheck *Handler = Callbacks.lookup(Call);
>From 33f51347af744776487ca37336d8f7b20a2befe8 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
Date: Thu, 9 Jul 2026 10:09:17 +0200
Subject: [PATCH 19/19] [analyzer] Fixed incorrect null check.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 087e0a27f013a..ac843e200daaa 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -150,7 +150,7 @@ class DebugLifetimeModeling : public Checker<eval::Call> {
bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
CheckerContext &C) const {
- if (isa_and_nonnull<CallExpr>(Call.getOriginExpr()))
+ if (!isa_and_nonnull<CallExpr>(Call.getOriginExpr()))
return false;
const FnCheck *Handler = Callbacks.lookup(Call);
More information about the cfe-commits
mailing list