[clang] [analyzer] Implemented the LifetimeModeling checker (PR #205951)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 29 11:46:17 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 1/8] [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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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"
//===----------------------------------------------------------------------===//
More information about the cfe-commits
mailing list