https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/170005

>From a71af86558edcaeb1876f0ba3550b4ef24c05a65 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <[email protected]>
Date: Sat, 29 Nov 2025 15:07:40 +0000
Subject: [PATCH] Implicit lifetimebound for std namespace

---
 .../Analyses/LifetimeSafety/FactsGenerator.h  |   1 +
 .../LifetimeSafety/LifetimeAnnotations.h      |  14 ++
 .../LifetimeSafety/FactsGenerator.cpp         |  19 +-
 .../LifetimeSafety/LifetimeAnnotations.cpp    |  82 ++++++++
 clang/lib/Analysis/LifetimeSafety/Origins.cpp |   3 +
 clang/lib/Sema/CheckExprLifetime.cpp          |  64 +------
 .../unittests/Analysis/LifetimeSafetyTest.cpp | 181 ++++++++++++++++++
 7 files changed, 298 insertions(+), 66 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 5b5626020e772..d3ef72a8d12dd 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -48,6 +48,7 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE);
   void VisitInitListExpr(const InitListExpr *ILE);
   void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
+  void VisitExprWithCleanups(const ExprWithCleanups *EC);
 
 private:
   OriginList *getOriginsList(const ValueDecl &D);
diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 1a16fb82f9a84..8e26a4d41a957 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -38,6 +38,20 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl 
*CMD);
 /// method or because it's a normal assignment operator.
 bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
 
+// Returns true if the implicit object argument (this) of a method call should
+// be tracked for GSL lifetime analysis. This applies to STL methods that 
return
+// pointers or references that depend on the lifetime of the object, such as
+// container iterators (begin, end), data accessors (c_str, data, get), or
+// element accessors (operator[], operator*, front, back, at).
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee);
+
+// Returns true if the first argument of a free function should be tracked for
+// GSL lifetime analysis. This applies to STL free functions that take a 
pointer
+// to a GSL Owner or Pointer and return a pointer or reference that depends on
+// the lifetime of the argument, such as std::begin, std::data, std::get, or
+// std::any_cast.
+bool shouldTrackFirstArgument(const FunctionDecl *FD);
+
 // Tells whether the type is annotated with [[gsl::Pointer]].
 bool isGslPointerType(QualType QT);
 // Tells whether the type is annotated with [[gsl::Owner]].
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 460be3ad99347..6883d31c4a12b 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -14,6 +14,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include "clang/Analysis/Analyses/PostOrderCFGView.h"
 #include "llvm/Support/Casting.h"
+#include "llvm/Support/Debug.h"
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/TimeProfiler.h"
 
@@ -78,10 +79,11 @@ void FactsGenerator::run() {
     EscapesInCurrentBlock.clear();
     for (unsigned I = 0; I < Block->size(); ++I) {
       const CFGElement &Element = Block->Elements[I];
-      if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
+      if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) {
         Visit(CS->getStmt());
-      else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
-                   Element.getAs<CFGLifetimeEnds>())
+        DEBUG_WITH_TYPE("PrintCFG", CS->getStmt()->dumpColor());
+      } else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
+                     Element.getAs<CFGLifetimeEnds>())
         handleLifetimeEnds(*LifetimeEnds);
     }
     CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
@@ -328,6 +330,12 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
   }
 }
 
+void FactsGenerator::VisitExprWithCleanups(const ExprWithCleanups *EC) {
+  if (hasOrigins(EC)) {
+    killAndFlowOrigin(*EC, *EC->getSubExpr());
+  }
+}
+
 void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
   /// TODO: Handle loans to temporaries.
   const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
@@ -387,11 +395,14 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
         Method && Method->isInstance()) {
       if (I == 0)
         // For the 'this' argument, the attribute is on the method itself.
-        return implicitObjectParamIsLifetimeBound(Method);
+        return implicitObjectParamIsLifetimeBound(Method) ||
+               shouldTrackImplicitObjectArg(Method);
       if ((I - 1) < Method->getNumParams())
         // For explicit arguments, find the corresponding parameter
         // declaration.
         PVD = Method->getParamDecl(I - 1);
+    } else if (I == 0 && shouldTrackFirstArgument(FD)) {
+      return true;
     } else if (I < FD->getNumParams()) {
       // For free functions or static methods.
       PVD = FD->getParamDecl(I);
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp 
b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 54e343fc2ee5e..860aa5373a32c 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -71,6 +71,88 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl 
*FD) {
   return isNormalAssignmentOperator(FD);
 }
 
+// Decl::isInStdNamespace will return false for iterators in some STL
+// implementations due to them being defined in a namespace outside of the std
+// namespace.
+static bool isInStlNamespace(const Decl *D) {
+  const DeclContext *DC = D->getDeclContext();
+  if (!DC)
+    return false;
+  if (const auto *ND = dyn_cast<NamespaceDecl>(DC))
+    if (const IdentifierInfo *II = ND->getIdentifier()) {
+      StringRef Name = II->getName();
+      if (Name.size() >= 2 && Name.front() == '_' &&
+          (Name[1] == '_' || isUppercase(Name[1])))
+        return true;
+    }
+
+  return DC->isStdNamespace();
+}
+
+static bool isPointerLikeType(QualType QT) {
+  return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
+}
+
+bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
+  if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
+    if (isGslPointerType(Conv->getConversionType()) &&
+        Callee->getParent()->hasAttr<OwnerAttr>())
+      return true;
+  if (!isInStlNamespace(Callee->getParent()))
+    return false;
+  if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
+      !isGslOwnerType(Callee->getFunctionObjectParameterType()))
+    return false;
+  if (isPointerLikeType(Callee->getReturnType())) {
+    if (!Callee->getIdentifier())
+      return false;
+    return llvm::StringSwitch<bool>(Callee->getName())
+        .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
+        .Cases({"end", "rend", "cend", "crend"}, true)
+        .Cases({"c_str", "data", "get"}, true)
+        // Map and set types.
+        .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
+        .Default(false);
+  }
+  if (Callee->getReturnType()->isReferenceType()) {
+    if (!Callee->getIdentifier()) {
+      auto OO = Callee->getOverloadedOperator();
+      if (!Callee->getParent()->hasAttr<OwnerAttr>())
+        return false;
+      return OO == OverloadedOperatorKind::OO_Subscript ||
+             OO == OverloadedOperatorKind::OO_Star;
+    }
+    return llvm::StringSwitch<bool>(Callee->getName())
+        .Cases({"front", "back", "at", "top", "value"}, true)
+        .Default(false);
+  }
+  return false;
+}
+
+bool shouldTrackFirstArgument(const FunctionDecl *FD) {
+  if (!FD->getIdentifier() || FD->getNumParams() != 1)
+    return false;
+  const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
+  if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
+    return false;
+  if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
+    return false;
+  if (FD->getReturnType()->isPointerType() ||
+      isGslPointerType(FD->getReturnType())) {
+    return llvm::StringSwitch<bool>(FD->getName())
+        .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
+        .Cases({"end", "rend", "cend", "crend"}, true)
+        .Case("data", true)
+        .Default(false);
+  }
+  if (FD->getReturnType()->isReferenceType()) {
+    return llvm::StringSwitch<bool>(FD->getName())
+        .Cases({"get", "any_cast"}, true)
+        .Default(false);
+  }
+  return false;
+}
+
 template <typename T> static bool isRecordWithAttr(QualType Type) {
   auto *RD = Type->getAsCXXRecordDecl();
   if (!RD)
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index ac8d8041f600b..65fc0c21e11b8 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -11,6 +11,7 @@
 #include "clang/AST/Attr.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclTemplate.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/AST/TypeBase.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 
@@ -79,6 +80,8 @@ OriginList *OriginManager::getOrCreateList(const ValueDecl 
*D) {
 OriginList *OriginManager::getOrCreateList(const Expr *E, size_t Depth) {
   if (auto *ParenIgnored = E->IgnoreParens(); ParenIgnored != E)
     return getOrCreateList(ParenIgnored);
+  if (auto *EC = dyn_cast<ExprWithCleanups>(E))
+    return getOrCreateList(EC->getSubExpr(), Depth);
 
   if (!hasOrigins(E))
     return nullptr;
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index c91ca751984c9..26e4d75b1fa49 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -320,66 +320,6 @@ static bool isStdInitializerListOfPointer(const RecordDecl 
*RD) {
   return false;
 }
 
-static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
-  if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
-    if (isGslPointerType(Conv->getConversionType()) &&
-        Callee->getParent()->hasAttr<OwnerAttr>())
-      return true;
-  if (!isInStlNamespace(Callee->getParent()))
-    return false;
-  if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
-      !isGslOwnerType(Callee->getFunctionObjectParameterType()))
-    return false;
-  if (isPointerLikeType(Callee->getReturnType())) {
-    if (!Callee->getIdentifier())
-      return false;
-    return llvm::StringSwitch<bool>(Callee->getName())
-        .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
-        .Cases({"end", "rend", "cend", "crend"}, true)
-        .Cases({"c_str", "data", "get"}, true)
-        // Map and set types.
-        .Cases({"find", "equal_range", "lower_bound", "upper_bound"}, true)
-        .Default(false);
-  }
-  if (Callee->getReturnType()->isReferenceType()) {
-    if (!Callee->getIdentifier()) {
-      auto OO = Callee->getOverloadedOperator();
-      if (!Callee->getParent()->hasAttr<OwnerAttr>())
-        return false;
-      return OO == OverloadedOperatorKind::OO_Subscript ||
-             OO == OverloadedOperatorKind::OO_Star;
-    }
-    return llvm::StringSwitch<bool>(Callee->getName())
-        .Cases({"front", "back", "at", "top", "value"}, true)
-        .Default(false);
-  }
-  return false;
-}
-
-static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
-  if (!FD->getIdentifier() || FD->getNumParams() != 1)
-    return false;
-  const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
-  if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
-    return false;
-  if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
-    return false;
-  if (FD->getReturnType()->isPointerType() ||
-      isGslPointerType(FD->getReturnType())) {
-    return llvm::StringSwitch<bool>(FD->getName())
-        .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
-        .Cases({"end", "rend", "cend", "crend"}, true)
-        .Case("data", true)
-        .Default(false);
-  }
-  if (FD->getReturnType()->isReferenceType()) {
-    return llvm::StringSwitch<bool>(FD->getName())
-        .Cases({"get", "any_cast"}, true)
-        .Default(false);
-  }
-  return false;
-}
-
 // Returns true if the given constructor is a copy-like constructor, such as
 // `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
 static bool isCopyLikeConstructor(const CXXConstructorDecl *Ctor) {
@@ -564,7 +504,7 @@ static void visitFunctionCallArguments(IndirectLocalPath 
&Path, Expr *Call,
       VisitLifetimeBoundArg(Callee, ObjectArg);
     else if (EnableGSLAnalysis) {
       if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
-          CME && shouldTrackImplicitObjectArg(CME))
+          CME && lifetimes::shouldTrackImplicitObjectArg(CME))
         VisitGSLPointerArg(Callee, ObjectArg);
     }
   }
@@ -605,7 +545,7 @@ static void visitFunctionCallArguments(IndirectLocalPath 
&Path, Expr *Call,
       VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
     else if (EnableGSLAnalysis && I == 0) {
       // Perform GSL analysis for the first argument
-      if (shouldTrackFirstArgument(CanonCallee)) {
+      if (lifetimes::shouldTrackFirstArgument(CanonCallee)) {
         VisitGSLPointerArg(CanonCallee, Arg);
       } else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
                  Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp 
b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index ddc9cb602fc26..553354f052875 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1597,5 +1597,186 @@ TEST_F(LifetimeAnalysisTest, 
TrivialClassDestructorsUAR) {
   EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
 }
 
+// ========================================================================= //
+//                    Tests for shouldTrackImplicitObjectArg
+// ========================================================================= //
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_STLBegin) {
+  SetupTest(R"(
+    namespace std {
+      template<typename T>
+      struct vector {
+        struct iterator {};
+        iterator begin();
+      };
+    }
+    
+    void target() {
+      std::vector<int> vec;
+      auto it = vec.begin();
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
+}
+
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_OwnerDeref) {
+  SetupTest(R"(
+    namespace std {
+      template<typename T>
+      struct optional {
+        T& operator*();
+      };
+    }
+    
+    void target() {
+      std::optional<int> opt;
+      int& r = *opt;
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_Value) {
+  SetupTest(R"(
+    namespace std {
+      template<typename T>
+      struct optional {
+        T& value();
+      };
+    }
+    
+    void target() {
+      std::optional<int> opt;
+      int& r = opt.value();
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_UniquePtr_Get) {
+  SetupTest(R"(
+    namespace std {
+      template<typename T>
+      struct unique_ptr {
+        T *get() const;
+      };
+    }
+    
+    void target() {
+      std::unique_ptr<int> up;
+      int* r = up.get();
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("r"), HasLoansTo({"up"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_ConversionOperator) {
+  SetupTest(R"(
+    struct [[gsl::Pointer(int)]] IntPtr {
+      int& operator*();
+    };
+    
+    struct [[gsl::Owner(int)]] OwnerWithConversion {
+      operator IntPtr();
+    };
+    
+    void target() {
+      OwnerWithConversion owner;
+      IntPtr ptr = owner;
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("ptr"), HasLoansTo({"owner"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_MapFind) {
+  SetupTest(R"(
+    namespace std {
+      template<typename K, typename V>
+      struct map {
+        struct iterator {};
+        iterator find(const K&);
+      };
+    }
+
+    void target() {
+      std::map<int, int> m;
+      auto it = m.find(42);
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("it"), HasLoansTo({"m"}, "p1"));
+}
+
+// ========================================================================= //
+//                    Tests for shouldTrackFirstArgument
+// ========================================================================= //
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdBegin) {
+  SetupTest(R"(
+    namespace std {
+      template<typename T>
+      struct vector {
+        struct iterator {};
+        iterator begin();
+      };
+      
+      template<typename C>
+      auto begin(C& c) -> decltype(c.begin());
+    }
+    
+    void target() {
+      std::vector<int> vec;
+      auto it = std::begin(vec);
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdData) {
+  SetupTest(R"(
+    namespace std {
+      template<typename T>
+      struct vector {
+        const T* data() const;
+      };
+      
+      template<typename C>
+      auto data(C& c) -> decltype(c.data());
+    }
+    
+    void target() {
+      std::vector<int> vec;
+      const int* p = std::data(vec);
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("p"), HasLoansTo({"vec"}, "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdAnyCast) {
+  SetupTest(R"(
+    namespace std {
+      struct any {};
+      
+      template<typename T>
+      T any_cast(const any& op);
+    }
+
+    void target() {
+      std::any a;
+      int& r = std::any_cast<int&>(a);
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(Origin("r"), HasLoansTo({"a"}, "p1"));
+}
+
 } // anonymous namespace
 } // namespace clang::lifetimes::internal

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

Reply via email to