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 <[email protected]>
Date: Fri, 23 Jun 2023 14:17:47 +0200
Subject: [PATCH 01/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:10:11 +0200
Subject: [PATCH 02/33] 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 <[email protected]>
Date: Tue, 2 Jun 2026 16:17:34 +0200
Subject: [PATCH 03/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:23:53 +0200
Subject: [PATCH 04/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:35:25 +0200
Subject: [PATCH 05/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:41:10 +0200
Subject: [PATCH 06/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:42:09 +0200
Subject: [PATCH 07/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:44:46 +0200
Subject: [PATCH 08/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:45:54 +0200
Subject: [PATCH 09/33] 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 <[email protected]>
Date: Tue, 2 Jun 2026 16:49:11 +0200
Subject: [PATCH 10/33] [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 <[email protected]>
Date: Tue, 2 Jun 2026 16:52:05 +0200
Subject: [PATCH 11/33] 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 <[email protected]>
---
 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);

>From 6c707671e4c1f8408e80ddb877e86dc975211444 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 16:52:25 +0200
Subject: [PATCH 12/33] Update clang/include/clang/Analysis/CFG.h
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Gábor Horváth <[email protected]>
---
 clang/include/clang/Analysis/CFG.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/CFG.h 
b/clang/include/clang/Analysis/CFG.h
index 57ebfa47ec90e..9b0a4d63cc49f 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -294,7 +294,7 @@ class CFGLoopExit : public CFGElement {
 };
 
 /// Base class for representing elements related to the lifetime of automatic
-/// objects
+/// objects.
 class CFGScopeMarker : public CFGElement {
 public:
   LLVM_ATTRIBUTE_RETURNS_NONNULL const Stmt *getTriggerStmt() const {

>From 4c4a8edb8aed75a54f3f431be2536ae990bb4748 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 16:52:41 +0200
Subject: [PATCH 13/33] Update clang/include/clang/Analysis/ProgramPoint.h
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Gábor Horváth <[email protected]>
---
 clang/include/clang/Analysis/ProgramPoint.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/ProgramPoint.h 
b/clang/include/clang/Analysis/ProgramPoint.h
index 51ae43a334f1f..d93f3e356cb0f 100644
--- a/clang/include/clang/Analysis/ProgramPoint.h
+++ b/clang/include/clang/Analysis/ProgramPoint.h
@@ -738,7 +738,7 @@ class LifetimeEnd : public ProgramPoint {
     return static_cast<const Stmt *>(getData1());
   }
 
-  /// Returns a variable declaration that uniquely identifies the scope
+  /// Returns a variable declaration that uniquely identifies the scope.
   LLVM_ATTRIBUTE_RETURNS_NONNULL const VarDecl *getDecl() const {
     return static_cast<const VarDecl *>(getData2());
   }

>From bed95c003474acd07a054f898f9c470676eb9de7 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 16:54:14 +0200
Subject: [PATCH 14/33] Add ftime-trace scope for LifetimeEnd callbacks

---
 clang/lib/StaticAnalyzer/Core/CheckerManager.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp 
b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
index 8487c89ccffa8..80c03899d1e39 100644
--- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
@@ -344,6 +344,7 @@ void 
CheckerManager::runCheckersForLifetimeEnd(ExplodedNodeSet &Dst,
                                                const ExplodedNodeSet &Src,
                                                const VarDecl *Decl,
                                                ExprEngine &Eng) {
+  llvm::TimeTraceScope TimeScope("CheckerManager::runCheckersForLifetimeEnd");
   CheckLifetimeEndContext C(LifetimeEndCheckers, Decl, Eng);
   expandGraphWithCheckers(C, Dst, Src);
 }

>From 382d3ef779f9b5e920075c1fc4314a2e349d5057 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 16:58:02 +0200
Subject: [PATCH 15/33] Add a stack-trace loc to lifetime-end processing

---
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index d6676969fbc52..32da5e097c76e 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1145,6 +1145,9 @@ void ExprEngine::ProcessLoopExit(const Stmt* S, 
ExplodedNode *Pred) {
 
 void ExprEngine::ProcessLifetimeEnd(const Stmt *S, const VarDecl *D,
                                     ExplodedNode *Pred) {
+  PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
+                                S->getBeginLoc(),
+                                "Error evaluating end of a lifetime");
   ExplodedNodeSet Src;
   NodeBuilder Bldr(Pred, Src, *currBldrCtx);
   LifetimeEnd PP(S, D, Pred->getStackFrame());

>From 88f0590bd5260518aca0f519d67004afacb801a9 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 17:24:02 +0200
Subject: [PATCH 16/33] Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 clang/include/clang/Analysis/ProgramPoint.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/ProgramPoint.h 
b/clang/include/clang/Analysis/ProgramPoint.h
index d93f3e356cb0f..85ebd2afadcf3 100644
--- a/clang/include/clang/Analysis/ProgramPoint.h
+++ b/clang/include/clang/Analysis/ProgramPoint.h
@@ -728,7 +728,7 @@ class LoopExit : public ProgramPoint {
     }
 };
 
-/// Represents a point when the lifetime ends of any automatic object.
+/// Represents a point when the lifetime of an automatic object ends.
 class LifetimeEnd : public ProgramPoint {
 public:
   LifetimeEnd(const Stmt *S, const VarDecl *D, const StackFrame *SF)

>From 6fff6eb9a18ce300481e66c8eb0988d47190c091 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 17:26:19 +0200
Subject: [PATCH 17/33] [NFC] More accurate doc comment

---
 clang/include/clang/Analysis/ProgramPoint.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/ProgramPoint.h 
b/clang/include/clang/Analysis/ProgramPoint.h
index 85ebd2afadcf3..3f33e5fef007e 100644
--- a/clang/include/clang/Analysis/ProgramPoint.h
+++ b/clang/include/clang/Analysis/ProgramPoint.h
@@ -738,7 +738,7 @@ class LifetimeEnd : public ProgramPoint {
     return static_cast<const Stmt *>(getData1());
   }
 
-  /// Returns a variable declaration that uniquely identifies the scope.
+  /// Returns the variable declaration whose lifetime has ended.
   LLVM_ATTRIBUTE_RETURNS_NONNULL const VarDecl *getDecl() const {
     return static_cast<const VarDecl *>(getData2());
   }

>From 75fea4ed9194151534ac58628798936ca74651ac Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 17:36:14 +0200
Subject: [PATCH 18/33] Add a test case with non-trivial destructor

---
 .../StaticAnalyzer/CheckLifetimeEndTest.cpp       | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp 
b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 71fd90d9b2454..7c9f5d8153756 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -88,4 +88,19 @@ void foo() {
   EXPECT_TRUE(Diags.empty());
 }
 
+TEST(CheckLifetimeEnd, NonTrivialDtor) {
+  constexpr auto Code = R"(
+ struct A {
+   ~A() {}
+ };
+ void foo() {
+   A a;
+ }
+   )";
+  std::string Diags;
+  EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+      Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n");
+}
+
 } // namespace

>From d89cc248b0b9e49710c5ce060dab4cd7363fdef5 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 17:39:12 +0200
Subject: [PATCH 19/33] Update clang/include/clang/Analysis/CFG.h
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Donát Nagy <[email protected]>
---
 clang/include/clang/Analysis/CFG.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Analysis/CFG.h 
b/clang/include/clang/Analysis/CFG.h
index 9b0a4d63cc49f..0fd74979863d6 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -385,8 +385,8 @@ class CFGScopeBegin : public CFGScopeMarker {
 
 private:
   friend class CFGElement;
-  static bool isKind(const CFGElement &elem) {
-    return elem.getKind() == ScopeBegin;
+  static bool isKind(const CFGElement &Elem) {
+    return Elem.getKind() == ScopeBegin;
   }
 };
 

>From 2788acdd8cd80fc46f091b3ec769bec5e515056b Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Tue, 2 Jun 2026 17:40:10 +0200
Subject: [PATCH 20/33] [NFC] Capitalize params

---
 clang/include/clang/Analysis/CFG.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Analysis/CFG.h 
b/clang/include/clang/Analysis/CFG.h
index 0fd74979863d6..2079f99046021 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -320,8 +320,8 @@ class CFGScopeMarker : public CFGElement {
 /// Represents the point where the lifetime of an automatic object ends
 class CFGLifetimeEnds : public CFGScopeMarker {
 public:
-  explicit CFGLifetimeEnds(const VarDecl *var, const Stmt *stmt)
-      : CFGScopeMarker(LifetimeEnds, stmt, var) {}
+  explicit CFGLifetimeEnds(const VarDecl *Var, const Stmt *Stmt)
+      : CFGScopeMarker(LifetimeEnds, Stmt, Var) {}
 
   const VarDecl *getVarDecl() const {
     return static_cast<const VarDecl *>(Data2.getPointer());

>From fa768712a2938f5b924d390628e6effbbc7cdebb Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Wed, 3 Jun 2026 09:37:49 +0200
Subject: [PATCH 21/33] Avoid potential UB risk in the test

---
 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 7c9f5d8153756..02a7b34ebe756 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -41,7 +41,7 @@ class LifetimeEndReporter : public 
Checker<check::LifetimeEnd> {
 public:
   void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const {
     auto II = D->getIdentifier();
-    EXPECT_TRUE(II != nullptr);
+    ASSERT_TRUE(II != nullptr);
     report(C, II->getName() + " LIFETIME END");
   }
 };

>From 730c3693b6931c87a25c4f92e79ae5a39d7cd12f Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Wed, 3 Jun 2026 09:41:31 +0200
Subject: [PATCH 22/33] Handle LifetimeEnd in getStmtForDiagnostics

---
 clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp 
b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp
index 5b978cc540059..454eff97511b1 100644
--- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp
@@ -337,6 +337,8 @@ const Stmt *ExplodedNode::getStmtForDiagnostics() const {
     return CEB->getReturnStmt();
   if (auto FEP = P.getAs<FunctionExitPoint>())
     return FEP->getStmt();
+  if (auto LE = P.getAs<LifetimeEnd>())
+    return LE->getTriggerStmt();
 
   return nullptr;
 }

>From e1acc2ec5b5be3a3b5e7cc7806361af5b6ca67ee Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Thu, 4 Jun 2026 14:38:36 +0200
Subject: [PATCH 23/33] Add tests for different declaration scopes

---
 .../StaticAnalyzer/CheckLifetimeEndTest.cpp   | 80 +++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp 
b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 02a7b34ebe756..31f0dfba30933 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -103,4 +103,84 @@ TEST(CheckLifetimeEnd, NonTrivialDtor) {
   EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n");
 }
 
+TEST(CheckLifetimeEnd, MultipleVariablesAndNestedScopes) {
+  constexpr auto Code = R"(
+void foo() {
+  int a = 0;
+  int b = 0;
+  {
+    int c = 0, d = 0;
+  }
+}
+  )";
+
+  std::string Diags;
+  EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+      Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n"
+                   "test.LifetimeEndReporter: b LIFETIME END\n"
+                   "test.LifetimeEndReporter: c LIFETIME END\n"
+                   "test.LifetimeEndReporter: d LIFETIME END\n");
+}
+
+TEST(CheckLifetimeEnd, LocalStaticVariable) {
+  constexpr auto Code = R"(
+void foo() {
+  static int i = 0;
+  int j = 0;
+}
+  )";
+
+  std::string Diags;
+  EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+      Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: j LIFETIME END\n");
+}
+
+TEST(CheckLifetimeEnd, GlobalVariable) {
+  constexpr auto Code = R"(
+int g = 0;
+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, LoopBodyVariable) {
+  constexpr auto Code = R"(
+void foo() {
+  while (true) {
+    int i = 0;
+    break;
+  }
+}
+  )";
+
+  std::string Diags;
+  EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+      Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n");
+}
+
+TEST(CheckLifetimeEnd, ForLoopInductionVariable) {
+  constexpr auto Code = R"(
+void foo() {
+  for (int i = 0; i < 1; i++) {
+    int j = 0;
+  }
+}
+  )";
+
+  std::string Diags;
+  EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+      Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"
+                   "test.LifetimeEndReporter: j LIFETIME END\n");
+}
+
 } // namespace

>From d0dafb1eac48ec33f6df505a3b809f1ec3146e5f Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Thu, 4 Jun 2026 14:57:46 +0200
Subject: [PATCH 24/33] Add a test case for lifetime-extended temporary

---
 .../StaticAnalyzer/CheckLifetimeEndTest.cpp         | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp 
b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 31f0dfba30933..c59226828b77f 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -183,4 +183,17 @@ void foo() {
                    "test.LifetimeEndReporter: j LIFETIME END\n");
 }
 
+TEST(CheckLifetimeEnd, LifetimeExtendedTemporary) {
+  constexpr auto Code = R"(
+void foo() {
+  const int& r = 42;
+}
+  )";
+
+  std::string Diags;
+  EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
+      Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: r LIFETIME END\n");
+}
+
 } // namespace

>From c8fbda4f0ae42438a19b5f211e1f0c4cdf1013e9 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Thu, 4 Jun 2026 15:41:17 +0200
Subject: [PATCH 25/33] Avoid printing diagnostics from unit tests

---
 clang/unittests/StaticAnalyzer/CheckerRegistration.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/clang/unittests/StaticAnalyzer/CheckerRegistration.h 
b/clang/unittests/StaticAnalyzer/CheckerRegistration.h
index c4c6e7a9a896d..75b04f5dabd69 100644
--- a/clang/unittests/StaticAnalyzer/CheckerRegistration.h
+++ b/clang/unittests/StaticAnalyzer/CheckerRegistration.h
@@ -8,6 +8,7 @@
 
 #include "clang/Analysis/PathDiagnostic.h"
 #include "clang/Frontend/CompilerInstance.h"
+#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
 #include "clang/StaticAnalyzer/Core/Checker.h"
@@ -91,6 +92,9 @@ template <AddCheckerFn... Fns> class TestAction : public 
ASTFrontendAction {
 
   std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
                                                  StringRef File) override {
+    // Suppress the default HTML/text path diagnostic consumers that would
+    // otherwise emit to stderr via DiagnosticsEngine::Report().
+    Compiler.getAnalyzerOpts().AnalysisDiagOpt = PD_NONE;
     std::unique_ptr<AnalysisASTConsumer> AnalysisConsumer =
         CreateAnalysisConsumer(Compiler);
     if (OnlyEmitWarnings)

>From 5abfdfac79e81e899301a2f448aec27c337f03bd Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Thu, 4 Jun 2026 17:38:39 +0200
Subject: [PATCH 26/33] [NFC] Pass Twine by const-ref

---
 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 c59226828b77f..644ada31b11a7 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -27,7 +27,7 @@ using namespace ento;
 class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
   const BugType LifetimeEndNode{this, "LifetimeEndReporter"};
 
-  bool report(CheckerContext &C, Twine Description) const {
+  bool report(CheckerContext &C, const Twine &Description) const {
     ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState());
     if (!Node)
       return false;

>From d15f73561f754285616c1fd52db891c3d684cc11 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Thu, 4 Jun 2026 17:38:53 +0200
Subject: [PATCH 27/33] [NFC] Use getDeclName().getAsString() in the test

---
 clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp 
b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 644ada31b11a7..43e16dde17239 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -40,9 +40,7 @@ class LifetimeEndReporter : public 
Checker<check::LifetimeEnd> {
 
 public:
   void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const {
-    auto II = D->getIdentifier();
-    ASSERT_TRUE(II != nullptr);
-    report(C, II->getName() + " LIFETIME END");
+    report(C, D->getDeclName().getAsString() + " LIFETIME END");
   }
 };
 

>From afebf268c8bd27d26c21ec69c759cae98614973f Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Fri, 5 Jun 2026 16:00:24 +0200
Subject: [PATCH 28/33] Demonstrate how lifetime-end is reported for 2 loop
 iterations

---
 .../StaticAnalyzer/CheckLifetimeEndTest.cpp   | 51 ++++++++++++-------
 1 file changed, 34 insertions(+), 17 deletions(-)

diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp 
b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 43e16dde17239..f7b6bdce2744a 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -20,15 +20,19 @@
 #include "llvm/Config/llvm-config.h"
 #include "gtest/gtest.h"
 
-namespace {
 using namespace clang;
 using namespace ento;
 
+REGISTER_TRAIT_WITH_PROGRAMSTATE(TestLifetimeEndReportCountTrait, unsigned)
+
+namespace {
+
 class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
   const BugType LifetimeEndNode{this, "LifetimeEndReporter"};
 
-  bool report(CheckerContext &C, const Twine &Description) const {
-    ExplodedNode *Node = C.generateNonFatalErrorNode(C.getState());
+  bool report(CheckerContext &C, const Twine &Description,
+              ProgramStateRef State) const {
+    ExplodedNode *Node = C.generateNonFatalErrorNode(State);
     if (!Node)
       return false;
 
@@ -40,7 +44,13 @@ class LifetimeEndReporter : public 
Checker<check::LifetimeEnd> {
 
 public:
   void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const {
-    report(C, D->getDeclName().getAsString() + " LIFETIME END");
+    ProgramStateRef State = C.getState();
+    // Intentionally add a unique number to each report to avoid deduplication.
+    unsigned Count = State->get<TestLifetimeEndReportCountTrait>();
+    State = State->set<TestLifetimeEndReportCountTrait>(Count + 1);
+    auto Description = llvm::formatv("{0} LIFETIME END {1}",
+                                     D->getDeclName().getAsString(), Count);
+    report(C, Description, State);
   }
 };
 
@@ -70,7 +80,7 @@ void foo() {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 0\n");
 }
 
 TEST(CheckLifetimeEnd, CFGLifetimeDisabled) {
@@ -98,7 +108,7 @@ TEST(CheckLifetimeEnd, NonTrivialDtor) {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END 0\n");
 }
 
 TEST(CheckLifetimeEnd, MultipleVariablesAndNestedScopes) {
@@ -115,10 +125,10 @@ void foo() {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END\n"
-                   "test.LifetimeEndReporter: b LIFETIME END\n"
-                   "test.LifetimeEndReporter: c LIFETIME END\n"
-                   "test.LifetimeEndReporter: d LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: a LIFETIME END 3\n"
+                   "test.LifetimeEndReporter: b LIFETIME END 2\n"
+                   "test.LifetimeEndReporter: c LIFETIME END 1\n"
+                   "test.LifetimeEndReporter: d LIFETIME END 0\n");
 }
 
 TEST(CheckLifetimeEnd, LocalStaticVariable) {
@@ -132,7 +142,7 @@ void foo() {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: j LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: j LIFETIME END 0\n");
 }
 
 TEST(CheckLifetimeEnd, GlobalVariable) {
@@ -146,7 +156,7 @@ void foo() {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 0\n");
 }
 
 TEST(CheckLifetimeEnd, LoopBodyVariable) {
@@ -162,14 +172,18 @@ void foo() {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 0\n");
 }
 
 TEST(CheckLifetimeEnd, ForLoopInductionVariable) {
   constexpr auto Code = R"(
 void foo() {
-  for (int i = 0; i < 1; i++) {
+  for (int i = 0; i < 2; i++) {
     int j = 0;
+    {
+       int nested = 0;
+    }
+    ++j;
   }
 }
   )";
@@ -177,8 +191,11 @@ void foo() {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END\n"
-                   "test.LifetimeEndReporter: j LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: i LIFETIME END 4\n"
+                   "test.LifetimeEndReporter: j LIFETIME END 1\n"
+                   "test.LifetimeEndReporter: j LIFETIME END 3\n"
+                   "test.LifetimeEndReporter: nested LIFETIME END 0\n"
+                   "test.LifetimeEndReporter: nested LIFETIME END 2\n");
 }
 
 TEST(CheckLifetimeEnd, LifetimeExtendedTemporary) {
@@ -191,7 +208,7 @@ void foo() {
   std::string Diags;
   EXPECT_TRUE(runCheckerOnCodeWithArgs<addLifetimeEndReporter>(
       Code, EnableLifetimeArgs, Diags, /*OnlyEmitWarnings=*/true));
-  EXPECT_EQ(Diags, "test.LifetimeEndReporter: r LIFETIME END\n");
+  EXPECT_EQ(Diags, "test.LifetimeEndReporter: r LIFETIME END 0\n");
 }
 
 } // namespace

>From 4c9f82faff47b1719ad1b6371d52e42f16de17ad Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Fri, 5 Jun 2026 17:44:27 +0200
Subject: [PATCH 29/33] Lit test for lifetime-end nodes in CFG

---
 .../lifetime-end-simple-cfg-output.cpp        | 138 ++++++++++++++++++
 1 file changed, 138 insertions(+)
 create mode 100644 clang/test/Analysis/lifetime-end-simple-cfg-output.cpp

diff --git a/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp 
b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
new file mode 100644
index 0000000000000..2033264952558
--- /dev/null
+++ b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
@@ -0,0 +1,138 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -analyzer-config 
cfg-lifetime=true %s > %t 2>&1
+// RUN: FileCheck --input-file=%t %s
+
+// Tests for lifetime-end CFG nodes.
+
+void test_simple_variable() {
+  int i = 0;
+}
+// CHECK:       void test_simple_variable()
+// CHECK:       [B2 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B1
+// CHECK:       [B1]
+// CHECK-NEXT:    1: 0
+// CHECK-NEXT:    2: int i = 0;
+// CHECK-NEXT:    3: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B0
+// CHECK:       [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1
+
+struct A {
+  ~A() {}
+};
+void test_nontrivial_dtor() {
+  A a;
+}
+// CHECK:       void test_nontrivial_dtor()
+// CHECK:       [B2 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B1
+// CHECK:       [B1]
+// CHECK-NEXT:    1:  (CXXConstructExpr, [B1.2], A)
+// CHECK-NEXT:    2: A a;
+// CHECK-NEXT:    3: [B1.2].~A() (Implicit destructor)
+// CHECK-NEXT:    4: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B0
+// CHECK:       [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1
+
+void test_multiple_variables_nested_scopes() {
+  int a = 0;
+  int b = 0;
+  {
+    int c = 0, d = 0;
+  }
+}
+// CHECK:       void test_multiple_variables_nested_scopes()
+// CHECK:       [B2 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B1
+// CHECK:       [B1]
+// CHECK-NEXT:    1: 0
+// CHECK-NEXT:    2: int a = 0;
+// CHECK-NEXT:    3: 0
+// CHECK-NEXT:    4: int b = 0;
+// CHECK-NEXT:    5: 0
+// CHECK-NEXT:    6: int c = 0;
+// CHECK-NEXT:    7: 0
+// CHECK-NEXT:    8: int d = 0;
+// CHECK-NEXT:    9: [B1.8] (Lifetime ends)
+// CHECK-NEXT:   10: [B1.6] (Lifetime ends)
+// CHECK-NEXT:   11: [B1.4] (Lifetime ends)
+// CHECK-NEXT:   12: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B0
+// CHECK:       [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1
+
+void test_local_static() {
+  static int i = 0;
+  int j = 0;
+}
+// CHECK:       void test_local_static()
+// CHECK:       [B4 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B3
+// CHECK:       [B1]
+// CHECK-NEXT:    1: 0
+// CHECK-NEXT:    2: int j = 0;
+// CHECK-NEXT:    3: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    Preds (2): B2 B3
+// CHECK-NEXT:    Succs (1): B0
+// CHECK:       [B2]
+// CHECK-NEXT:    1: 0
+// CHECK-NEXT:    2: static int i = 0;
+// CHECK-NEXT:    Preds (1): B3
+// CHECK-NEXT:    Succs (1): B1
+// CHECK:       [B3]
+// CHECK-NEXT:    T: static init i
+// CHECK-NEXT:    Preds (1): B4
+// CHECK-NEXT:    Succs (2): B1 B2
+// CHECK:       [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1
+
+void test_loop_body() {
+  while (true) {
+    int i = 0;
+    break;
+  }
+}
+// CHECK:       void test_loop_body()
+// CHECK:       [B5 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B4
+// CHECK:       [B1]
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B4
+// CHECK:       [B2]
+// CHECK-NEXT:    1: [B3.2] (Lifetime ends)
+// CHECK-NEXT:    Succs (1): B1
+// CHECK:       [B3]
+// CHECK-NEXT:    1: 0
+// CHECK-NEXT:    2: int i = 0;
+// CHECK-NEXT:    3: [B3.2] (Lifetime ends)
+// CHECK-NEXT:    T: break;
+// CHECK-NEXT:    Preds (1): B4
+// CHECK-NEXT:    Succs (1): B0
+// CHECK:       [B4]
+// CHECK-NEXT:    1: true
+// CHECK-NEXT:    T: while [B4.1]
+// CHECK-NEXT:    Preds (2): B1 B5
+// CHECK-NEXT:    Succs (2): B3 NULL
+// CHECK:       [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B3
+
+void test_lifetime_extended_temporary() {
+  const int &r = 42;
+}
+// CHECK:       void test_lifetime_extended_temporary()
+// CHECK:       [B2 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B1
+// CHECK:       [B1]
+// CHECK-NEXT:    1: 42
+// CHECK-NEXT:    2: [B1.1] (ImplicitCastExpr, NoOp, const int)
+// CHECK-NEXT:    3: [B1.2]
+// CHECK-NEXT:    4: const int &r = 42;
+// CHECK-NEXT:    5: [B1.4] (Lifetime ends)
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B0
+// CHECK:       [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1

>From 40e46063de88765a94210d634a3bf68dd47cef4e Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Mon, 8 Jun 2026 09:13:31 +0200
Subject: [PATCH 30/33] Simplify LifetimeEndReporter auxiliary class

---
 .../StaticAnalyzer/CheckLifetimeEndTest.cpp   | 20 +++++++------------
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp 
b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index f7b6bdce2744a..269162425285d 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -30,18 +30,6 @@ namespace {
 class LifetimeEndReporter : public Checker<check::LifetimeEnd> {
   const BugType LifetimeEndNode{this, "LifetimeEndReporter"};
 
-  bool report(CheckerContext &C, const Twine &Description,
-              ProgramStateRef State) const {
-    ExplodedNode *Node = C.generateNonFatalErrorNode(State);
-    if (!Node)
-      return false;
-
-    auto Report = std::make_unique<PathSensitiveBugReport>(
-        LifetimeEndNode, Description.str(), Node);
-    C.emitReport(std::move(Report));
-    return true;
-  }
-
 public:
   void checkLifetimeEnd(const VarDecl *D, CheckerContext &C) const {
     ProgramStateRef State = C.getState();
@@ -50,7 +38,13 @@ class LifetimeEndReporter : public 
Checker<check::LifetimeEnd> {
     State = State->set<TestLifetimeEndReportCountTrait>(Count + 1);
     auto Description = llvm::formatv("{0} LIFETIME END {1}",
                                      D->getDeclName().getAsString(), Count);
-    report(C, Description, State);
+
+    ExplodedNode *Node = C.generateNonFatalErrorNode(State);
+    EXPECT_TRUE(Node != nullptr);
+
+    auto Report = std::make_unique<PathSensitiveBugReport>(
+        LifetimeEndNode, Description.str(), Node);
+    C.emitReport(std::move(Report));
   }
 };
 

>From d0c9db62ffbe2a946c992354db8d0b0c87a37841 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Mon, 8 Jun 2026 09:28:03 +0200
Subject: [PATCH 31/33] Make the CFG-dump test stricter

---
 .../lifetime-end-simple-cfg-output.cpp        | 87 ++++++++++++-------
 1 file changed, 58 insertions(+), 29 deletions(-)

diff --git a/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp 
b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
index 2033264952558..0dfee428d7484 100644
--- a/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
+++ b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
@@ -6,17 +6,27 @@
 void test_simple_variable() {
   int i = 0;
 }
-// CHECK:       void test_simple_variable()
-// CHECK:       [B2 (ENTRY)]
+// CHECK:      void test_simple_variable()
+// CHECK-NEXT: [B2 (ENTRY)]
 // CHECK-NEXT:    Succs (1): B1
-// CHECK:       [B1]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B1]
 // CHECK-NEXT:    1: 0
 // CHECK-NEXT:    2: int i = 0;
 // CHECK-NEXT:    3: [B1.2] (Lifetime ends)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
-// CHECK:       [B0 (EXIT)]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B0 (EXIT)]
 // CHECK-NEXT:    Preds (1): B1
+// CHECK-EMPTY:
+// CHECK-NEXT: ~A() noexcept
+// CHECK-NEXT: [B1 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B0
+// CHECK-EMPTY:
+// CHECK-NEXT: [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1
+// CHECK-EMPTY:
 
 struct A {
   ~A() {}
@@ -24,18 +34,21 @@ struct A {
 void test_nontrivial_dtor() {
   A a;
 }
-// CHECK:       void test_nontrivial_dtor()
-// CHECK:       [B2 (ENTRY)]
+// CHECK-NEXT: void test_nontrivial_dtor()
+// CHECK-NEXT: [B2 (ENTRY)]
 // CHECK-NEXT:    Succs (1): B1
-// CHECK:       [B1]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B1]
 // CHECK-NEXT:    1:  (CXXConstructExpr, [B1.2], A)
 // CHECK-NEXT:    2: A a;
 // CHECK-NEXT:    3: [B1.2].~A() (Implicit destructor)
 // CHECK-NEXT:    4: [B1.2] (Lifetime ends)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
-// CHECK:       [B0 (EXIT)]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B0 (EXIT)]
 // CHECK-NEXT:    Preds (1): B1
+// CHECK-EMPTY:
 
 void test_multiple_variables_nested_scopes() {
   int a = 0;
@@ -44,10 +57,11 @@ void test_multiple_variables_nested_scopes() {
     int c = 0, d = 0;
   }
 }
-// CHECK:       void test_multiple_variables_nested_scopes()
-// CHECK:       [B2 (ENTRY)]
+// CHECK-NEXT: void test_multiple_variables_nested_scopes()
+// CHECK-NEXT: [B2 (ENTRY)]
 // CHECK-NEXT:    Succs (1): B1
-// CHECK:       [B1]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B1]
 // CHECK-NEXT:    1: 0
 // CHECK-NEXT:    2: int a = 0;
 // CHECK-NEXT:    3: 0
@@ -62,33 +76,40 @@ void test_multiple_variables_nested_scopes() {
 // CHECK-NEXT:   12: [B1.2] (Lifetime ends)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
-// CHECK:       [B0 (EXIT)]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B0 (EXIT)]
 // CHECK-NEXT:    Preds (1): B1
+// CHECK-EMPTY:
 
 void test_local_static() {
   static int i = 0;
   int j = 0;
 }
-// CHECK:       void test_local_static()
-// CHECK:       [B4 (ENTRY)]
+// CHECK-NEXT: void test_local_static()
+// CHECK-NEXT: [B4 (ENTRY)]
 // CHECK-NEXT:    Succs (1): B3
-// CHECK:       [B1]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B1]
 // CHECK-NEXT:    1: 0
 // CHECK-NEXT:    2: int j = 0;
 // CHECK-NEXT:    3: [B1.2] (Lifetime ends)
 // CHECK-NEXT:    Preds (2): B2 B3
 // CHECK-NEXT:    Succs (1): B0
-// CHECK:       [B2]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B2]
 // CHECK-NEXT:    1: 0
 // CHECK-NEXT:    2: static int i = 0;
 // CHECK-NEXT:    Preds (1): B3
 // CHECK-NEXT:    Succs (1): B1
-// CHECK:       [B3]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B3]
 // CHECK-NEXT:    T: static init i
 // CHECK-NEXT:    Preds (1): B4
 // CHECK-NEXT:    Succs (2): B1 B2
-// CHECK:       [B0 (EXIT)]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B0 (EXIT)]
 // CHECK-NEXT:    Preds (1): B1
+// CHECK-EMPTY:
 
 void test_loop_body() {
   while (true) {
@@ -96,37 +117,44 @@ void test_loop_body() {
     break;
   }
 }
-// CHECK:       void test_loop_body()
-// CHECK:       [B5 (ENTRY)]
+// CHECK-NEXT: void test_loop_body()
+// CHECK-NEXT: [B5 (ENTRY)]
 // CHECK-NEXT:    Succs (1): B4
-// CHECK:       [B1]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B1]
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B4
-// CHECK:       [B2]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B2]
 // CHECK-NEXT:    1: [B3.2] (Lifetime ends)
 // CHECK-NEXT:    Succs (1): B1
-// CHECK:       [B3]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B3]
 // CHECK-NEXT:    1: 0
 // CHECK-NEXT:    2: int i = 0;
 // CHECK-NEXT:    3: [B3.2] (Lifetime ends)
 // CHECK-NEXT:    T: break;
 // CHECK-NEXT:    Preds (1): B4
 // CHECK-NEXT:    Succs (1): B0
-// CHECK:       [B4]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B4]
 // CHECK-NEXT:    1: true
 // CHECK-NEXT:    T: while [B4.1]
 // CHECK-NEXT:    Preds (2): B1 B5
 // CHECK-NEXT:    Succs (2): B3 NULL
-// CHECK:       [B0 (EXIT)]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B0 (EXIT)]
 // CHECK-NEXT:    Preds (1): B3
+// CHECK-EMPTY:
 
 void test_lifetime_extended_temporary() {
   const int &r = 42;
 }
-// CHECK:       void test_lifetime_extended_temporary()
-// CHECK:       [B2 (ENTRY)]
+// CHECK-NEXT: void test_lifetime_extended_temporary()
+// CHECK-NEXT: [B2 (ENTRY)]
 // CHECK-NEXT:    Succs (1): B1
-// CHECK:       [B1]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B1]
 // CHECK-NEXT:    1: 42
 // CHECK-NEXT:    2: [B1.1] (ImplicitCastExpr, NoOp, const int)
 // CHECK-NEXT:    3: [B1.2]
@@ -134,5 +162,6 @@ void test_lifetime_extended_temporary() {
 // CHECK-NEXT:    5: [B1.4] (Lifetime ends)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
-// CHECK:       [B0 (EXIT)]
+// CHECK-EMPTY:
+// CHECK-NEXT: [B0 (EXIT)]
 // CHECK-NEXT:    Preds (1): B1

>From 93fb78476f685861d464f67918c1255fa13204f5 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Mon, 8 Jun 2026 09:39:12 +0200
Subject: [PATCH 32/33] Demonstrate order between lifetime end and scope ends

---
 .../lifetime-end-simple-cfg-output.cpp        | 77 +++++++++++--------
 1 file changed, 46 insertions(+), 31 deletions(-)

diff --git a/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp 
b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
index 0dfee428d7484..198dae4cbb461 100644
--- a/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
+++ b/clang/test/Analysis/lifetime-end-simple-cfg-output.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -analyzer-config 
cfg-lifetime=true %s > %t 2>&1
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -analyzer-config 
cfg-lifetime=true,cfg-scopes=true %s > %t 2>&1
 // RUN: FileCheck --input-file=%t %s
 
 // Tests for lifetime-end CFG nodes.
@@ -11,9 +11,11 @@ void test_simple_variable() {
 // CHECK-NEXT:    Succs (1): B1
 // CHECK-EMPTY:
 // CHECK-NEXT: [B1]
-// CHECK-NEXT:    1: 0
-// CHECK-NEXT:    2: int i = 0;
-// CHECK-NEXT:    3: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    1: CFGScopeBegin(i)
+// CHECK-NEXT:    2: 0
+// CHECK-NEXT:    3: int i = 0;
+// CHECK-NEXT:    4: [B1.3] (Lifetime ends)
+// CHECK-NEXT:    5: CFGScopeEnd(i)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
 // CHECK-EMPTY:
@@ -39,10 +41,12 @@ void test_nontrivial_dtor() {
 // CHECK-NEXT:    Succs (1): B1
 // CHECK-EMPTY:
 // CHECK-NEXT: [B1]
-// CHECK-NEXT:    1:  (CXXConstructExpr, [B1.2], A)
-// CHECK-NEXT:    2: A a;
-// CHECK-NEXT:    3: [B1.2].~A() (Implicit destructor)
-// CHECK-NEXT:    4: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    1: CFGScopeBegin(a)
+// CHECK-NEXT:    2:  (CXXConstructExpr, [B1.3], A)
+// CHECK-NEXT:    3: A a;
+// CHECK-NEXT:    4: [B1.3].~A() (Implicit destructor)
+// CHECK-NEXT:    5: [B1.3] (Lifetime ends)
+// CHECK-NEXT:    6: CFGScopeEnd(a)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
 // CHECK-EMPTY:
@@ -62,18 +66,22 @@ void test_multiple_variables_nested_scopes() {
 // CHECK-NEXT:    Succs (1): B1
 // CHECK-EMPTY:
 // CHECK-NEXT: [B1]
-// CHECK-NEXT:    1: 0
-// CHECK-NEXT:    2: int a = 0;
-// CHECK-NEXT:    3: 0
-// CHECK-NEXT:    4: int b = 0;
-// CHECK-NEXT:    5: 0
-// CHECK-NEXT:    6: int c = 0;
+// CHECK-NEXT:    1: CFGScopeBegin(a)
+// CHECK-NEXT:    2: 0
+// CHECK-NEXT:    3: int a = 0;
+// CHECK-NEXT:    4: 0
+// CHECK-NEXT:    5: int b = 0;
+// CHECK-NEXT:    6: CFGScopeBegin(c)
 // CHECK-NEXT:    7: 0
-// CHECK-NEXT:    8: int d = 0;
-// CHECK-NEXT:    9: [B1.8] (Lifetime ends)
-// CHECK-NEXT:   10: [B1.6] (Lifetime ends)
-// CHECK-NEXT:   11: [B1.4] (Lifetime ends)
-// CHECK-NEXT:   12: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    8: int c = 0;
+// CHECK-NEXT:    9: 0
+// CHECK-NEXT:   10: int d = 0;
+// CHECK-NEXT:   11: [B1.10] (Lifetime ends)
+// CHECK-NEXT:   12: [B1.8] (Lifetime ends)
+// CHECK-NEXT:   13: CFGScopeEnd(c)
+// CHECK-NEXT:   14: [B1.5] (Lifetime ends)
+// CHECK-NEXT:   15: [B1.3] (Lifetime ends)
+// CHECK-NEXT:   16: CFGScopeEnd(a)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
 // CHECK-EMPTY:
@@ -90,9 +98,11 @@ void test_local_static() {
 // CHECK-NEXT:    Succs (1): B3
 // CHECK-EMPTY:
 // CHECK-NEXT: [B1]
-// CHECK-NEXT:    1: 0
-// CHECK-NEXT:    2: int j = 0;
-// CHECK-NEXT:    3: [B1.2] (Lifetime ends)
+// CHECK-NEXT:    1: CFGScopeBegin(j)
+// CHECK-NEXT:    2: 0
+// CHECK-NEXT:    3: int j = 0;
+// CHECK-NEXT:    4: [B1.3] (Lifetime ends)
+// CHECK-NEXT:    5: CFGScopeEnd(j)
 // CHECK-NEXT:    Preds (2): B2 B3
 // CHECK-NEXT:    Succs (1): B0
 // CHECK-EMPTY:
@@ -126,13 +136,16 @@ void test_loop_body() {
 // CHECK-NEXT:    Succs (1): B4
 // CHECK-EMPTY:
 // CHECK-NEXT: [B2]
-// CHECK-NEXT:    1: [B3.2] (Lifetime ends)
+// CHECK-NEXT:    1: [B3.3] (Lifetime ends)
+// CHECK-NEXT:    2: CFGScopeEnd(i)
 // CHECK-NEXT:    Succs (1): B1
 // CHECK-EMPTY:
 // CHECK-NEXT: [B3]
-// CHECK-NEXT:    1: 0
-// CHECK-NEXT:    2: int i = 0;
-// CHECK-NEXT:    3: [B3.2] (Lifetime ends)
+// CHECK-NEXT:    1: CFGScopeBegin(i)
+// CHECK-NEXT:    2: 0
+// CHECK-NEXT:    3: int i = 0;
+// CHECK-NEXT:    4: [B3.3] (Lifetime ends)
+// CHECK-NEXT:    5: CFGScopeEnd(i)
 // CHECK-NEXT:    T: break;
 // CHECK-NEXT:    Preds (1): B4
 // CHECK-NEXT:    Succs (1): B0
@@ -155,11 +168,13 @@ void test_lifetime_extended_temporary() {
 // CHECK-NEXT:    Succs (1): B1
 // CHECK-EMPTY:
 // CHECK-NEXT: [B1]
-// CHECK-NEXT:    1: 42
-// CHECK-NEXT:    2: [B1.1] (ImplicitCastExpr, NoOp, const int)
-// CHECK-NEXT:    3: [B1.2]
-// CHECK-NEXT:    4: const int &r = 42;
-// CHECK-NEXT:    5: [B1.4] (Lifetime ends)
+// CHECK-NEXT:    1: CFGScopeBegin(r)
+// CHECK-NEXT:    2: 42
+// CHECK-NEXT:    3: [B1.2] (ImplicitCastExpr, NoOp, const int)
+// CHECK-NEXT:    4: [B1.3]
+// CHECK-NEXT:    5: const int &r = 42;
+// CHECK-NEXT:    6: [B1.5] (Lifetime ends)
+// CHECK-NEXT:    7: CFGScopeEnd(r)
 // CHECK-NEXT:    Preds (1): B2
 // CHECK-NEXT:    Succs (1): B0
 // CHECK-EMPTY:

>From 3e7e4cead7d9a44ece0f385ce7cafa7958f90b14 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <[email protected]>
Date: Mon, 8 Jun 2026 10:53:40 +0200
Subject: [PATCH 33/33] Add a missing header for llvm::formatv

---
 clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp 
b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
index 269162425285d..03185f63b5fc8 100644
--- a/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
+++ b/clang/unittests/StaticAnalyzer/CheckLifetimeEndTest.cpp
@@ -18,6 +18,7 @@
 #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
 #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
 #include "llvm/Config/llvm-config.h"
+#include "llvm/Support/FormatVariadic.h"
 #include "gtest/gtest.h"
 
 using namespace clang;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to