[clang] [clang] Trigger checkLifetimeEnd callback from CFGLifetimeEnds element (PR #201123)
Arseniy Zaostrovnykh via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 2 07:52:20 PDT 2026
https://github.com/necto updated https://github.com/llvm/llvm-project/pull/201123
>From 6445e885e166190295650ce25276e028a68e737b Mon Sep 17 00:00:00 2001
From: tomasz-kaminski-sonarsource <tomasz.kaminski at sonarsource.com>
Date: Fri, 23 Jun 2023 14:17:47 +0200
Subject: [PATCH 01/11] [clang] Trigger checkLifetimeEnd callback from
CFGLifetimeEnds element
This patch adds handling of the `CFGLifetimeEnd` element to the CSA, and
produces a newly created callback `checkLifetimeEnd` for each occurrence
of it.
It is useful to implement detection of dangling pointers as in:
```
void su_use_after_block () { int* p=0; { int x=1; p=&x; } *p = 2; }
// ^ p dangles
```
This patch does not implement the check itself. it is motivated by the
discussion in
https://discourse.llvm.org/t/what-is-the-status-of-scopeend-and-scopebegin/90861
--
upstreamed from CPP-4539
---
clang/include/clang/Analysis/CFG.h | 75 ++++++++-------
clang/include/clang/Analysis/ProgramPoint.h | 80 ++++++++++------
.../clang/StaticAnalyzer/Core/Checker.h | 14 +++
.../StaticAnalyzer/Core/CheckerManager.h | 13 +++
.../Core/PathSensitive/ExprEngine.h | 1 +
clang/lib/Analysis/PathDiagnostic.cpp | 3 +
clang/lib/Analysis/ProgramPoint.cpp | 5 +
.../StaticAnalyzer/Core/CheckerManager.cpp | 55 +++++++++--
clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 18 ++--
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 16 ++++
clang/unittests/StaticAnalyzer/CMakeLists.txt | 1 +
.../StaticAnalyzer/CheckLifetimeEndTest.cpp | 93 +++++++++++++++++++
12 files changed, 302 insertions(+), 72 deletions(-)
create mode 100644 clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h
index 6c214d9ce10e2..57ebfa47ec90e 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -57,12 +57,15 @@ class CFGElement {
enum Kind {
// main kind
Initializer,
- ScopeBegin,
- ScopeEnd,
NewAllocator,
- LifetimeEnds,
LoopExit,
FullExprCleanup,
+ // scope marker kind
+ ScopeBegin,
+ ScopeEnd,
+ LifetimeEnds,
+ SCOPE_BEGIN = ScopeBegin,
+ SCOPE_END = LifetimeEnds,
// stmt kind
Statement,
Constructor,
@@ -290,18 +293,38 @@ class CFGLoopExit : public CFGElement {
}
};
+/// Base class for representing elements related to the lifetime of automatic
+/// objects
+class CFGScopeMarker : public CFGElement {
+public:
+ LLVM_ATTRIBUTE_RETURNS_NONNULL const Stmt *getTriggerStmt() const {
+ return static_cast<const Stmt *>(Data1.getPointer());
+ }
+
+private:
+ friend class CFGElement;
+
+ static bool isKind(const CFGElement &E) {
+ return E.getKind() >= SCOPE_BEGIN && E.getKind() <= SCOPE_END;
+ }
+
+protected:
+ CFGScopeMarker() = default;
+
+ explicit CFGScopeMarker(Kind K, const Stmt *S, const void *Ptr2 = nullptr)
+ : CFGElement(K, S, Ptr2) {
+ assert(isKind(*this));
+ }
+};
+
/// Represents the point where the lifetime of an automatic object ends
-class CFGLifetimeEnds : public CFGElement {
+class CFGLifetimeEnds : public CFGScopeMarker {
public:
explicit CFGLifetimeEnds(const VarDecl *var, const Stmt *stmt)
- : CFGElement(LifetimeEnds, var, stmt) {}
+ : CFGScopeMarker(LifetimeEnds, stmt, var) {}
const VarDecl *getVarDecl() const {
- return static_cast<const VarDecl *>(Data1.getPointer());
- }
-
- const Stmt *getTriggerStmt() const {
- return static_cast<const Stmt *>(Data2.getPointer());
+ return static_cast<const VarDecl *>(Data2.getPointer());
}
private:
@@ -349,50 +372,40 @@ class CFGFullExprCleanup : public CFGElement {
/// Represents beginning of a scope implicitly generated
/// by the compiler on encountering a CompoundStmt
-class CFGScopeBegin : public CFGElement {
+class CFGScopeBegin : public CFGScopeMarker {
public:
CFGScopeBegin() {}
CFGScopeBegin(const VarDecl *VD, const Stmt *S)
- : CFGElement(ScopeBegin, VD, S) {}
-
- // Get statement that triggered a new scope.
- const Stmt *getTriggerStmt() const {
- return static_cast<const Stmt *>(Data2.getPointer());
- }
+ : CFGScopeMarker(ScopeBegin, S, VD) {}
// Get VD that triggered a new scope.
const VarDecl *getVarDecl() const {
- return static_cast<const VarDecl *>(Data1.getPointer());
+ return static_cast<const VarDecl *>(Data2.getPointer());
}
private:
friend class CFGElement;
- static bool isKind(const CFGElement &E) {
- Kind kind = E.getKind();
- return kind == ScopeBegin;
+ static bool isKind(const CFGElement &elem) {
+ return elem.getKind() == ScopeBegin;
}
};
/// Represents end of a scope implicitly generated by
/// the compiler after the last Stmt in a CompoundStmt's body
-class CFGScopeEnd : public CFGElement {
+class CFGScopeEnd : public CFGScopeMarker {
public:
CFGScopeEnd() {}
- CFGScopeEnd(const VarDecl *VD, const Stmt *S) : CFGElement(ScopeEnd, VD, S) {}
+ CFGScopeEnd(const VarDecl *VD, const Stmt *S)
+ : CFGScopeMarker(ScopeEnd, S, VD) {}
const VarDecl *getVarDecl() const {
- return static_cast<const VarDecl *>(Data1.getPointer());
- }
-
- const Stmt *getTriggerStmt() const {
- return static_cast<const Stmt *>(Data2.getPointer());
+ return static_cast<const VarDecl *>(Data2.getPointer());
}
private:
friend class CFGElement;
- static bool isKind(const CFGElement &E) {
- Kind kind = E.getKind();
- return kind == ScopeEnd;
+ static bool isKind(const CFGElement &elem) {
+ return elem.getKind() == ScopeEnd;
}
};
diff --git a/clang/include/clang/Analysis/ProgramPoint.h b/clang/include/clang/Analysis/ProgramPoint.h
index b0fedea2fd8ee..51ae43a334f1f 100644
--- a/clang/include/clang/Analysis/ProgramPoint.h
+++ b/clang/include/clang/Analysis/ProgramPoint.h
@@ -59,33 +59,36 @@ class SimpleProgramPointTag : public ProgramPointTag {
class ProgramPoint {
public:
- enum Kind { BlockEdgeKind,
- BlockEntranceKind,
- BlockExitKind,
- PreStmtKind,
- PreStmtPurgeDeadSymbolsKind,
- PostStmtPurgeDeadSymbolsKind,
- PostStmtKind,
- PreLoadKind,
- PostLoadKind,
- PreStoreKind,
- PostStoreKind,
- PostConditionKind,
- PostLValueKind,
- PostAllocatorCallKind,
- MinPostStmtKind = PostStmtKind,
- MaxPostStmtKind = PostAllocatorCallKind,
- PostInitializerKind,
- CallEnterKind,
- CallExitBeginKind,
- CallExitEndKind,
- FunctionExitKind,
- PreImplicitCallKind,
- PostImplicitCallKind,
- MinImplicitCallKind = PreImplicitCallKind,
- MaxImplicitCallKind = PostImplicitCallKind,
- LoopExitKind,
- EpsilonKind};
+ enum Kind {
+ BlockEdgeKind,
+ BlockEntranceKind,
+ BlockExitKind,
+ PreStmtKind,
+ PreStmtPurgeDeadSymbolsKind,
+ PostStmtPurgeDeadSymbolsKind,
+ PostStmtKind,
+ PreLoadKind,
+ PostLoadKind,
+ PreStoreKind,
+ PostStoreKind,
+ PostConditionKind,
+ PostLValueKind,
+ PostAllocatorCallKind,
+ MinPostStmtKind = PostStmtKind,
+ MaxPostStmtKind = PostAllocatorCallKind,
+ PostInitializerKind,
+ CallEnterKind,
+ CallExitBeginKind,
+ CallExitEndKind,
+ FunctionExitKind,
+ PreImplicitCallKind,
+ PostImplicitCallKind,
+ MinImplicitCallKind = PreImplicitCallKind,
+ MaxImplicitCallKind = PostImplicitCallKind,
+ LoopExitKind,
+ LifetimeEndKind,
+ EpsilonKind
+ };
static StringRef getProgramPointKindName(Kind K);
std::optional<SourceLocation> getSourceLocation() const;
@@ -725,6 +728,29 @@ class LoopExit : public ProgramPoint {
}
};
+/// Represents a point when the lifetime ends of any automatic object.
+class LifetimeEnd : public ProgramPoint {
+public:
+ LifetimeEnd(const Stmt *S, const VarDecl *D, const StackFrame *SF)
+ : ProgramPoint(S, D, LifetimeEndKind, SF) {}
+
+ LLVM_ATTRIBUTE_RETURNS_NONNULL const Stmt *getTriggerStmt() const {
+ return static_cast<const Stmt *>(getData1());
+ }
+
+ /// Returns a variable declaration that uniquely identifies the scope
+ LLVM_ATTRIBUTE_RETURNS_NONNULL const VarDecl *getDecl() const {
+ return static_cast<const VarDecl *>(getData2());
+ }
+
+private:
+ friend class ProgramPoint;
+ LifetimeEnd() = default;
+ static bool isKind(const ProgramPoint &Location) {
+ return Location.getKind() == LifetimeEndKind;
+ }
+};
+
/// This is a meta program point, which should be skipped by all the diagnostic
/// reasoning etc.
class EpsilonPoint : public ProgramPoint {
diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h
index ab38a05bfd79d..78772b12d4709 100644
--- a/clang/include/clang/StaticAnalyzer/Core/Checker.h
+++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h
@@ -206,6 +206,20 @@ class Location {
}
};
+class LifetimeEnd {
+ template <typename CHECKER>
+ static void _checkCall(void *checker, const VarDecl *D, CheckerContext &C) {
+ ((const CHECKER *)checker)->checkLifetimeEnd(D, C);
+ }
+
+public:
+ template <typename CHECKER>
+ static void _register(CHECKER *checker, CheckerManager &mgr) {
+ mgr._registerForLifetimeEnd(
+ CheckerManager::CheckLifetimeEndFunc(checker, _checkCall<CHECKER>));
+ }
+};
+
class Bind {
template <typename CHECKER>
static void _checkBind(void *checker, SVal location, SVal val, const Stmt *S,
diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
index 9d352960c1db7..e1cd01ef13f2d 100644
--- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
+++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
@@ -330,6 +330,12 @@ class CheckerManager {
const CallEvent &Call, ExprEngine &Eng,
bool wasInlined = false);
+ /// Run checkers for end of variable lifetime
+ void runCheckersForLifetimeEnd(ExplodedNodeSet &Dst,
+ const ExplodedNodeSet &Src,
+ const VarDecl *Decl, const Stmt *TriggerStmt,
+ ExprEngine &Eng);
+
/// Run checkers for load/store of a location.
void runCheckersForLocation(ExplodedNodeSet &Dst,
const ExplodedNodeSet &Src,
@@ -493,6 +499,9 @@ class CheckerManager {
using CheckCallFunc =
CheckerFn<void (const CallEvent &, CheckerContext &)>;
+ using CheckLifetimeEndFunc =
+ CheckerFn<void(const VarDecl *, CheckerContext &)>;
+
using CheckLocationFunc = CheckerFn<void(SVal location, bool isLoad,
const Stmt *S, CheckerContext &)>;
@@ -557,6 +566,8 @@ class CheckerManager {
void _registerForPreCall(CheckCallFunc checkfn);
void _registerForPostCall(CheckCallFunc checkfn);
+ void _registerForLifetimeEnd(CheckLifetimeEndFunc checkfn);
+
void _registerForLocation(CheckLocationFunc checkfn);
void _registerForBind(CheckBindFunc checkfn);
@@ -665,6 +676,8 @@ class CheckerManager {
std::vector<CheckCallFunc> PreCallCheckers;
std::vector<CheckCallFunc> PostCallCheckers;
+ std::vector<CheckLifetimeEndFunc> LifetimeEndCheckers;
+
std::vector<CheckLocationFunc> LocationCheckers;
std::vector<CheckBindFunc> BindCheckers;
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index e4ddabbe9c927..bb2de45cec92a 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -368,6 +368,7 @@ class ExprEngine {
void ProcessStmt(const Stmt *S, ExplodedNode *Pred);
void ProcessLoopExit(const Stmt* S, ExplodedNode *Pred);
+ void ProcessLifetimeEnd(const Stmt *S, const VarDecl *D, ExplodedNode *Pred);
void ProcessInitializer(const CFGInitializer I, ExplodedNode *Pred);
diff --git a/clang/lib/Analysis/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp
index 24fed40a93ed1..a95ef4582be99 100644
--- a/clang/lib/Analysis/PathDiagnostic.cpp
+++ b/clang/lib/Analysis/PathDiagnostic.cpp
@@ -720,6 +720,9 @@ PathDiagnosticLocation::create(const ProgramPoint& P,
} else if (std::optional<FunctionExitPoint> FE =
P.getAs<FunctionExitPoint>()) {
return PathDiagnosticLocation(FE->getStmt(), SMng, FE->getStackFrame());
+ } else if (std::optional<LifetimeEnd> LE = P.getAs<LifetimeEnd>()) {
+ return PathDiagnosticLocation::createEnd(LE->getTriggerStmt(), SMng,
+ LE->getStackFrame());
} else {
llvm_unreachable("Unexpected ProgramPoint");
}
diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp
index 11c8c8242eb19..697e0afd30ed2 100644
--- a/clang/lib/Analysis/ProgramPoint.cpp
+++ b/clang/lib/Analysis/ProgramPoint.cpp
@@ -217,6 +217,11 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const {
<< castAs<LoopExit>().getLoopStmt()->getStmtClassName() << '\"';
break;
+ case ProgramPoint::LifetimeEndKind:
+ Out << "LifetimeEnd\", \"var\": \""
+ << castAs<LifetimeEnd>().getDecl()->getNameAsString() << '\"';
+ break;
+
case ProgramPoint::PreImplicitCallKind: {
ImplicitCallPoint PC = castAs<ImplicitCallPoint>();
Out << "PreCall\", \"decl\": \""
diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
index 35603e333bf17..138d4bc8d95ad 100644
--- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
@@ -35,17 +35,18 @@ using namespace clang;
using namespace ento;
bool CheckerManager::hasPathSensitiveCheckers() const {
- const auto IfAnyAreNonEmpty = [](const auto &... Callbacks) -> bool {
+ const auto IfAnyAreNonEmpty = [](const auto &...Callbacks) -> bool {
return (!Callbacks.empty() || ...);
};
return IfAnyAreNonEmpty(
StmtCheckers, PreObjCMessageCheckers, ObjCMessageNilCheckers,
PostObjCMessageCheckers, PreCallCheckers, PostCallCheckers,
- LocationCheckers, BindCheckers, BlockEntranceCheckers,
- EndAnalysisCheckers, BeginFunctionCheckers, EndFunctionCheckers,
- BranchConditionCheckers, NewAllocatorCheckers, LiveSymbolsCheckers,
- DeadSymbolsCheckers, RegionChangesCheckers, PointerEscapeCheckers,
- EvalAssumeCheckers, EvalCallCheckers, EndOfTranslationUnitCheckers);
+ LifetimeEndCheckers, LocationCheckers, BindCheckers,
+ BlockEntranceCheckers, EndAnalysisCheckers, BeginFunctionCheckers,
+ EndFunctionCheckers, BranchConditionCheckers, NewAllocatorCheckers,
+ LiveSymbolsCheckers, DeadSymbolsCheckers, RegionChangesCheckers,
+ PointerEscapeCheckers, EvalAssumeCheckers, EvalCallCheckers,
+ EndOfTranslationUnitCheckers);
}
void CheckerManager::reportInvalidCheckerOptionValue(
@@ -311,6 +312,44 @@ void CheckerManager::runCheckersForCallEvent(bool isPreVisit,
expandGraphWithCheckers(C, Dst, Src);
}
+namespace {
+
+struct CheckLifetimeEndContext {
+ using CheckersTy = std::vector<CheckerManager::CheckLifetimeEndFunc>;
+
+ const CheckersTy &Checkers;
+ const VarDecl *Decl;
+ const Stmt *TriggerStmt;
+ ExprEngine &Eng;
+
+ CheckLifetimeEndContext(const CheckersTy &checkers, const VarDecl *decl,
+ const Stmt *stmt, ExprEngine &eng)
+ : Checkers(checkers), Decl(decl), TriggerStmt(stmt), Eng(eng) {}
+
+ CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); }
+ CheckersTy::const_iterator checkers_end() { return Checkers.end(); }
+
+ void runChecker(CheckerManager::CheckLifetimeEndFunc checkFn,
+ NodeBuilder &Bldr, ExplodedNode *Pred) {
+ assert(Pred->getLocation().getAs<LifetimeEnd>().has_value());
+ const ProgramPoint L = Pred->getLocation().withTag(checkFn.Checker);
+ CheckerContext C(Bldr, Eng, Pred, L);
+ checkFn(Decl, C);
+ }
+};
+
+} // namespace
+
+/// Run checkers for end of variable lifetime
+void CheckerManager::runCheckersForLifetimeEnd(ExplodedNodeSet &Dst,
+ const ExplodedNodeSet &Src,
+ const VarDecl *Decl,
+ const Stmt *TriggerStmt,
+ ExprEngine &Eng) {
+ CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, TriggerStmt, Eng);
+ expandGraphWithCheckers(C, Dst, Src);
+}
+
namespace {
struct CheckLocationContext {
@@ -903,6 +942,10 @@ void CheckerManager::_registerForPostCall(CheckCallFunc checkfn) {
PostCallCheckers.push_back(checkfn);
}
+void CheckerManager::_registerForLifetimeEnd(CheckLifetimeEndFunc checkfn) {
+ LifetimeEndCheckers.push_back(checkfn);
+}
+
void CheckerManager::_registerForLocation(CheckLocationFunc checkfn) {
LocationCheckers.push_back(checkfn);
}
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index 2a64e340ed214..b38dfdfa6f2c4 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -254,11 +254,9 @@ void CoreEngine::dispatchWorkItem(ExplodedNode *Pred, ProgramPoint Loc,
break;
}
default:
- assert(Loc.getAs<PostStmt>() ||
- Loc.getAs<PostInitializer>() ||
- Loc.getAs<PostImplicitCall>() ||
- Loc.getAs<CallExitEnd>() ||
- Loc.getAs<LoopExit>() ||
+ assert(Loc.getAs<PostStmt>() || Loc.getAs<PostInitializer>() ||
+ Loc.getAs<PostImplicitCall>() || Loc.getAs<CallExitEnd>() ||
+ Loc.getAs<LoopExit>() || Loc.getAs<LifetimeEnd>() ||
Loc.getAs<PostAllocatorCall>());
HandlePostStmt(WU.getBlock(), WU.getIndex(), Pred);
break;
@@ -306,6 +304,9 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) {
} else if (std::optional<CFGAutomaticObjDtor> AutoDtor =
LastElement.getAs<CFGAutomaticObjDtor>()) {
RS = dyn_cast<ReturnStmt>(AutoDtor->getTriggerStmt());
+ } else if (std::optional<CFGScopeMarker> ScopeMarker =
+ LastElement.getAs<CFGScopeMarker>()) {
+ RS = dyn_cast<ReturnStmt>(ScopeMarker->getTriggerStmt());
}
}
@@ -591,9 +592,10 @@ void CoreEngine::enqueueStmtNode(ExplodedNode *N,
// Do not create extra nodes. Move to the next CFG element.
if (N->getLocation().getAs<PostInitializer>() ||
- N->getLocation().getAs<PostImplicitCall>()||
- N->getLocation().getAs<LoopExit>()) {
- WList->enqueue(N, Block, Idx+1);
+ N->getLocation().getAs<PostImplicitCall>() ||
+ N->getLocation().getAs<LoopExit>() ||
+ N->getLocation().getAs<LifetimeEnd>()) {
+ WList->enqueue(N, Block, Idx + 1);
return;
}
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 34ca88705cba6..21dff1961a76d 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -977,6 +977,9 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred,
ProcessLoopExit(E.castAs<CFGLoopExit>().getLoopStmt(), Pred);
return;
case CFGElement::LifetimeEnds:
+ ProcessLifetimeEnd(E.castAs<CFGLifetimeEnds>().getTriggerStmt(),
+ E.castAs<CFGLifetimeEnds>().getVarDecl(), Pred);
+ return;
case CFGElement::CleanupFunction:
case CFGElement::FullExprCleanup:
case CFGElement::ScopeBegin:
@@ -1140,6 +1143,19 @@ void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) {
Engine.enqueueStmtNode(N, getCurrBlock(), currStmtIdx);
}
+void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D,
+ ExplodedNode *Pred) {
+ ExplodedNodeSet Src;
+ NodeBuilder Bldr(Pred, Src, *currBldrCtx);
+ LifetimeEnd PP(S, D, Pred->getStackFrame());
+ Bldr.generateNode(PP, Pred->getState(), Pred);
+
+ ExplodedNodeSet Dst;
+ getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, S, *this);
+ Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx);
+ return;
+}
+
void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
ExplodedNode *Pred) {
const CXXCtorInitializer *BMI = CFGInit.getInitializer();
diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt
index ed16a1372bea2..943850e49b0b5 100644
--- a/clang/unittests/StaticAnalyzer/CMakeLists.txt
+++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt
@@ -6,6 +6,7 @@ add_clang_unittest(StaticAnalysisTests
BugReportInterestingnessTest.cpp
CallDescriptionTest.cpp
CallEventTest.cpp
+ CheckLifetimeEndTest.cpp
ConflictingEvalCallsTest.cpp
ExprEngineVisitTest.cpp
FalsePositiveRefutationBRVisitorTest.cpp
diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
new file mode 100644
index 0000000000000..9b3dd4bae7d9a
--- /dev/null
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -0,0 +1,93 @@
+//===- unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp --===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CheckerRegistration.h"
+#include "Reusables.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.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 "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
+#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
+#include "llvm/Config/llvm-config.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace ento {
+namespace {
+
+class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
+ using Self = LifetimeEndReporter;
+ const BugType LifetimeEndNode{this, "LifetimeEndReporter"};
+
+ bool report(CheckerContext &C, StringRef Description) const {
+ ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState());
+ if (!Node)
+ return false;
+
+ auto Report = std::make_unique<PathSensitiveBugReport>(LifetimeEndNode,
+ Description, Node);
+ C.emitReport(std::move(Report));
+ return true;
+ }
+
+public:
+ void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const {
+ if (auto II = D->getIdentifier())
+ report(C, (II->getName() + " LIFETIME END").str());
+ }
+};
+
+void addLifetimeEndReporter(AnalysisASTConsumer &AnalysisConsumer,
+ AnalyzerOptions &AnOpts) {
+ AnOpts.CheckersAndPackages = {
+ {"test.LifetimeEndReporter", true},
+ };
+ AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {
+ Registry.addChecker<LifetimeEndReporter>(
+ "test.LifetimeEndReporter", "EmptyDescription", "EmptyDocsUri");
+ });
+}
+
+const std::vector<std::string> DisableLifetimeArgs{
+ "-Xclang", "-analyzer-config", "-Xclang", "cfg-lifetime=false"};
+const std::vector<std::string> EnableLifetimeArgs{
+ "-Xclang", "-analyzer-config", "-Xclang", "cfg-lifetime=true"};
+
+TEST(CheckLifetimeEnd, CFGLifetimeEnabled) {
+ constexpr auto Code = R"(
+void foo() {
+ int i = 0;
+}
+ )";
+
+ std::string Diags;
+ EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+ Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+ EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n");
+}
+
+TEST(CheckLifetimeEnd, CFGLifetimeDisabled) {
+ constexpr auto Code = R"(
+void foo() {
+ int i = 0;
+}
+ )";
+
+ std::string Diags;
+ EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+ Code, DisableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+ EXPECT_TRUE(Diags.empty());
+}
+
+} // namespace
+} // namespace ento
+} // namespace clang
>From c7f7b3cd8f25cdab81aeaf9dc676a5b091d0c11a Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:10:11 +0200
Subject: [PATCH 02/11] Handle the new enumeration value
---
clang/lib/Analysis/ProgramPoint.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp
index 697e0afd30ed2..bca2581d923b6 100644
--- a/clang/lib/Analysis/ProgramPoint.cpp
+++ b/clang/lib/Analysis/ProgramPoint.cpp
@@ -95,6 +95,8 @@ StringRef ProgramPoint::getProgramPointKindName(Kind K) {
return "PostImplicitCall";
case LoopExitKind:
return "LoopExit";
+ case LifetimeEndKind:
+ return "LifetimeEnd";
case EpsilonKind:
return "Epsilon";
}
@@ -158,6 +160,10 @@ std::optional<SourceLocation> ProgramPoint::getSourceLocation() const {
if (const Stmt *S = castAs<LoopExit>().getLoopStmt())
return S->getBeginLoc();
return std::nullopt;
+ case LifetimeEndKind:
+ if (const Stmt *S = castAs<LifetimeEnd>().getTriggerStmt())
+ return S->getBeginLoc();
+ return std::nullopt;
case EpsilonKind:
return std::nullopt;
}
>From df5f26b566b64f26db01f861de8fbe5b5aa0dc96 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:17:34 +0200
Subject: [PATCH 03/11] [NFC] Remove unused trigger statement plumbing
---
clang/include/clang/StaticAnalyzer/Core/CheckerManager.h | 3 +--
clang/lib/StaticAnalyzer/Core/CheckerManager.cpp | 8 +++-----
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 2 +-
3 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
index e1cd01ef13f2d..11de0ab86eb11 100644
--- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
+++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
@@ -333,8 +333,7 @@ class CheckerManager {
/// Run checkers for end of variable lifetime
void runCheckersForLifetimeEnd(ExplodedNodeSet &Dst,
const ExplodedNodeSet &Src,
- const VarDecl *Decl, const Stmt *TriggerStmt,
- ExprEngine &Eng);
+ const VarDecl *Decl, ExprEngine &Eng);
/// Run checkers for load/store of a location.
void runCheckersForLocation(ExplodedNodeSet &Dst,
diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
index 138d4bc8d95ad..8487c89ccffa8 100644
--- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
@@ -319,12 +319,11 @@ struct CheckLifetimeEndContext {
const CheckersTy &Checkers;
const VarDecl *Decl;
- const Stmt *TriggerStmt;
ExprEngine &Eng;
CheckLifetimeEndContext(const CheckersTy &checkers, const VarDecl *decl,
- const Stmt *stmt, ExprEngine &eng)
- : Checkers(checkers), Decl(decl), TriggerStmt(stmt), Eng(eng) {}
+ ExprEngine &eng)
+ : Checkers(checkers), Decl(decl), Eng(eng) {}
CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); }
CheckersTy::const_iterator checkers_end() { return Checkers.end(); }
@@ -344,9 +343,8 @@ struct CheckLifetimeEndContext {
void CheckerManager::runCheckersForLifetimeEnd(ExplodedNodeSet &Dst,
const ExplodedNodeSet &Src,
const VarDecl *Decl,
- const Stmt *TriggerStmt,
ExprEngine &Eng) {
- CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, TriggerStmt, Eng);
+ CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, Eng);
expandGraphWithCheckers(C, Dst, Src);
}
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 21dff1961a76d..aa4bd6aec75b3 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1151,7 +1151,7 @@ void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D,
Bldr.generateNode(PP, Pred->getState(), Pred);
ExplodedNodeSet Dst;
- getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, S, *this);
+ getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, *this);
Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx);
return;
}
>From 5efa9eacafecc75c09b42141fae27a640e41de48 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:23:53 +0200
Subject: [PATCH 04/11] [NFC] Use more appropriate name for the internal
private function proxy
---
clang/include/clang/StaticAnalyzer/Core/Checker.h | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h
index 78772b12d4709..249d9c512f7a9 100644
--- a/clang/include/clang/StaticAnalyzer/Core/Checker.h
+++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h
@@ -208,15 +208,16 @@ class Location {
class LifetimeEnd {
template <typename CHECKER>
- static void _checkCall(void *checker, const VarDecl *D, CheckerContext &C) {
+ static void _checkLifetimeEnd(void *checker, const VarDecl *D,
+ CheckerContext &C) {
((const CHECKER *)checker)->checkLifetimeEnd(D, C);
}
public:
template <typename CHECKER>
static void _register(CHECKER *checker, CheckerManager &mgr) {
- mgr._registerForLifetimeEnd(
- CheckerManager::CheckLifetimeEndFunc(checker, _checkCall<CHECKER>));
+ mgr._registerForLifetimeEnd(CheckerManager::CheckLifetimeEndFunc(
+ checker, _checkLifetimeEnd<CHECKER>));
}
};
>From 6b188744ba8150277552cb7055ab902b402fee69 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:35:25 +0200
Subject: [PATCH 05/11] [NFC] Remove pointless return statement
---
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index aa4bd6aec75b3..d6676969fbc52 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1153,7 +1153,6 @@ void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D,
ExplodedNodeSet Dst;
getCheckerManager().runCheckersForLifetimeEnd(Dst, Src, D, *this);
Engine.enqueueStmtNodes(Dst, currBldrCtx->getBlock(), currStmtIdx);
- return;
}
void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
>From 368ae051cca220119e4895cdc582011b908bfbb3 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:41:10 +0200
Subject: [PATCH 06/11] [NFC] Avoid placing unit tests into clang::ento
namespace
---
clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 9b3dd4bae7d9a..cdd5ed9a3e801 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -20,9 +20,9 @@
#include "llvm/Config/llvm-config.h"
#include "gtest/gtest.h"
-namespace clang {
-namespace ento {
namespace {
+using namespace clang;
+using namespace ento;
class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
using Self = LifetimeEndReporter;
@@ -89,5 +89,3 @@ void foo() {
}
} // namespace
-} // namespace ento
-} // namespace clang
>From 7247c5979e1d5f6606aedd7acce1628792efa8c4 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:42:09 +0200
Subject: [PATCH 07/11] [NFC] Remove unused type alias
---
clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index cdd5ed9a3e801..8a683039a50c0 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -25,7 +25,6 @@ using namespace clang;
using namespace ento;
class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
- using Self = LifetimeEndReporter;
const BugType LifetimeEndNode{this, "LifetimeEndReporter"};
bool report(CheckerContext &C, StringRef Description) const {
>From 46b6c0dc3d1bc730d7a31cdec922c54d8acbd54a Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:44:46 +0200
Subject: [PATCH 08/11] [NFC] Postpone Twine materialization to PSBR
constructor call
---
clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 8a683039a50c0..7e245a70de44c 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -27,13 +27,13 @@ using namespace ento;
class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
const BugType LifetimeEndNode{this, "LifetimeEndReporter"};
- bool report(CheckerContext &C, StringRef Description) const {
+ bool report(CheckerContext &C, Twine Description) const {
ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState());
if (!Node)
return false;
- auto Report = std::make_unique<PathSensitiveBugReport>(LifetimeEndNode,
- Description, Node);
+ auto Report = std::make_unique<PathSensitiveBugReport>(
+ LifetimeEndNode, Description.str(), Node);
C.emitReport(std::move(Report));
return true;
}
@@ -41,7 +41,7 @@ class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
public:
void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const {
if (auto II = D->getIdentifier())
- report(C, (II->getName() + " LIFETIME END").str());
+ report(C, II->getName() + " LIFETIME END");
}
};
>From 70718075ca287ac58ebd3610862935098d898ba0 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:45:54 +0200
Subject: [PATCH 09/11] Assert the identifier is not null in the test
---
clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 7e245a70de44c..7f7849b189a9c 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -40,8 +40,9 @@ class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
public:
void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const {
- if (auto II = D->getIdentifier())
- report(C, II->getName() + " LIFETIME END");
+ auto II = D->getIdentifier();
+ EXPECT_TRUE(II != nullptr);
+ report(C, II->getName() + " LIFETIME END");
}
};
>From 45d68480656264bb38e7bfb17bf5d58cdc954743 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 2 Jun 2026 16:49:11 +0200
Subject: [PATCH 10/11] [NFC] Adjust comment length
---
clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 7f7849b189a9c..71fd90d9b2454 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -1,4 +1,4 @@
-//===- unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp --===//
+//===- unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp ------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
>From 949f37a49fa69335246b53dfcf4512e8eb047cbe Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <arseniy.zaostrovnykh at sonarsource.com>
Date: Tue, 2 Jun 2026 16:52:05 +0200
Subject: [PATCH 11/11] Update
clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Gábor Horváth <xazax.hun at gmail.com>
---
clang/include/clang/StaticAnalyzer/Core/CheckerManager.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
index 11de0ab86eb11..3311774f069ea 100644
--- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
+++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h
@@ -330,7 +330,7 @@ class CheckerManager {
const CallEvent &Call, ExprEngine &Eng,
bool wasInlined = false);
- /// Run checkers for end of variable lifetime
+ /// Run checkers for the end of a variable's lifetime.
void runCheckersForLifetimeEnd(ExplodedNodeSet &Dst,
const ExplodedNodeSet &Src,
const VarDecl *Decl, ExprEngine &Eng);
More information about the cfe-commits
mailing list