https://github.com/benedekaibas updated 
https://github.com/llvm/llvm-project/pull/200145

>From 826dbd7525158bf0946b41ec0f629c235b8238b2 Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Thu, 28 May 2026 11:37:49 +0200
Subject: [PATCH 1/9] Started implementing the lifetime annotation checker.

---
 .../clang/StaticAnalyzer/Checkers/Checkers.td |  4 +
 .../StaticAnalyzer/Checkers/CMakeLists.txt    |  1 +
 .../Checkers/LifetimeAnnotations.cpp          | 73 +++++++++++++++++++
 3 files changed, 78 insertions(+)
 create mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp

diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td 
b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index eca2afbe340a9..85d59fc139728 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -788,6 +788,10 @@ def SmartPtrChecker: Checker<"SmartPtr">,
   Dependencies<[SmartPtrModeling]>,
   Documentation<HasDocumentation>;
 
+def LifetimeAnnotations : Checker<"LifetimeAnnotations">,
+  HelpText<"Check for lifetime violations using lifetime annotations">,
+  Documentation<NotDocumented>;
+
 } // end: "alpha.cplusplus"
 
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt 
b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 8a0621077b977..3f426186189fa 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -55,6 +55,7 @@ add_clang_library(clangStaticAnalyzerCheckers
   IteratorModeling.cpp
   IteratorRangeChecker.cpp
   IvarInvalidationChecker.cpp
+  LifetimeAnnotations.cpp
   LLVMConventionsChecker.cpp
   LocalizationChecker.cpp
   MacOSKeychainAPIChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
new file mode 100644
index 0000000000000..54e98b945c7b3
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -0,0 +1,73 @@
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include <AllocationState.h>
+
+using namespace clang;
+using namespace ento;
+
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, const MemRegion *,
+                               const MemRegion *);
+
+class LifetimeAnnotations : public Checker<check::PostCall> {
+public:
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+                  const char *Sep) const override;
+};
+
+void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
+                                        CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+
+  const auto *MethodDecl = dyn_cast_if_present<CXXMethodDecl>(Call.getDecl());
+
+  if (!MethodDecl)
+    return;
+
+  unsigned LBParamIdx = MethodDecl->getNumParams();
+  for (unsigned i = 0; i < MethodDecl->getNumParams(); i++) {
+    if (MethodDecl->getParamDecl(i)->hasAttr<LifetimeBoundAttr>()) {
+      LBParamIdx = i;
+      break;
+    }
+  }
+  if (LBParamIdx == MethodDecl->getNumParams())
+    return;
+
+  SVal RetVal = Call.getReturnValue();
+  const MemRegion *RetValRegion = RetVal.getAsRegion();
+  if (!RetValRegion)
+    return;
+
+  SVal ArgVal = Call.getArgSVal(LBParamIdx);
+  const MemRegion *ArgValRegion = ArgVal.getAsRegion();
+  if (!ArgValRegion)
+    return;
+
+  State = State->set<LifetimeBoundMap>(RetValRegion, ArgValRegion);
+  C.addTransition(State);
+}
+
+void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
+                                     const char *NL, const char *Sep) const {
+  auto LBTy = State->get<LifetimeBoundMap>();
+
+  if (!LBTy.isEmpty()) {
+    Out << Sep << "LifetimeBound objects: ";
+
+    for (auto I : LBTy) {
+      Out << I.first << " bound to " << I.second << NL;
+    }
+  }
+}
+
+void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
+  mgr.registerChecker<LifetimeAnnotations>();
+}
+
+bool ento::shouldRegisterLifetimeAnnotations(const CheckerManager &mgr) {
+  return true;
+}

>From fa2b6c5e8c9de65307f9dfec47019530b005465c Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Sun, 31 May 2026 00:40:48 +0200
Subject: [PATCH 2/9] Addressed mentors feedback.

---
 .../Checkers/LifetimeAnnotations.cpp          | 68 ++++++++++++-------
 1 file changed, 44 insertions(+), 24 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 54e98b945c7b3..4052e13859041 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -3,12 +3,15 @@
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
-#include <AllocationState.h>
+#include "AllocationState.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+
 
 using namespace clang;
 using namespace ento;
+using namespace clang::lifetimes;
 
-REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, const MemRegion *,
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef,
                                const MemRegion *);
 
 class LifetimeAnnotations : public Checker<check::PostCall> {
@@ -22,45 +25,62 @@ void LifetimeAnnotations::checkPostCall(const CallEvent 
&Call,
                                         CheckerContext &C) const {
   ProgramStateRef State = C.getState();
 
-  const auto *MethodDecl = dyn_cast_if_present<CXXMethodDecl>(Call.getDecl());
+  const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call);
+  if (!FC)
+    return;
 
-  if (!MethodDecl)
+  const FunctionDecl *FD = FC->getDecl();
+  if (!FD)
     return;
 
-  unsigned LBParamIdx = MethodDecl->getNumParams();
-  for (unsigned i = 0; i < MethodDecl->getNumParams(); i++) {
-    if (MethodDecl->getParamDecl(i)->hasAttr<LifetimeBoundAttr>()) {
-      LBParamIdx = i;
+  unsigned LBParamIdx = FD->getNumParams();
+  // FIXME: Use range based for loop instead. Currently that would require
+  // to also change how we create ArgVal which would need a new logic to
+  // be implemented.
+  for (unsigned I = 0, E = FD->getNumParams(); I != E; I++) {
+    if (FD->getParamDecl(I)->hasAttr<LifetimeBoundAttr>()) {
+      LBParamIdx = I;
+      // FIXME: If multiple parameters are annotated this logic would
+      // prevent the analyzer to read after the first parameter.
       break;
     }
   }
-  if (LBParamIdx == MethodDecl->getNumParams())
-    return;
-
   SVal RetVal = Call.getReturnValue();
-  const MemRegion *RetValRegion = RetVal.getAsRegion();
-  if (!RetValRegion)
-    return;
 
-  SVal ArgVal = Call.getArgSVal(LBParamIdx);
-  const MemRegion *ArgValRegion = ArgVal.getAsRegion();
-  if (!ArgValRegion)
+  SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
+  if(!RetValSym)
     return;
 
-  State = State->set<LifetimeBoundMap>(RetValRegion, ArgValRegion);
+  if (LBParamIdx != FD->getNumParams()) {
+    SVal ArgVal = Call.getArgSVal(LBParamIdx);
+    const MemRegion *ArgValRegion = ArgVal.getAsRegion();
+    // FIXME: if(!ArgValRegion) should be also handled since in those cases
+    // the argument has no region, but still needs to be tracked.
+    if (ArgValRegion)
+        State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion);
+  }
+
+  if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+    if (implicitObjectParamIsLifetimeBound(FD)) {
+      const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion();
+
+      if (AttrRegion)
+          State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion);
+    }
+  }
   C.addTransition(State);
 }
 
 void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
                                      const char *NL, const char *Sep) const {
-  auto LBTy = State->get<LifetimeBoundMap>();
+  auto LBVal = State->get<LifetimeBoundMap>();
 
-  if (!LBTy.isEmpty()) {
-    Out << Sep << "LifetimeBound objects: ";
+  if (LBVal.isEmpty())
+    return;
 
-    for (auto I : LBTy) {
-      Out << I.first << " bound to " << I.second << NL;
-    }
+  Out << Sep << "LifetimeBound bindings:" << NL;
+  for (auto&& [RetValSym, ArgValRegion] : LBVal) {
+    Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL;
   }
 }
 

>From 9ff637735dc8242b6f7735dfd2773649f4602bc6 Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Fri, 5 Jun 2026 13:33:50 +0200
Subject: [PATCH 3/9] Save the current work that has the test cases and the
 helper function implemented.

---
 ...-AST-matching-to-get-containers-regi.patch | 130 ++++++++++++++++++
 .../Checkers/LifetimeAnnotations.cpp          | 108 +++++++++++++--
 clang/test/Analysis/lifetime-bound.cpp        | 111 +++++++++++++++
 3 files changed, 335 insertions(+), 14 deletions(-)
 create mode 100644 
0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch
 create mode 100644 clang/test/Analysis/lifetime-bound.cpp

diff --git a/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch 
b/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch
new file mode 100644
index 0000000000000..450bdda9f70dc
--- /dev/null
+++ b/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch
@@ -0,0 +1,130 @@
+From 00c5cf9e103a88ad9af285104f878c5f932ad8ff Mon Sep 17 00:00:00 2001
+From: benedekaibas <[email protected]>
+Date: Tue, 2 Jun 2026 13:13:46 +0200
+Subject: [PATCH] [analyzer] Removed AST matching to get containers' region and
+ fixed small issues.
+
+---
+ .../StaticAnalyzer/Checkers/MoveChecker.cpp   | 40 +++++++++++--------
+ .../test/Analysis/use-after-move-iterator.cpp | 24 +++++------
+ 2 files changed, 36 insertions(+), 28 deletions(-)
+
+diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+index 47e9c585054a..9ff2e90b618a 100644
+--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
++++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+@@ -518,31 +518,29 @@ bool MoveChecker::evalCall(const CallEvent &Call, 
CheckerContext &C) const {
+   if (!StdMoveCall.matches(Call))
+     return false;
+ 
+-  const auto *BeginCall =
+-      dyn_cast<CXXMemberCallExpr>(CE->getArg(0)->IgnoreImpCasts());
+-  if (!BeginCall)
++  const auto *POS = getIteratorPosition(State, Call.getArgSVal(0));
++  if (!POS)
+     return false;
+ 
+-  const Expr *ContainerExpr = BeginCall->getImplicitObjectArgument();
+-  const auto *DRE = dyn_cast<DeclRefExpr>(ContainerExpr->IgnoreImpCasts());
+-  if (!DRE)
++  const MemRegion *ContainerRegion = POS->getContainer();
++  if (!ContainerRegion)
+     return false;
+ 
+-  const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
+-  if (!VD)
++  const auto *TypedRegion =
++      dyn_cast_if_present<TypedValueRegion>(ContainerRegion);
++  if (!TypedRegion)
+     return false;
+ 
+-  const MemRegion *Region =
+-      State->getLValue(VD, C.getLocationContext()).getAsRegion();
+-  if (!Region)
+-    return false;
++  QualType ObjTy = TypedRegion->getValueType();
+ 
+-  const CXXRecordDecl *RD = ContainerExpr->getType()->getAsCXXRecordDecl();
++  const auto *RD = ObjTy->getAsCXXRecordDecl();
+   if (!RD)
+     return false;
+ 
+-  ObjectKind OK = classifyObject(State, Region, RD);
++  ObjectKind OK = classifyObject(State, ContainerRegion, RD);
+ 
++  // FIXME: Also apply getIteratorPosition from IteratorModeling to recover 
the
++  // destination region instead of doing AST pattern matching.
+   const auto *BackInsCall = 
dyn_cast<CallExpr>(CE->getArg(2)->IgnoreImpCasts());
+   if (!BackInsCall)
+     return false;
+@@ -573,7 +571,8 @@ bool MoveChecker::evalCall(const CallEvent &Call, 
CheckerContext &C) const {
+                                    /*CausesPointerEscape=*/false);
+ 
+   if (shouldBeTracked(OK))
+-    State = State->set<TrackedContentsMap>(Region, RegionState::getMoved());
++    State = State->set<TrackedContentsMap>(ContainerRegion,
++                                           RegionState::getMoved());
+ 
+   C.addTransition(State);
+   return true;
+@@ -736,10 +735,19 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
+ 
+     if (const auto *POS = getIteratorPosition(State, Val)) {
+       const MemRegion *ContainerRegion = POS->getContainer();
++      if (!ContainerRegion)
++        return;
++
++      const auto *TypedRegion =
++          dyn_cast_if_present<TypedValueRegion>(ContainerRegion);
++      if (!TypedRegion)
++        return;
+ 
+-      const auto *TypedRegion = cast<TypedValueRegion>(ContainerRegion);
+       QualType ObjTy = TypedRegion->getValueType();
+       const auto *R = ObjTy->getAsCXXRecordDecl();
++      if (!R)
++        return;
++
+       if (State->get<TrackedContentsMap>(ContainerRegion)) {
+         ExplodedNode *N = tryToReportBug(ContainerRegion, R, C, MK_FunCall);
+         if (!N || N->isSink())
+diff --git a/clang/test/Analysis/use-after-move-iterator.cpp 
b/clang/test/Analysis/use-after-move-iterator.cpp
+index 50dd7e57b42e..2357be3a6bb3 100644
+--- a/clang/test/Analysis/use-after-move-iterator.cpp
++++ b/clang/test/Analysis/use-after-move-iterator.cpp
+@@ -10,20 +10,20 @@
+ // IteratorModeling is enabled.
+ 
//===----------------------------------------------------------------------===//
+ 
+-void iteratorDerefSource() {
++std::string iteratorDeref(int rng) {
+   std::list<std::string> l1;
+   l1.push_back("l1");
+   std::list<std::string> l2;
+ 
+-  std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+-  *l1.cbegin(); // expected-warning {{Method called on moved-from object 
'l1'}}
+-}
+-
+-void iteratorDerefDest() {
+-  std::list<std::string> l1;
+-  l1.push_back("l1");
+-  std::list<std::string> l2;
+-
+-  std::move(l1.begin(), l1.end(), std::back_inserter(l2));
+-  *l2.cbegin(); // no-warning
++  switch (rng) {
++    case 10: {
++      std::move(l1.begin(), l1.end(), std::back_inserter(l2));
++      return *l1.cbegin(); // expected-warning {{Method called on moved-from 
object 'l1'}}
++    }
++    case 20: {
++      std::move(l1.begin(), l1.end(), std::back_inserter(l2));
++      return *l2.cbegin(); // no-warning: only l1 was invalidated and not l2!
++    }
++  }
++  return 0;
+ }
+-- 
+2.43.0
+
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 4052e13859041..1f93d25d7a788 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -3,6 +3,7 @@
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/Support/raw_ostream.h"
 #include "AllocationState.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 
@@ -13,16 +14,30 @@ using namespace clang::lifetimes;
 
 REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef,
                                const MemRegion *);
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *, const 
MemRegion *);
 
-class LifetimeAnnotations : public Checker<check::PostCall> {
+
+class LifetimeAnnotations : public Checker<check::PostCall, eval::Call> {
 public:
   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
   void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
                   const char *Sep) const override;
+  bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+  void analyzerLifetimeBound(const CallEvent &Call, const CallExpr *, 
CheckerContext &C) const;
+
+  const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
+};
+
+typedef void (LifetimeAnnotations::*FnCheck)(const CallEvent &Call, const 
CallExpr *,
+                                              CheckerContext &) const;
+CallDescriptionMap<FnCheck> Callbacks = {
+  {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
+    &LifetimeAnnotations::analyzerLifetimeBound},
 };
 
 void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
                                         CheckerContext &C) const {
+  llvm::errs() << "checkPostCall fired" << "\n";
   ProgramStateRef State = C.getState();
 
   const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call);
@@ -33,6 +48,8 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
   if (!FD)
     return;
 
+  SVal RetVal = Call.getReturnValue();
+  SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
   unsigned LBParamIdx = FD->getNumParams();
   // FIXME: Use range based for loop instead. Currently that would require
   // to also change how we create ArgVal which would need a new logic to
@@ -45,27 +62,34 @@ void LifetimeAnnotations::checkPostCall(const CallEvent 
&Call,
       break;
     }
   }
-  SVal RetVal = Call.getReturnValue();
-
-  SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
-  if(!RetValSym)
-    return;
 
   if (LBParamIdx != FD->getNumParams()) {
     SVal ArgVal = Call.getArgSVal(LBParamIdx);
-    const MemRegion *ArgValRegion = ArgVal.getAsRegion();
-    // FIXME: if(!ArgValRegion) should be also handled since in those cases
-    // the argument has no region, but still needs to be tracked.
-    if (ArgValRegion)
+    if (const MemRegion *ArgValRegion = ArgVal.getAsRegion()) {
+      if (RetValSym)
+        llvm::errs() << "RetValSym: ";
+        RetValSym->dump();
+        llvm::errs() << "\n";
         State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion);
+        llvm::errs() << "State got set with RetValSym" << "\n";
+        C.getState()->dump();
+        llvm::errs() << "\n";
+      if (const MemRegion *RetValRegion = RetVal.getAsRegion())
+        State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion);
+    }
   }
 
   if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+    llvm::errs() << "isCXXThisVal true" << "\n";
     if (implicitObjectParamIsLifetimeBound(FD)) {
-      const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion();
-
-      if (AttrRegion)
+      llvm::errs() << "isLifetimeBound true" << "\n";
+      if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
+        llvm::errs() << "is AttrRegion non null" << "\n";
+        if (RetValSym)
           State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion);
+        if (const MemRegion *RetValRegion = RetVal.getAsRegion())
+          State = State->set<LifetimeBoundMapVal>(RetValRegion, AttrRegion);
+      }
     }
   }
   C.addTransition(State);
@@ -74,14 +98,70 @@ void LifetimeAnnotations::checkPostCall(const CallEvent 
&Call,
 void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
                                      const char *NL, const char *Sep) const {
   auto LBVal = State->get<LifetimeBoundMap>();
+  auto LBValTwo = State->get<LifetimeBoundMapVal>();
 
-  if (LBVal.isEmpty())
+  if (LBVal.isEmpty() && LBValTwo.isEmpty())
     return;
 
   Out << Sep << "LifetimeBound bindings:" << NL;
   for (auto&& [RetValSym, ArgValRegion] : LBVal) {
     Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL;
   }
+  for (auto&& [RetVal, ArgValRegion]: LBValTwo) {
+    Out << " Origin " << RetVal << " contains Loan " << ArgValRegion << NL;
+  }
+}
+
+bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) 
const {
+
+  const auto *CE = llvm::dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return false;
+
+  const FnCheck *Handler = Callbacks.lookup(Call);
+  if (!Handler)
+    return false;
+
+  (this->*(*Handler))(Call, CE, C);
+  return true;
+  C.addTransition(C.getState());
+}
+
+void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const 
CallExpr *CE, CheckerContext &C) const {
+  llvm::errs() << "\n";
+  llvm::errs() << "lifetime_bound called" << "\n";
+  ProgramStateRef State = C.getState();
+  unsigned int ArgExpr = CE->getNumArgs();
+  if (ArgExpr != 1)
+    return;
+
+  SVal ArgSVal = Call.getArgSVal(0);
+
+  const MemRegion *ArgValRegion = ArgSVal.getAsRegion();
+  SymbolRef ArgSValSym = ArgSVal.getAsSymbol(/*IncludeBaseRegions=*/true);
+
+  llvm::SmallString<128> Str;
+  llvm::raw_svector_ostream OS(Str);
+  ExplodedNode *N = C.generateNonFatalErrorNode();
+  if (!N)
+    return;
+
+  if (ArgSValSym) {
+    if (const auto *ArgValLookFor = State->get<LifetimeBoundMap>(ArgSValSym)) {
+      OS << " Origin " << ArgSValSym << " contains loan " << *ArgValLookFor;
+      auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+      C.emitReport(std::move(BR));
+      Str.clear();
+    }
+  }
+  if (ArgValRegion) {
+    if (const auto *AttrValLookFor = 
State->get<LifetimeBoundMapVal>(ArgValRegion)) {
+      OS << " Origin " << ArgValRegion << " bound to " << *AttrValLookFor;
+      auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
+      C.emitReport(std::move(BR));
+      Str.clear();
+    }
+  }
 }
 
 void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
new file mode 100644
index 0000000000000..1cedfe5b0398f
--- /dev/null
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -0,0 +1,111 @@
+// RUN: %clang_analyze_cc1 
-analyzer-checker=alpha.cplusplus.LifetimeAnnotations \
+// RUN:   -verify %s
+// RUN: %clang_analyze_cc1 
-analyzer-checker=alpha.cplusplus.LifetimeAnnotations \
+// RUN:   -analyzer-config c++-container-inlining=false -verify %s
+
+void clang_analyzer_dump(...);
+
+// These are the cases when the result of function calls are MemRegions.
+
+struct A {};
+
+// Ref type parameter annotated case
+struct X {
+  int& choose(int& a [[clang::lifetimebound]]) { return a; }
+};
+
+void clang_analyzer_lifetime_bound(int&);
+
+void caller() {
+  int v = 0;
+  X obj;
+  int& r = obj.choose(v);
+  clang_analyzer_lifetime_bound(r); // expected-warning {{Origin v bound to v}}
+  clang_analyzer_dump(r);
+}
+
+// Obj ref type function return annotated case
+struct Y {
+  A a;
+  A& getA() [[clang::lifetimebound]] { return a; }
+};
+
+void clang_analyzer_lifetime_bound(A& a);
+
+void caller_two() {
+  // Return statement is annotated case.
+  Y y;
+  A& f = y.getA();
+  clang_analyzer_lifetime_bound(f); // expected-warning {{Origin y.a bound to 
y}}
+  clang_analyzer_dump(f);
+}
+
+// Obj ptr type function return annotated case
+struct Z {
+  A a;
+  A* getA() [[clang::lifetimebound]] { return &a; }
+};
+
+void clang_analyzer_lifetime_bound(A* a);
+
+void caller_three() {
+  Z z;
+  A* func = z.getA();
+  clang_analyzer_lifetime_bound(func); // expected-warning {{Origin z.a bound 
to z}}
+  clang_analyzer_dump(func);
+}
+
+// Free function with annotated param and ref return
+int& foo(int& num [[clang::lifetimebound]]) { return num; }
+
+void clang_analyzer_lifetime_bound(int&);
+
+void caller_four() {
+  int num = 5;
+  int& s = foo(num);
+  clang_analyzer_lifetime_bound(s); // expected-warning {{Origin num bound to 
num}}
+  clang_analyzer_dump(s);
+}
+
+// Free function with annotated param and ptr return
+int* boo(int* num [[clang::lifetimebound]]) { return num; }
+
+void clang_analyzer_lifetime_bound(int*);
+
+void caller_five() {
+  int n = 55;
+  int* n_ptr = &n;
+  int* s = boo(n_ptr);
+
+  clang_analyzer_lifetime_bound(s); // expected-warning {{Origin n bound to n}}
+  clang_analyzer_dump(s);
+}
+
+// These are the cases when the result of function calls are SymbolRefs.
+
+// Function returns ptr and has an annotated parameter
+int* foo(int* n [[clang::lifetimebound]]);
+
+void clang_analyzer_lifetime_bound(int*);
+
+void caller_six() {
+  int y = 15;
+  int* y_ptr = &y;
+  auto bind = foo(y_ptr);
+
+  clang_analyzer_lifetime_bound(bind);
+                                       // expected-warning@-1 {{Origin bound 
to n}}
+                                      // expected-warning@-1 {{Origin contains 
loan n}}
+  clang_analyzer_dump(bind);
+
+// FIXME: The full warning does look like this:
+// Origin SymRegion{conj_$5{int *, LC1, S847, #1}} bound to n
+// Origin conj_$5{int *, LC1, S847, #1} contains loan n
+// Since the conj sym number and the ID can change across runs I have decided 
to just include
+// string parts of the error message since that is the only consistent part of 
the emitted report.
+// This does not apply to the test cases above this test case.
+}
+
+
+// Function returns a reference and has an annotated parameter
+

>From 2de8ddc32b07d2217bdeac16fd1c578b02ae5e78 Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Fri, 5 Jun 2026 16:09:28 +0200
Subject: [PATCH 4/9] Save the current work that has the test cases and the
 helper function implemented + more test cases.

---
 .../Checkers/LifetimeAnnotations.cpp             |  9 ++++++++-
 clang/test/Analysis/lifetime-bound.cpp           | 16 ++++++++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 1f93d25d7a788..787698671ae75 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -145,7 +145,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const 
CallEvent &Call, const Cal
   ExplodedNode *N = C.generateNonFatalErrorNode();
   if (!N)
     return;
-
+  llvm::errs() << "ArgSValSym: " << (ArgSValSym ? "non-null" : "null") << "\n";
   if (ArgSValSym) {
     if (const auto *ArgValLookFor = State->get<LifetimeBoundMap>(ArgSValSym)) {
       OS << " Origin " << ArgSValSym << " contains loan " << *ArgValLookFor;
@@ -154,7 +154,13 @@ void LifetimeAnnotations::analyzerLifetimeBound(const 
CallEvent &Call, const Cal
       Str.clear();
     }
   }
+
+  llvm::errs() << "ArgValRegion: " << (ArgValRegion ? "non-null" : "null") << 
"\n";
   if (ArgValRegion) {
+    llvm::errs() << "\n";
+    llvm::errs() << "ArgValRegion: ";
+    ArgValRegion->dump();
+    llvm::errs() << "\n";
     if (const auto *AttrValLookFor = 
State->get<LifetimeBoundMapVal>(ArgValRegion)) {
       OS << " Origin " << ArgValRegion << " bound to " << *AttrValLookFor;
       auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
@@ -164,6 +170,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const 
CallEvent &Call, const Cal
   }
 }
 
+
 void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
   mgr.registerChecker<LifetimeAnnotations>();
 }
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
index 1cedfe5b0398f..8cca08aca3f1c 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -108,4 +108,20 @@ void caller_six() {
 
 
 // Function returns a reference and has an annotated parameter
+int& func(int& some_number [[clang::lifetimebound]]);
+
+void clang_analyzer_lifetime_bound(int&);
+
+void caller_seven() {
+  int f = 15;
+  auto& bind = func(f);
+
+  clang_analyzer_lifetime_bound(bind);
+                                       // expected-warning@-1 {{Origin bound 
to some_number}}
+                                      // expected-warning@-1 {{Origin contains 
loan some_number}}
+  clang_analyzer_dump(bind);
+
+// The FIXME about the full warning applies to this text case as well.
+}
+
 

>From c8d266749a90750db1e9585034836fad8150c27b Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Fri, 5 Jun 2026 16:37:42 +0200
Subject: [PATCH 5/9] Removed debugged comments.

---
 .../Checkers/LifetimeAnnotations.cpp          | 20 ++--------------
 clang/test/Analysis/lifetime-bound.cpp        | 24 +++++++++++++++----
 2 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 787698671ae75..8baf4b0ba223b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -67,24 +67,15 @@ void LifetimeAnnotations::checkPostCall(const CallEvent 
&Call,
     SVal ArgVal = Call.getArgSVal(LBParamIdx);
     if (const MemRegion *ArgValRegion = ArgVal.getAsRegion()) {
       if (RetValSym)
-        llvm::errs() << "RetValSym: ";
-        RetValSym->dump();
-        llvm::errs() << "\n";
         State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion);
-        llvm::errs() << "State got set with RetValSym" << "\n";
-        C.getState()->dump();
-        llvm::errs() << "\n";
       if (const MemRegion *RetValRegion = RetVal.getAsRegion())
         State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion);
     }
   }
 
   if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
-    llvm::errs() << "isCXXThisVal true" << "\n";
     if (implicitObjectParamIsLifetimeBound(FD)) {
-      llvm::errs() << "isLifetimeBound true" << "\n";
       if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
-        llvm::errs() << "is AttrRegion non null" << "\n";
         if (RetValSym)
           State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion);
         if (const MemRegion *RetValRegion = RetVal.getAsRegion())
@@ -128,8 +119,7 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call, 
CheckerContext &C) con
 }
 
 void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const 
CallExpr *CE, CheckerContext &C) const {
-  llvm::errs() << "\n";
-  llvm::errs() << "lifetime_bound called" << "\n";
+
   ProgramStateRef State = C.getState();
   unsigned int ArgExpr = CE->getNumArgs();
   if (ArgExpr != 1)
@@ -145,7 +135,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const 
CallEvent &Call, const Cal
   ExplodedNode *N = C.generateNonFatalErrorNode();
   if (!N)
     return;
-  llvm::errs() << "ArgSValSym: " << (ArgSValSym ? "non-null" : "null") << "\n";
+
   if (ArgSValSym) {
     if (const auto *ArgValLookFor = State->get<LifetimeBoundMap>(ArgSValSym)) {
       OS << " Origin " << ArgSValSym << " contains loan " << *ArgValLookFor;
@@ -155,12 +145,7 @@ void LifetimeAnnotations::analyzerLifetimeBound(const 
CallEvent &Call, const Cal
     }
   }
 
-  llvm::errs() << "ArgValRegion: " << (ArgValRegion ? "non-null" : "null") << 
"\n";
   if (ArgValRegion) {
-    llvm::errs() << "\n";
-    llvm::errs() << "ArgValRegion: ";
-    ArgValRegion->dump();
-    llvm::errs() << "\n";
     if (const auto *AttrValLookFor = 
State->get<LifetimeBoundMapVal>(ArgValRegion)) {
       OS << " Origin " << ArgValRegion << " bound to " << *AttrValLookFor;
       auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N);
@@ -170,7 +155,6 @@ void LifetimeAnnotations::analyzerLifetimeBound(const 
CallEvent &Call, const Cal
   }
 }
 
-
 void ento::registerLifetimeAnnotations(CheckerManager &mgr) {
   mgr.registerChecker<LifetimeAnnotations>();
 }
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
index 8cca08aca3f1c..2acc1734644f2 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -81,6 +81,8 @@ void caller_five() {
   clang_analyzer_dump(s);
 }
 
+
+
 // These are the cases when the result of function calls are SymbolRefs.
 
 // Function returns ptr and has an annotated parameter
@@ -91,11 +93,11 @@ void clang_analyzer_lifetime_bound(int*);
 void caller_six() {
   int y = 15;
   int* y_ptr = &y;
-  auto bind = foo(y_ptr);
+  auto* bind = foo(y_ptr);
 
   clang_analyzer_lifetime_bound(bind);
-                                       // expected-warning@-1 {{Origin bound 
to n}}
-                                      // expected-warning@-1 {{Origin contains 
loan n}}
+                                       // expected-warning@-1 {{Origin bound 
to y}}
+                                       // expected-warning@-1 {{Origin 
contains loan y}}
   clang_analyzer_dump(bind);
 
 // FIXME: The full warning does look like this:
@@ -118,10 +120,24 @@ void caller_seven() {
 
   clang_analyzer_lifetime_bound(bind);
                                        // expected-warning@-1 {{Origin bound 
to some_number}}
-                                      // expected-warning@-1 {{Origin contains 
loan some_number}}
+                                       // expected-warning@-1 {{Origin 
contains loan some_number}}
   clang_analyzer_dump(bind);
 
 // The FIXME about the full warning applies to this text case as well.
 }
 
+// Function returns a reference and has two annotated parameters.
+int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]);
+
+void clang_analyzer_lifetime_bound(int&);
+
+void caller_eight() {
+  int first_num = 1;
+  int second_num = 2;
+  auto numbers = f(first_num, second_num);
 
+  clang_analyzer_lifetime_bound(numbers);
+                                          // expected-warning@-1 {{Origin 
bound to first_num}}
+                                          // expected-warning@-1 {{Origin 
contains loan first_num}}
+  clang_analyzer_dump(numbers);
+}

>From 5b83c6cdfb7f0c395f286d2ce45e2ad188a5b0ec Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Fri, 5 Jun 2026 18:27:00 +0200
Subject: [PATCH 6/9] Done with TODOs for testing, but they have to be cleaned.

---
 .../Checkers/LifetimeAnnotations.cpp          | 10 +++++-----
 clang/test/Analysis/lifetime-bound.cpp        | 20 ++++++++++++++++---
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 8baf4b0ba223b..ac7d147b916fe 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -88,17 +88,17 @@ void LifetimeAnnotations::checkPostCall(const CallEvent 
&Call,
 
 void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
                                      const char *NL, const char *Sep) const {
-  auto LBVal = State->get<LifetimeBoundMap>();
-  auto LBValTwo = State->get<LifetimeBoundMapVal>();
+  auto LBMap = State->get<LifetimeBoundMap>();
+  auto LBMapVal = State->get<LifetimeBoundMapVal>();
 
-  if (LBVal.isEmpty() && LBValTwo.isEmpty())
+  if (LBMap.isEmpty() && LBMapVal.isEmpty())
     return;
 
   Out << Sep << "LifetimeBound bindings:" << NL;
-  for (auto&& [RetValSym, ArgValRegion] : LBVal) {
+  for (auto&& [RetValSym, ArgValRegion] : LBMap) {
     Out << " Origin " << RetValSym << " contains Loan " << ArgValRegion << NL;
   }
-  for (auto&& [RetVal, ArgValRegion]: LBValTwo) {
+  for (auto&& [RetVal, ArgValRegion]: LBMapVal) {
     Out << " Origin " << RetVal << " contains Loan " << ArgValRegion << NL;
   }
 }
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
index 2acc1734644f2..5029e6589a6be 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -81,6 +81,20 @@ void caller_five() {
   clang_analyzer_dump(s);
 }
 
+// Free function with both annotated and non-annotated parameters.
+int& fn(int& f, int& s [[clang::lifetimebound]]) { return s; }
+
+void clang_analyzer_lifetime_bound(int&);
+
+void caller_six() {
+  int even = 50;
+  int odd = 55;
+  int& s = fn(even, odd);
+
+  clang_analyzer_lifetime_bound(s); // expected-warning {{Origin odd bound to 
odd}}
+  clang_analyzer_dump(s);
+}
+
 
 
 // These are the cases when the result of function calls are SymbolRefs.
@@ -90,7 +104,7 @@ int* foo(int* n [[clang::lifetimebound]]);
 
 void clang_analyzer_lifetime_bound(int*);
 
-void caller_six() {
+void caller_seven() {
   int y = 15;
   int* y_ptr = &y;
   auto* bind = foo(y_ptr);
@@ -114,7 +128,7 @@ int& func(int& some_number [[clang::lifetimebound]]);
 
 void clang_analyzer_lifetime_bound(int&);
 
-void caller_seven() {
+void caller_eight() {
   int f = 15;
   auto& bind = func(f);
 
@@ -131,7 +145,7 @@ int& f(int& a [[clang::lifetimebound]], int& b 
[[clang::lifetimebound]]);
 
 void clang_analyzer_lifetime_bound(int&);
 
-void caller_eight() {
+void caller_nine() {
   int first_num = 1;
   int second_num = 2;
   auto numbers = f(first_num, second_num);

>From e6efa13624dfecfbd33a8a9d9c164695ea4d6c5b Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Fri, 5 Jun 2026 20:59:28 +0200
Subject: [PATCH 7/9] Finished TODOs for the week.

---
 .../Checkers/LifetimeAnnotations.cpp          |  1 -
 clang/test/Analysis/lifetime-bound.cpp        | 72 ++++++-------------
 2 files changed, 23 insertions(+), 50 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index ac7d147b916fe..12cbf6cfe253f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -37,7 +37,6 @@ CallDescriptionMap<FnCheck> Callbacks = {
 
 void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
                                         CheckerContext &C) const {
-  llvm::errs() << "checkPostCall fired" << "\n";
   ProgramStateRef State = C.getState();
 
   const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call);
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
index 5029e6589a6be..230f6bd47e804 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -3,25 +3,25 @@
 // RUN: %clang_analyze_cc1 
-analyzer-checker=alpha.cplusplus.LifetimeAnnotations \
 // RUN:   -analyzer-config c++-container-inlining=false -verify %s
 
-void clang_analyzer_dump(...);
+struct A {};
 
-// These are the cases when the result of function calls are MemRegions.
+void clang_analyzer_lifetime_bound(int*);
+void clang_analyzer_lifetime_bound(int&);
+void clang_analyzer_lifetime_bound(A*);
+void clang_analyzer_lifetime_bound(A&);
 
-struct A {};
+// These are the cases when the result of function calls are MemRegions.
 
 // Ref type parameter annotated case
 struct X {
   int& choose(int& a [[clang::lifetimebound]]) { return a; }
 };
 
-void clang_analyzer_lifetime_bound(int&);
-
 void caller() {
   int v = 0;
   X obj;
   int& r = obj.choose(v);
-  clang_analyzer_lifetime_bound(r); // expected-warning {{Origin v bound to v}}
-  clang_analyzer_dump(r);
+  clang_analyzer_lifetime_bound(r); // expected-warning {{bound to v}}
 }
 
 // Obj ref type function return annotated case
@@ -30,14 +30,11 @@ struct Y {
   A& getA() [[clang::lifetimebound]] { return a; }
 };
 
-void clang_analyzer_lifetime_bound(A& a);
-
 void caller_two() {
   // Return statement is annotated case.
   Y y;
   A& f = y.getA();
-  clang_analyzer_lifetime_bound(f); // expected-warning {{Origin y.a bound to 
y}}
-  clang_analyzer_dump(f);
+  clang_analyzer_lifetime_bound(f); // expected-warning {{bound to y}}
 }
 
 // Obj ptr type function return annotated case
@@ -46,53 +43,41 @@ struct Z {
   A* getA() [[clang::lifetimebound]] { return &a; }
 };
 
-void clang_analyzer_lifetime_bound(A* a);
-
 void caller_three() {
   Z z;
   A* func = z.getA();
-  clang_analyzer_lifetime_bound(func); // expected-warning {{Origin z.a bound 
to z}}
-  clang_analyzer_dump(func);
+  clang_analyzer_lifetime_bound(func); // expected-warning {{bound to z}}
 }
 
 // Free function with annotated param and ref return
 int& foo(int& num [[clang::lifetimebound]]) { return num; }
 
-void clang_analyzer_lifetime_bound(int&);
-
 void caller_four() {
   int num = 5;
   int& s = foo(num);
-  clang_analyzer_lifetime_bound(s); // expected-warning {{Origin num bound to 
num}}
-  clang_analyzer_dump(s);
+  clang_analyzer_lifetime_bound(s); // expected-warning {{bound to num}}
 }
 
 // Free function with annotated param and ptr return
 int* boo(int* num [[clang::lifetimebound]]) { return num; }
 
-void clang_analyzer_lifetime_bound(int*);
-
 void caller_five() {
   int n = 55;
   int* n_ptr = &n;
   int* s = boo(n_ptr);
 
-  clang_analyzer_lifetime_bound(s); // expected-warning {{Origin n bound to n}}
-  clang_analyzer_dump(s);
+  clang_analyzer_lifetime_bound(s); // expected-warning {{bound to n}}
 }
 
 // Free function with both annotated and non-annotated parameters.
 int& fn(int& f, int& s [[clang::lifetimebound]]) { return s; }
 
-void clang_analyzer_lifetime_bound(int&);
-
 void caller_six() {
   int even = 50;
   int odd = 55;
   int& s = fn(even, odd);
 
-  clang_analyzer_lifetime_bound(s); // expected-warning {{Origin odd bound to 
odd}}
-  clang_analyzer_dump(s);
+  clang_analyzer_lifetime_bound(s); // expected-warning {{bound to odd}}
 }
 
 
@@ -102,18 +87,13 @@ void caller_six() {
 // Function returns ptr and has an annotated parameter
 int* foo(int* n [[clang::lifetimebound]]);
 
-void clang_analyzer_lifetime_bound(int*);
-
 void caller_seven() {
   int y = 15;
   int* y_ptr = &y;
   auto* bind = foo(y_ptr);
 
-  clang_analyzer_lifetime_bound(bind);
-                                       // expected-warning@-1 {{Origin bound 
to y}}
-                                       // expected-warning@-1 {{Origin 
contains loan y}}
-  clang_analyzer_dump(bind);
-
+  clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to y}}
+                                       // expected-warning@-1 {{contains loan 
y}}
 // FIXME: The full warning does look like this:
 // Origin SymRegion{conj_$5{int *, LC1, S847, #1}} bound to n
 // Origin conj_$5{int *, LC1, S847, #1} contains loan n
@@ -122,36 +102,30 @@ void caller_seven() {
 // This does not apply to the test cases above this test case.
 }
 
-
 // Function returns a reference and has an annotated parameter
 int& func(int& some_number [[clang::lifetimebound]]);
 
-void clang_analyzer_lifetime_bound(int&);
-
 void caller_eight() {
   int f = 15;
   auto& bind = func(f);
 
-  clang_analyzer_lifetime_bound(bind);
-                                       // expected-warning@-1 {{Origin bound 
to some_number}}
-                                       // expected-warning@-1 {{Origin 
contains loan some_number}}
-  clang_analyzer_dump(bind);
-
+  clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to f}}
+                                       // expected-warning@-1 {{contains loan 
f}}
 // The FIXME about the full warning applies to this text case as well.
 }
 
 // Function returns a reference and has two annotated parameters.
 int& f(int& a [[clang::lifetimebound]], int& b [[clang::lifetimebound]]);
 
-void clang_analyzer_lifetime_bound(int&);
-
 void caller_nine() {
   int first_num = 1;
   int second_num = 2;
-  auto numbers = f(first_num, second_num);
+  int& numbers = f(first_num, second_num);
+
+  clang_analyzer_lifetime_bound(numbers); // expected-warning {{bound to 
first_num}}
+                                          // expected-warning@-1 {{contains 
loan first_num}}
 
-  clang_analyzer_lifetime_bound(numbers);
-                                          // expected-warning@-1 {{Origin 
bound to first_num}}
-                                          // expected-warning@-1 {{Origin 
contains loan first_num}}
-  clang_analyzer_dump(numbers);
+// FIXME: Currently the callback only iterates until the first annotated 
parameter which
+// means the second annotation never gets read here. That is a clear bug. It 
should be fixed
+// in order to analyze all the parameters which are annotated.
 }

>From 89332c6f707135b2de7dbd1ea8b578879ffca119 Mon Sep 17 00:00:00 2001
From: Benedek Kaibas <[email protected]>
Date: Fri, 5 Jun 2026 21:03:21 +0200
Subject: [PATCH 8/9] Removed non-related fie.

---
 ...-AST-matching-to-get-containers-regi.patch | 130 ------------------
 1 file changed, 130 deletions(-)
 delete mode 100644 
0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch

diff --git a/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch 
b/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch
deleted file mode 100644
index 450bdda9f70dc..0000000000000
--- a/0001-analyzer-Removed-AST-matching-to-get-containers-regi.patch
+++ /dev/null
@@ -1,130 +0,0 @@
-From 00c5cf9e103a88ad9af285104f878c5f932ad8ff Mon Sep 17 00:00:00 2001
-From: benedekaibas <[email protected]>
-Date: Tue, 2 Jun 2026 13:13:46 +0200
-Subject: [PATCH] [analyzer] Removed AST matching to get containers' region and
- fixed small issues.
-
----
- .../StaticAnalyzer/Checkers/MoveChecker.cpp   | 40 +++++++++++--------
- .../test/Analysis/use-after-move-iterator.cpp | 24 +++++------
- 2 files changed, 36 insertions(+), 28 deletions(-)
-
-diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
-index 47e9c585054a..9ff2e90b618a 100644
---- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
-+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
-@@ -518,31 +518,29 @@ bool MoveChecker::evalCall(const CallEvent &Call, 
CheckerContext &C) const {
-   if (!StdMoveCall.matches(Call))
-     return false;
- 
--  const auto *BeginCall =
--      dyn_cast<CXXMemberCallExpr>(CE->getArg(0)->IgnoreImpCasts());
--  if (!BeginCall)
-+  const auto *POS = getIteratorPosition(State, Call.getArgSVal(0));
-+  if (!POS)
-     return false;
- 
--  const Expr *ContainerExpr = BeginCall->getImplicitObjectArgument();
--  const auto *DRE = dyn_cast<DeclRefExpr>(ContainerExpr->IgnoreImpCasts());
--  if (!DRE)
-+  const MemRegion *ContainerRegion = POS->getContainer();
-+  if (!ContainerRegion)
-     return false;
- 
--  const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
--  if (!VD)
-+  const auto *TypedRegion =
-+      dyn_cast_if_present<TypedValueRegion>(ContainerRegion);
-+  if (!TypedRegion)
-     return false;
- 
--  const MemRegion *Region =
--      State->getLValue(VD, C.getLocationContext()).getAsRegion();
--  if (!Region)
--    return false;
-+  QualType ObjTy = TypedRegion->getValueType();
- 
--  const CXXRecordDecl *RD = ContainerExpr->getType()->getAsCXXRecordDecl();
-+  const auto *RD = ObjTy->getAsCXXRecordDecl();
-   if (!RD)
-     return false;
- 
--  ObjectKind OK = classifyObject(State, Region, RD);
-+  ObjectKind OK = classifyObject(State, ContainerRegion, RD);
- 
-+  // FIXME: Also apply getIteratorPosition from IteratorModeling to recover 
the
-+  // destination region instead of doing AST pattern matching.
-   const auto *BackInsCall = 
dyn_cast<CallExpr>(CE->getArg(2)->IgnoreImpCasts());
-   if (!BackInsCall)
-     return false;
-@@ -573,7 +571,8 @@ bool MoveChecker::evalCall(const CallEvent &Call, 
CheckerContext &C) const {
-                                    /*CausesPointerEscape=*/false);
- 
-   if (shouldBeTracked(OK))
--    State = State->set<TrackedContentsMap>(Region, RegionState::getMoved());
-+    State = State->set<TrackedContentsMap>(ContainerRegion,
-+                                           RegionState::getMoved());
- 
-   C.addTransition(State);
-   return true;
-@@ -736,10 +735,19 @@ void MoveChecker::checkPreCall(const CallEvent &Call, 
CheckerContext &C) const {
- 
-     if (const auto *POS = getIteratorPosition(State, Val)) {
-       const MemRegion *ContainerRegion = POS->getContainer();
-+      if (!ContainerRegion)
-+        return;
-+
-+      const auto *TypedRegion =
-+          dyn_cast_if_present<TypedValueRegion>(ContainerRegion);
-+      if (!TypedRegion)
-+        return;
- 
--      const auto *TypedRegion = cast<TypedValueRegion>(ContainerRegion);
-       QualType ObjTy = TypedRegion->getValueType();
-       const auto *R = ObjTy->getAsCXXRecordDecl();
-+      if (!R)
-+        return;
-+
-       if (State->get<TrackedContentsMap>(ContainerRegion)) {
-         ExplodedNode *N = tryToReportBug(ContainerRegion, R, C, MK_FunCall);
-         if (!N || N->isSink())
-diff --git a/clang/test/Analysis/use-after-move-iterator.cpp 
b/clang/test/Analysis/use-after-move-iterator.cpp
-index 50dd7e57b42e..2357be3a6bb3 100644
---- a/clang/test/Analysis/use-after-move-iterator.cpp
-+++ b/clang/test/Analysis/use-after-move-iterator.cpp
-@@ -10,20 +10,20 @@
- // IteratorModeling is enabled.
- 
//===----------------------------------------------------------------------===//
- 
--void iteratorDerefSource() {
-+std::string iteratorDeref(int rng) {
-   std::list<std::string> l1;
-   l1.push_back("l1");
-   std::list<std::string> l2;
- 
--  std::move(l1.begin(), l1.end(), std::back_inserter(l2));
--  *l1.cbegin(); // expected-warning {{Method called on moved-from object 
'l1'}}
--}
--
--void iteratorDerefDest() {
--  std::list<std::string> l1;
--  l1.push_back("l1");
--  std::list<std::string> l2;
--
--  std::move(l1.begin(), l1.end(), std::back_inserter(l2));
--  *l2.cbegin(); // no-warning
-+  switch (rng) {
-+    case 10: {
-+      std::move(l1.begin(), l1.end(), std::back_inserter(l2));
-+      return *l1.cbegin(); // expected-warning {{Method called on moved-from 
object 'l1'}}
-+    }
-+    case 20: {
-+      std::move(l1.begin(), l1.end(), std::back_inserter(l2));
-+      return *l2.cbegin(); // no-warning: only l1 was invalidated and not l2!
-+    }
-+  }
-+  return 0;
- }
--- 
-2.43.0
-

>From 9733f253c9b8a218cd8de83ef4a922d8810fd6fb Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Mon, 8 Jun 2026 12:31:13 +0200
Subject: [PATCH 9/9] Addressed mentors' issues.

---
 .../Checkers/LifetimeAnnotations.cpp           | 14 ++++++--------
 clang/test/Analysis/lifetime-bound.cpp         | 18 ++++--------------
 2 files changed, 10 insertions(+), 22 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
index 12cbf6cfe253f..01dbb0c115bc3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeAnnotations.cpp
@@ -10,7 +10,6 @@
 
 using namespace clang;
 using namespace ento;
-using namespace clang::lifetimes;
 
 REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef,
                                const MemRegion *);
@@ -29,7 +28,7 @@ class LifetimeAnnotations : public Checker<check::PostCall, 
eval::Call> {
 };
 
 typedef void (LifetimeAnnotations::*FnCheck)(const CallEvent &Call, const 
CallExpr *,
-                                              CheckerContext &) const;
+                                              CheckerContext &C) const;
 CallDescriptionMap<FnCheck> Callbacks = {
   {{CDM::SimpleFunc, {"clang_analyzer_lifetime_bound"}},
     &LifetimeAnnotations::analyzerLifetimeBound},
@@ -39,7 +38,7 @@ void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
                                         CheckerContext &C) const {
   ProgramStateRef State = C.getState();
 
-  const auto *FC = dyn_cast_if_present<AnyFunctionCall>(&Call);
+  const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
   if (!FC)
     return;
 
@@ -67,17 +66,17 @@ void LifetimeAnnotations::checkPostCall(const CallEvent 
&Call,
     if (const MemRegion *ArgValRegion = ArgVal.getAsRegion()) {
       if (RetValSym)
         State = State->set<LifetimeBoundMap>(RetValSym, ArgValRegion);
-      if (const MemRegion *RetValRegion = RetVal.getAsRegion())
+      else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
         State = State->set<LifetimeBoundMapVal>(RetValRegion, ArgValRegion);
     }
   }
 
   if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
-    if (implicitObjectParamIsLifetimeBound(FD)) {
+    if (clang::lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
       if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
         if (RetValSym)
           State = State->set<LifetimeBoundMap>(RetValSym, AttrRegion);
-        if (const MemRegion *RetValRegion = RetVal.getAsRegion())
+        else if (const MemRegion *RetValRegion = RetVal.getAsRegion())
           State = State->set<LifetimeBoundMapVal>(RetValRegion, AttrRegion);
       }
     }
@@ -104,7 +103,7 @@ void LifetimeAnnotations::printState(raw_ostream &Out, 
ProgramStateRef State,
 
 bool LifetimeAnnotations::evalCall(const CallEvent &Call, CheckerContext &C) 
const {
 
-  const auto *CE = llvm::dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
+  const auto *CE = dyn_cast_if_present<CallExpr>(Call.getOriginExpr());
   if (!CE)
     return false;
 
@@ -114,7 +113,6 @@ bool LifetimeAnnotations::evalCall(const CallEvent &Call, 
CheckerContext &C) con
 
   (this->*(*Handler))(Call, CE, C);
   return true;
-  C.addTransition(C.getState());
 }
 
 void LifetimeAnnotations::analyzerLifetimeBound(const CallEvent &Call, const 
CallExpr *CE, CheckerContext &C) const {
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
index 230f6bd47e804..1d9b4eabcee04 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -77,7 +77,7 @@ void caller_six() {
   int odd = 55;
   int& s = fn(even, odd);
 
-  clang_analyzer_lifetime_bound(s); // expected-warning {{bound to odd}}
+  clang_analyzer_lifetime_bound(s); // expected-warning {{Origin odd bound to 
odd}}
 }
 
 
@@ -92,14 +92,7 @@ void caller_seven() {
   int* y_ptr = &y;
   auto* bind = foo(y_ptr);
 
-  clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to y}}
-                                       // expected-warning@-1 {{contains loan 
y}}
-// FIXME: The full warning does look like this:
-// Origin SymRegion{conj_$5{int *, LC1, S847, #1}} bound to n
-// Origin conj_$5{int *, LC1, S847, #1} contains loan n
-// Since the conj sym number and the ID can change across runs I have decided 
to just include
-// string parts of the error message since that is the only consistent part of 
the emitted report.
-// This does not apply to the test cases above this test case.
+  clang_analyzer_lifetime_bound(bind); // expected-warning {{contains loan y}}
 }
 
 // Function returns a reference and has an annotated parameter
@@ -109,9 +102,7 @@ void caller_eight() {
   int f = 15;
   auto& bind = func(f);
 
-  clang_analyzer_lifetime_bound(bind); // expected-warning {{bound to f}}
-                                       // expected-warning@-1 {{contains loan 
f}}
-// The FIXME about the full warning applies to this text case as well.
+  clang_analyzer_lifetime_bound(bind); // expected-warning {{contains loan f}}
 }
 
 // Function returns a reference and has two annotated parameters.
@@ -122,8 +113,7 @@ void caller_nine() {
   int second_num = 2;
   int& numbers = f(first_num, second_num);
 
-  clang_analyzer_lifetime_bound(numbers); // expected-warning {{bound to 
first_num}}
-                                          // expected-warning@-1 {{contains 
loan first_num}}
+  clang_analyzer_lifetime_bound(numbers); // expected-warning {{contains loan 
first_num}}
 
 // FIXME: Currently the callback only iterates until the first annotated 
parameter which
 // means the second annotation never gets read here. That is a clear bug. It 
should be fixed

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

Reply via email to