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/2] 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/2] 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;
   }
 }
 

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

Reply via email to