[clang] [clang] Introduce [[clang::lifetime_capture_by(X)]] (PR #111499)

Utkarsh Saxena via cfe-commits cfe-commits at lists.llvm.org
Tue Oct 15 06:28:19 PDT 2024


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

>From 4951a7b9b87f9800bc3629bd44f65141ba98c6b0 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 8 Oct 2024 08:19:56 +0000
Subject: [PATCH 1/8] start working on lifetime capture

---
 clang/include/clang/Basic/Attr.td             |  40 +++++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  16 +++
 clang/include/clang/Sema/Sema.h               |   7 ++
 clang/lib/AST/TypePrinter.cpp                 |  21 ++++
 clang/lib/Sema/CheckExprLifetime.cpp          |  54 +++++++---
 clang/lib/Sema/CheckExprLifetime.h            |  26 +++--
 clang/lib/Sema/SemaChecking.cpp               |  27 +++++
 clang/lib/Sema/SemaDecl.cpp                   |   1 +
 clang/lib/Sema/SemaDeclAttr.cpp               | 102 ++++++++++++++++++
 clang/lib/Sema/SemaExpr.cpp                   |   4 +-
 clang/lib/Sema/SemaInit.cpp                   |   2 +-
 clang/lib/Sema/SemaOverload.cpp               |   4 +-
 clang/lib/Sema/SemaType.cpp                   |  13 +++
 clang/test/SemaCXX/attr-lifetimebound.cpp     |  94 ++++++++++++++++
 14 files changed, 387 insertions(+), 24 deletions(-)

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 35b9716e13ff21..4dcb143b91f84f 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1869,6 +1869,46 @@ def LifetimeBound : DeclOrTypeAttr {
   let SimpleHandler = 1;
 }
 
+def LifetimeCaptureBy : DeclOrTypeAttr {
+  let Spellings = [Clang<"lifetime_capture_by", 0>];
+  let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
+  let Args = [VariadicParamOrParamIdxArgument<"Params">];
+  let Documentation = [LifetimeBoundDocs];
+  let LangOpts = [CPlusPlus];
+
+  // let SimpleHandler = 1;
+  // let LateParsed = LateAttrParseStandard;
+  // let HasCustomParsing = 1;
+  // let ParseArgumentsAsUnevaluated = 1;
+
+  let AdditionalMembers = [{
+private:
+  SmallVector<IdentifierInfo*, 1> ArgIdents;
+  SmallVector<SourceLocation, 1> ArgLocs;
+
+public:
+  static const int INVALID = -2;
+  static const int UNKNOWN = -1;
+  static const int GLOBAL = -1;
+  static const int THIS = 0;
+
+  void setArgs(SmallVector<IdentifierInfo*, 1> Idents,
+               SmallVector<SourceLocation, 1> Locs) { 
+    assert(Idents.size() == Locs.size());
+    assert(Idents.size() == params_Size);
+    ArgIdents = std::move(Idents);
+    ArgLocs = std::move(Locs);
+  }
+  
+  const SmallVector<IdentifierInfo*, 1>& getArgIdents() const { return ArgIdents; }
+  const SmallVector<SourceLocation, 1>& getArgLocs() const { return ArgLocs; }
+  void setParamIdx(size_t Idx, int Val) { 
+    assert(Idx < params_Size);
+    params_[Idx] = Val;
+  }
+}];
+}
+
 def TrivialABI : InheritableAttr {
   // This attribute does not have a C [[]] spelling because it requires the
   // CPlusPlus language option.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e8b64f3c5a0187..ea034af77c3dbe 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3382,6 +3382,18 @@ def err_callback_callee_is_variadic : Error<
   "'callback' attribute callee may not be variadic">;
 def err_callback_implicit_this_not_available : Error<
   "'callback' argument at position %0 references unavailable implicit 'this'">;
+
+def err_capture_by_attribute_multiple : Error<
+  "multiple 'lifetime_capture' attributes specified">;
+def err_capture_by_attribute_no_entity : Error<
+  "'lifetime_capture_by' attribute specifies no capturing entity">;
+def err_capture_by_implicit_this_not_available : Error<
+  "'lifetime_capture_by' argument references unavailable implicit 'this'">;
+def err_capture_by_attribute_argument_unknown : Error<
+  "'lifetime_capture_by' attribute argument %0 is not a known function parameter"
+  ". Must be a function parameter of one of 'this', 'global' or 'unknown'">;
+def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">;
+
 def err_init_method_bad_return_type : Error<
   "init methods must return an object pointer type, not %0">;
 def err_attribute_invalid_size : Error<
@@ -10185,6 +10197,10 @@ def warn_dangling_pointer_assignment : Warning<
    "object backing the pointer %0 "
    "will be destroyed at the end of the full-expression">,
    InGroup<DanglingAssignment>;
+def warn_dangling_reference_captured : Warning<
+   "object captured by the '%0' "
+   "will be destroyed at the end of the full-expression">,
+   InGroup<DanglingAssignment>;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 0809ac1b144ef6..a26b3fa8755161 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1830,6 +1830,10 @@ class Sema final : public SemaBase {
   /// Add [[gsl::Pointer]] attributes for std:: types.
   void inferGslPointerAttribute(TypedefNameDecl *TD);
 
+  LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
+                                                    StringRef ParamName);
+  void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);
+
   /// Add _Nullable attributes for std:: types.
   void inferNullableClassAttribute(CXXRecordDecl *CRD);
 
@@ -2384,6 +2388,9 @@ class Sema final : public SemaBase {
   bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res);
   bool BuiltinVectorToScalarMath(CallExpr *TheCall);
 
+  void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
+                              const Expr *ThisArg, ArrayRef<const Expr *> Args);
+
   /// Handles the checks for format strings, non-POD arguments to vararg
   /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
   /// attributes and AArch64 SME attributes.
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index ca75bb97c158e1..02bece7cce51d7 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -12,6 +12,7 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Attr.h"
+#include "clang/AST/Attrs.inc"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
@@ -25,6 +26,7 @@
 #include "clang/AST/TextNodeDumper.h"
 #include "clang/AST/Type.h"
 #include "clang/Basic/AddressSpaces.h"
+#include "clang/Basic/AttrKinds.h"
 #include "clang/Basic/ExceptionSpecificationType.h"
 #include "clang/Basic/IdentifierTable.h"
 #include "clang/Basic/LLVM.h"
@@ -1907,6 +1909,24 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
     OS << " [[clang::lifetimebound]]";
     return;
   }
+  if (T->getAttrKind() == attr::LifetimeCaptureBy) {
+    OS << " [[clang::lifetime_capture_by(...)";
+    // const LifetimeCaptureByAttr* A= T->getAs<LifetimeCaptureByAttr>();
+    // bool valid = true;
+    // for (int I : A->params())
+    //   valid &= I != -2;
+    // if (valid) {
+    //   OS << "invalid)";
+    //   return;
+    // }
+    // for (size_t I = 0; I < A->params_size(); ++I) {
+    //   OS << A->getArgIdents()[I]->getName()
+    //      << "(idx: " << *(A->params_begin() + I) << ")";
+    //   if (I != A->params_size() - 1)
+    //     OS << ", ";
+    // }
+    return;
+  }
 
   // The printing of the address_space attribute is handled by the qualifier
   // since it is still stored in the qualifier. Return early to prevent printing
@@ -1966,6 +1986,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::SizedBy:
   case attr::SizedByOrNull:
   case attr::LifetimeBound:
+  case attr::LifetimeCaptureBy:
   case attr::TypeNonNull:
   case attr::TypeNullable:
   case attr::TypeNullableResult:
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index c98fbca849faba..3e2e3565eb2337 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -41,10 +41,14 @@ enum LifetimeKind {
   /// a default member initializer), the program is ill-formed.
   LK_MemInitializer,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity is a pointer and we assign the address of a temporary
   /// object to it.
   LK_Assignment,
+
+  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// because the entity may capture the reference to a temporary object.
+  LK_LifetimeCapture,
 };
 using LifetimeResult =
     llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
@@ -189,6 +193,7 @@ struct IndirectLocalPathEntry {
     VarInit,
     LValToRVal,
     LifetimeBoundCall,
+    LifetimeCapture,
     TemporaryCopy,
     LambdaCaptureInit,
     GslReferenceInit,
@@ -898,6 +903,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
     case IndirectLocalPathEntry::AddressOf:
     case IndirectLocalPathEntry::LValToRVal:
     case IndirectLocalPathEntry::LifetimeBoundCall:
+    case IndirectLocalPathEntry::LifetimeCapture:
     case IndirectLocalPathEntry::TemporaryCopy:
     case IndirectLocalPathEntry::GslReferenceInit:
     case IndirectLocalPathEntry::GslPointerInit:
@@ -928,6 +934,7 @@ static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
     case IndirectLocalPathEntry::VarInit:
     case IndirectLocalPathEntry::AddressOf:
     case IndirectLocalPathEntry::LifetimeBoundCall:
+    case IndirectLocalPathEntry::LifetimeCapture:
       continue;
     case IndirectLocalPathEntry::GslPointerInit:
     case IndirectLocalPathEntry::GslReferenceInit:
@@ -948,11 +955,11 @@ static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
 }
 
 static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
-                                           const AssignedEntity &Entity) {
+                                           const CapturingEntity &Entity) {
   bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
       diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
   return (EnableGSLAssignmentWarnings &&
-          (isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
+          (isRecordWithAttr<PointerAttr>(Entity.Expression->getType()) ||
            isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
 }
 
@@ -960,9 +967,10 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
                                   const InitializedEntity *InitEntity,
                                   const InitializedEntity *ExtendingEntity,
                                   LifetimeKind LK,
-                                  const AssignedEntity *AEntity, Expr *Init) {
-  assert((AEntity && LK == LK_Assignment) ||
-         (InitEntity && LK != LK_Assignment));
+                                  const CapturingEntity *CEntity, Expr *Init) {
+  assert(InitEntity || CEntity);
+  assert(!CEntity || LK == LK_Assignment || LK == LK_LifetimeCapture);
+  assert(!InitEntity || LK != LK_Assignment);
   // If this entity doesn't have an interesting lifetime, don't bother looking
   // for temporaries within its initializer.
   if (LK == LK_FullExpression)
@@ -1046,6 +1054,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
       break;
     }
 
+    case LK_LifetimeCapture: {
+      if (!MTE)
+        return false;
+      assert(shouldLifetimeExtendThroughPath(Path) ==
+                 PathLifetimeKind::NoExtend &&
+             "No lifetime extension for in function calls");
+      SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
+          << CEntity->Expression << DiagRange;
+      return false;
+    }
+
     case LK_Assignment: {
       if (!MTE || pathContainsInit(Path))
         return false;
@@ -1056,7 +1075,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
                    IsGslPtrValueFromGslTempOwner
                        ? diag::warn_dangling_lifetime_pointer_assignment
                        : diag::warn_dangling_pointer_assignment)
-          << AEntity->LHS << DiagRange;
+          << CEntity->Expression << DiagRange;
       return false;
     }
     case LK_MemInitializer: {
@@ -1199,6 +1218,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
         break;
 
       case IndirectLocalPathEntry::LifetimeBoundCall:
+      case IndirectLocalPathEntry::LifetimeCapture:
       case IndirectLocalPathEntry::TemporaryCopy:
       case IndirectLocalPathEntry::GslPointerInit:
       case IndirectLocalPathEntry::GslReferenceInit:
@@ -1243,8 +1263,10 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   };
 
   llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
-  if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
+  if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *CEntity))
     Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});
+  else if (LK == LK_LifetimeCapture)
+    Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});
 
   if (Init->isGLValue())
     visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
@@ -1256,7 +1278,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
         /*RevisitSubinits=*/!InitEntity);
 }
 
-void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
+void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
                        Expr *Init) {
   auto LTResult = getEntityLifetime(&Entity);
   LifetimeKind LK = LTResult.getInt();
@@ -1265,12 +1287,12 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
                         /*AEntity*/ nullptr, Init);
 }
 
-void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
-                       Expr *Init) {
+void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
+                             Expr *RHS) {
   bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
       diag::warn_dangling_pointer_assignment, SourceLocation());
   bool RunAnalysis = (EnableDanglingPointerAssignment &&
-                      Entity.LHS->getType()->isPointerType()) ||
+                      Entity.Expression->getType()->isPointerType()) ||
                      shouldRunGSLAssignmentAnalysis(SemaRef, Entity);
 
   if (!RunAnalysis)
@@ -1278,7 +1300,13 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
 
   checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
                         /*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
-                        Init);
+                        RHS);
 }
 
+void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
+                          Expr *Captured) {
+  checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
+                        /*ExtendingEntity=*/nullptr, LK_LifetimeCapture,
+                        &Entity, Captured);
+}
 } // namespace clang::sema
diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h
index 8c8d0806dee0a3..98562fba54b188 100644
--- a/clang/lib/Sema/CheckExprLifetime.h
+++ b/clang/lib/Sema/CheckExprLifetime.h
@@ -18,22 +18,36 @@
 
 namespace clang::sema {
 
-/// Describes an entity that is being assigned.
-struct AssignedEntity {
-  // The left-hand side expression of the assignment.
-  Expr *LHS = nullptr;
+struct CapturingEntity {
+  // The expression of the entity which captures another entity.
+  // For example:
+  //  1. In an assignment, this would be the left-hand side expression.
+  //    std::string_view sv;
+  //    sv = std::string(); // Here 'sv' is the 'Entity'.
+  //
+  //  2. In an function call involving a lifetime capture, this would be the
+  //  argument capturing the lifetime of another argument.
+  //    void addToSet(std::string_view s [[clang::lifetime_capture_by(sv)]],
+  //                  set<std::string_view>& setsv);
+  //    set<std::string_view> ssv;
+  //    addToSet(std::string(), ssv); // Here 'ssv' is the 'Entity'.
+  Expr *Expression = nullptr;
   CXXMethodDecl *AssignmentOperator = nullptr;
 };
 
 /// Check that the lifetime of the given expr (and its subobjects) is
 /// sufficient for initializing the entity, and perform lifetime extension
 /// (when permitted) if not.
-void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
+void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
                        Expr *Init);
 
 /// Check that the lifetime of the given expr (and its subobjects) is
 /// sufficient for assigning to the entity.
-void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init);
+void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
+                             Expr *RHS);
+
+void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
+                          Expr *Captured);
 
 } // namespace clang::sema
 
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 99500daca295c9..b6d74650c3e0bc 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11,10 +11,12 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "CheckExprLifetime.h"
 #include "clang/AST/APValue.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/AttrIterator.h"
+#include "clang/AST/Attrs.inc"
 #include "clang/AST/CharUnits.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
@@ -3203,6 +3205,29 @@ void Sema::CheckArgAlignment(SourceLocation Loc, NamedDecl *FDecl,
         << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+                                  const Expr *ThisArg,
+                                  ArrayRef<const Expr *> Args) {
+  auto GetArgAt = [&](int Idx) {
+    if (IsMemberFunction && Idx == 0)
+      return const_cast<Expr *>(ThisArg);
+    return const_cast<Expr *>(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+    auto *CapturedByAttr =
+        FD->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
+    if (!CapturedByAttr)
+      continue;
+    for (int CapturingParamIdx : CapturedByAttr->params()) {
+      Expr *Capturing = GetArgAt(CapturingParamIdx);
+      Expr *Captured = GetArgAt(I + IsMemberFunction);
+      CapturingEntity CE{Capturing};
+      // Ensure that 'Captured' lives atleast as long as the 'Capturing' entity.
+      checkCaptureLifetime(*this, CE, Captured);
+    }
+  }
+}
+
 void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
                      const Expr *ThisArg, ArrayRef<const Expr *> Args,
                      bool IsMemberFunction, SourceLocation Loc,
@@ -3244,6 +3269,8 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
     }
   }
 
+  if (FD)
+    checkLifetimeCaptureBy(FD, IsMemberFunction, ThisArg, Args);
   if (FDecl || Proto) {
     CheckNonNullArguments(*this, FDecl, Proto, Args, Loc);
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8557c25b93a8da..04e69f355d96df 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16615,6 +16615,7 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
   }
 
   inferLifetimeBoundAttribute(FD);
+  LazyProcessLifetimeCaptureByParams(FD);
   AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD);
 
   // If C++ exceptions are enabled but we are told extern "C" functions cannot
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 14cc51cf89665a..ccf0b555538dfd 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -14,6 +14,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTMutationListener.h"
 #include "clang/AST/CXXInheritance.h"
+#include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/DeclTemplate.h"
@@ -3834,6 +3835,104 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
       S.Context, AL, EncodingIndices.data(), EncodingIndices.size()));
 }
 
+LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
+                                                        StringRef ParamName) {
+  // Atleast one capture by is required.
+  if (AL.getNumArgs() == 0) {
+    Diag(AL.getLoc(), diag::err_capture_by_attribute_no_entity)
+        << AL.getRange();
+    return nullptr;
+  }
+  SmallVector<IdentifierInfo *, 1> ParamIdents;
+  SmallVector<SourceLocation, 1> ParamLocs;
+  for (unsigned I = 0; I < AL.getNumArgs(); ++I) {
+    if (AL.isArgExpr(I)) {
+      Expr *E = AL.getArgAsExpr(I);
+      Diag(E->getExprLoc(), diag::err_capture_by_attribute_argument_unknown)
+          << E << E->getExprLoc();
+      continue;
+    }
+    assert(AL.isArgIdent(I));
+    IdentifierLoc *IdLoc = AL.getArgAsIdent(I);
+    if (IdLoc->Ident->getName() == ParamName) {
+      Diag(IdLoc->Loc, diag::err_capture_by_references_itself) << IdLoc->Loc;
+      continue;
+    }
+    ParamIdents.push_back(IdLoc->Ident);
+    ParamLocs.push_back(IdLoc->Loc);
+  }
+  SmallVector<int, 1> FakeParamIndices(ParamIdents.size(), -2);
+  LifetimeCaptureByAttr *CapturedBy = ::new (Context) LifetimeCaptureByAttr(
+      Context, AL, FakeParamIndices.data(), FakeParamIndices.size());
+  CapturedBy->setArgs(std::move(ParamIdents), std::move(ParamLocs));
+  return CapturedBy;
+}
+
+static void HandleLifetimeCaptureByAttr(Sema &S, Decl *D,
+                                        const ParsedAttr &AL) {
+  // Do not allow multiple attributes.
+  if (D->hasAttr<LifetimeCaptureByAttr>()) {
+    S.Diag(AL.getLoc(), diag::err_capture_by_attribute_multiple)
+        << AL.getRange();
+    return;
+  }
+  auto *PVD = dyn_cast<ParmVarDecl>(D);
+  assert(PVD);
+  auto *CaptureByAttr = S.ParseLifetimeCaptureByAttr(AL, PVD->getName());
+  if (CaptureByAttr)
+    D->addAttr(CaptureByAttr);
+}
+
+void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) {
+  bool HasImplicitThisParam = isInstanceMethod(FD);
+
+  llvm::StringMap<int> NameIdxMapping;
+  NameIdxMapping["global"] = -1;
+  NameIdxMapping["unknown"] = -1;
+  int Idx = 0;
+  if (HasImplicitThisParam) {
+    NameIdxMapping["this"] = 0;
+    Idx++;
+  }
+  for (const ParmVarDecl *PVD : FD->parameters())
+    NameIdxMapping[PVD->getName()] = Idx++;
+  auto HandleCaptureBy = [&](LifetimeCaptureByAttr *CapturedBy) {
+    if (!CapturedBy)
+      return;
+    const auto &Entities = CapturedBy->getArgIdents();
+    for (size_t I = 0; I < Entities.size(); ++I) {
+      StringRef Name = Entities[I]->getName();
+      auto It = NameIdxMapping.find(Name);
+      if (It == NameIdxMapping.end()) {
+        auto Loc = CapturedBy->getArgLocs()[I];
+        if (!HasImplicitThisParam && Name == "this")
+          Diag(Loc, diag::err_capture_by_implicit_this_not_available) << Loc;
+        else
+          Diag(Loc, diag::err_capture_by_attribute_argument_unknown)
+              << Entities[I] << Loc;
+        continue;
+      }
+      CapturedBy->setParamIdx(I, It->second);
+    }
+  };
+  for (ParmVarDecl *PVD : FD->parameters())
+    HandleCaptureBy(PVD->getAttr<LifetimeCaptureByAttr>());
+  if (!HasImplicitThisParam)
+    return;
+  TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+  if (!TSI)
+    return;
+  AttributedTypeLoc ATL;
+  for (TypeLoc TL = TSI->getTypeLoc();
+       (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
+       TL = ATL.getModifiedLoc()) {
+    auto *A = ATL.getAttrAs<LifetimeCaptureByAttr>();
+    if (!A)
+      continue;
+    HandleCaptureBy(const_cast<LifetimeCaptureByAttr *>(A));
+  }
+}
+
 static bool isFunctionLike(const Type &T) {
   // Check for explicit function types.
   // 'called_once' is only supported in Objective-C and it has
@@ -6618,6 +6717,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_Callback:
     handleCallbackAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_LifetimeCaptureBy:
+    HandleLifetimeCaptureByAttr(S, D, AL);
+    break;
   case ParsedAttr::AT_CalledOnce:
     handleCalledOnceAttr(S, D, AL);
     break;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 80c252c79e4d7a..55b0f574170f74 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -13707,8 +13707,8 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS,
 
   CheckForNullPointerDereference(*this, LHSExpr);
 
-  AssignedEntity AE{LHSExpr};
-  checkExprLifetime(*this, AE, RHS.get());
+  CapturingEntity AE{LHSExpr};
+  checkAssignmentLifetime(*this, AE, RHS.get());
 
   if (getLangOpts().CPlusPlus20 && LHSType.isVolatileQualified()) {
     if (CompoundType.isNull()) {
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 4d11f2a43fcc6b..026a38e21103b2 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -7397,7 +7397,7 @@ PerformConstructorInitialization(Sema &S,
 
 void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
                                     Expr *Init) {
-  return sema::checkExprLifetime(*this, Entity, Init);
+  return sema::checkInitLifetime(*this, Entity, Init);
 }
 
 static void DiagnoseNarrowingInInitList(Sema &S,
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index d304f322aced64..1c96ba890256c1 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -14800,8 +14800,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
           // Check for a self move.
           DiagnoseSelfMove(Args[0], Args[1], OpLoc);
           // lifetime check.
-          checkExprLifetime(
-              *this, AssignedEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)},
+          checkAssignmentLifetime(
+              *this, CapturingEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)},
               Args[1]);
         }
         if (ImplicitThis) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 950bd6db0359d1..edf99a25088686 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8548,6 +8548,15 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State,
   }
 }
 
+static void HandleLifetimeCaptureByAttr(TypeProcessingState &State,
+                                        QualType &CurType, ParsedAttr &PA) {
+  if (State.getDeclarator().isDeclarationOfFunction()) {
+    auto *Attr = State.getSema().ParseLifetimeCaptureByAttr(PA, "this");
+    if (Attr)
+      CurType = State.getAttributedType(Attr, CurType, CurType);
+  }
+}
+
 static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
                                         QualType &CurType,
                                         const ParsedAttr &Attr, Sema &S) {
@@ -8709,6 +8718,10 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
       if (TAL == TAL_DeclChunk)
         HandleLifetimeBoundAttr(state, type, attr);
       break;
+    case ParsedAttr::AT_LifetimeCaptureBy:
+      if (TAL == TAL_DeclChunk)
+        HandleLifetimeCaptureByAttr(state, type, attr);
+      break;
 
     case ParsedAttr::AT_NoDeref: {
       // FIXME: `noderef` currently doesn't work correctly in [[]] syntax.
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 0fb997a5671085..7a0215fa3c576a 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -307,3 +307,97 @@ void test(StatusOr<FooView> foo1, StatusOr<NonAnnotatedFooView> foo2) {
   foo2 = NonAnnotatedFoo();
 }
 } // namespace GH106372
+
+namespace lifetime_capture_by {
+
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+
+///////////////////////////
+// Test for valid usages.
+///////////////////////////
+[[clang::lifetime_capture_by(unknown)]] // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}}
+void nonMember(
+    const int &x1 [[clang::lifetime_capture_by(s, t)]],
+    S &s,
+    S &t,
+    const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument 12345 + 12 is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
+    const int &x3 [[clang::lifetime_capture_by(abcdefgh)]],   // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
+    const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument "abcdefgh" is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
+    const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}}
+    const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}}
+    const int& x7 [[clang::lifetime_capture_by(u, 
+                                               x7)]], // expected-error {{'lifetime_capture_by' argument references itself}}
+    const S& u
+  )
+{
+  s.captureInt(x1);
+}
+
+struct T {
+  void member(
+    const int &x [[clang::lifetime_capture_by(s)]], 
+    S &s,
+    S &t,            
+    const int &y [[clang::lifetime_capture_by(s)]],
+    const int &z [[clang::lifetime_capture_by(this, x, y)]],
+    const int &u [[clang::lifetime_capture_by(global, x, s)]])
+  {
+    s.captureInt(x);
+  }
+};
+
+struct ThisIsCaptured {
+  void foo(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}}
+};
+
+///////////////////////////
+// Detect dangling cases.
+///////////////////////////
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureSV(std::string_view x, S&s);
+
+void use() {
+  S s;
+  int local;
+  captureInt(1, // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}}
+            s);
+  captureInt(local, s);
+  
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  std::string_view local_sv;
+  captureSV(local_sv, s);
+  captureSV(local_sv + local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by the 's'}}
+            s);
+  captureSV(substr(
+      std::string() // expected-warning {{object captured by the 's'}}
+      ), s);
+  captureSV(strcopy(std::string()), s);
+  
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  s.captureInt(1); // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}}
+  s.captureSV(std::string()); // expected-warning {{object captured by the 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by the 's'}}
+  s.captureSV(strcopy(std::string()));
+}
+} // namespace lifetime_capture_by_usage
+
+// Test for templated code.
+// 2 nested function calls foo(sv, bar(sv, setsv));
\ No newline at end of file

>From 51d7dc80690b4416ee972ed1aef31f78b323923d Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 8 Oct 2024 08:19:56 +0000
Subject: [PATCH 2/8] start working on lifetime capture

---
 clang/lib/AST/TypePrinter.cpp             | 16 ++--------------
 clang/lib/Sema/CheckExprLifetime.cpp      | 11 ++---------
 clang/lib/Sema/CheckExprLifetime.h        |  6 ------
 clang/test/SemaCXX/attr-lifetimebound.cpp |  1 -
 4 files changed, 4 insertions(+), 30 deletions(-)

diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 02bece7cce51d7..52bf59a9717ecb 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1910,21 +1910,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
     return;
   }
   if (T->getAttrKind() == attr::LifetimeCaptureBy) {
+    // FIXME: Print the attribute arguments once we have a way to retrieve these
+    // here.
     OS << " [[clang::lifetime_capture_by(...)";
-    // const LifetimeCaptureByAttr* A= T->getAs<LifetimeCaptureByAttr>();
-    // bool valid = true;
-    // for (int I : A->params())
-    //   valid &= I != -2;
-    // if (valid) {
-    //   OS << "invalid)";
-    //   return;
-    // }
-    // for (size_t I = 0; I < A->params_size(); ++I) {
-    //   OS << A->getArgIdents()[I]->getName()
-    //      << "(idx: " << *(A->params_begin() + I) << ")";
-    //   if (I != A->params_size() - 1)
-    //     OS << ", ";
-    // }
     return;
   }
 
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index c0180add9a1c41..9e58961dfa4226 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -50,7 +50,7 @@ enum LifetimeKind {
   /// object to it.
   LK_Assignment,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity may capture the reference to a temporary object.
   LK_LifetimeCapture,
 };
@@ -1202,7 +1202,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
         return false;
       assert(shouldLifetimeExtendThroughPath(Path) ==
                  PathLifetimeKind::NoExtend &&
-             "No lifetime extension for in function calls");
+             "No lifetime extension in function calls");
       SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
           << CEntity->Expression << DiagRange;
       return false;
@@ -1435,13 +1435,6 @@ void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
                         /*AEntity*/ nullptr, Init);
 }
 
-void checkExprLifetimeMustTailArg(Sema &SemaRef,
-                                  const InitializedEntity &Entity, Expr *Init) {
-  checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail,
-                        /*AEntity*/ nullptr, Init);
-}
-
-
 void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
                              Expr *RHS) {
   bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h
index 9c5241e9b5d806..98562fba54b188 100644
--- a/clang/lib/Sema/CheckExprLifetime.h
+++ b/clang/lib/Sema/CheckExprLifetime.h
@@ -49,12 +49,6 @@ void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
 void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
                           Expr *Captured);
 
-/// Check that the lifetime of the given expr (and its subobjects) is
-/// sufficient, assuming that it is passed as an argument to a musttail
-/// function.
-void checkExprLifetimeMustTailArg(Sema &SemaRef,
-                                  const InitializedEntity &Entity, Expr *Init);
-
 } // namespace clang::sema
 
 #endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 7a0215fa3c576a..a7f15896e97a88 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -380,7 +380,6 @@ void use() {
 
   std::string_view local_sv;
   captureSV(local_sv, s);
-  captureSV(local_sv + local_sv, s);
   captureSV(std::string(), // expected-warning {{object captured by the 's'}}
             s);
   captureSV(substr(

>From 37aca98638bee739aa43a7046bf6e83aa61c9d6b Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 8 Oct 2024 18:34:53 +0000
Subject: [PATCH 3/8] implement: 'this' is captured.

---
 clang/lib/Sema/CheckExprLifetime.cpp      |  6 ++++++
 clang/lib/Sema/CheckExprLifetime.h        |  5 +++++
 clang/lib/Sema/SemaChecking.cpp           | 19 +++++++++++++++++++
 clang/test/SemaCXX/attr-lifetimebound.cpp | 20 +++++++++++++++++---
 4 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 9e58961dfa4226..851d4cdb6575f5 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -1435,6 +1435,12 @@ void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
                         /*AEntity*/ nullptr, Init);
 }
 
+void checkExprLifetimeMustTailArg(Sema &SemaRef,
+                                  const InitializedEntity &Entity, Expr *Init) {
+  checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail,
+                        /*AEntity*/ nullptr, Init);
+}
+
 void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
                              Expr *RHS) {
   bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h
index 98562fba54b188..976260d40e46bf 100644
--- a/clang/lib/Sema/CheckExprLifetime.h
+++ b/clang/lib/Sema/CheckExprLifetime.h
@@ -49,6 +49,11 @@ void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
 void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
                           Expr *Captured);
 
+/// Check that the lifetime of the given expr (and its subobjects) is
+/// sufficient, assuming that it is passed as an argument to a musttail
+/// function.
+void checkExprLifetimeMustTailArg(Sema &SemaRef,
+                                  const InitializedEntity &Entity, Expr *Init);
 } // namespace clang::sema
 
 #endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 3f0452875e0e8f..89214afde06a7d 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3246,6 +3246,25 @@ void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
       checkCaptureLifetime(*this, CE, Captured);
     }
   }
+  if (IsMemberFunction) {
+    TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+    if (!TSI)
+      return;
+    AttributedTypeLoc ATL;
+    for (TypeLoc TL = TSI->getTypeLoc();
+         (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
+         TL = ATL.getModifiedLoc()) {
+      auto *CapturedByAttr = ATL.getAttrAs<LifetimeCaptureByAttr>();
+      if (!CapturedByAttr)
+        continue;
+      Expr *Captured = GetArgAt(0);
+      for (int CapturingParamIdx : CapturedByAttr->params()) {
+        Expr *Capturing = GetArgAt(CapturingParamIdx);
+        CapturingEntity CE{Capturing};
+        checkCaptureLifetime(*this, CE, Captured);
+      }
+    }
+  }
 }
 
 void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index a7f15896e97a88..8cd1d4e643b640 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -351,7 +351,7 @@ struct T {
 };
 
 struct ThisIsCaptured {
-  void foo(S& s) [[clang::lifetime_capture_by(s)]];
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
   void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}}
   void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}}
 };
@@ -366,9 +366,12 @@ std::string_view substr(const std::string& s [[clang::lifetimebound]]);
 std::string_view strcopy(const std::string& s);
 
 void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
 void noCaptureSV(std::string_view x, S&s);
 
 void use() {
+  std::string_view local_sv;
+  std::string local_s;
   S s;
   int local;
   captureInt(1, // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}}
@@ -378,23 +381,34 @@ void use() {
   noCaptureInt(1, s);
   noCaptureInt(local, s);
 
-  std::string_view local_sv;
+  // Capture using std::string_view.
   captureSV(local_sv, s);
   captureSV(std::string(), // expected-warning {{object captured by the 's'}}
             s);
   captureSV(substr(
       std::string() // expected-warning {{object captured by the 's'}}
       ), s);
+  captureSV(substr(local_s), s);
   captureSV(strcopy(std::string()), s);
-  
+
   noCaptureSV(local_sv, s);
   noCaptureSV(std::string(), s);
   noCaptureSV(substr(std::string()), s);
 
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by the 's'}}
+  captureS(local_s, s);
+
+  // Member functions.  
   s.captureInt(1); // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}}
   s.captureSV(std::string()); // expected-warning {{object captured by the 's'}}
   s.captureSV(substr(std::string())); // expected-warning {{object captured by the 's'}}
   s.captureSV(strcopy(std::string()));
+
+  // This is captured.
+  ThisIsCaptured{}.capture(s); //expected-warning {{object captured by the 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
 }
 } // namespace lifetime_capture_by_usage
 

>From c1ed8622f3b42b1aa2dfd1fe6c19854185ccd553 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 8 Oct 2024 21:12:31 +0000
Subject: [PATCH 4/8] start working on implicit lifetime capture

---
 clang/include/clang/Sema/Sema.h |  3 +++
 clang/lib/Sema/SemaAttr.cpp     | 23 +++++++++++++++++++++++
 clang/lib/Sema/SemaDecl.cpp     |  3 ++-
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d90e9a4bbbcc19..ab2dc53ee10c3f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1759,6 +1759,9 @@ class Sema final : public SemaBase {
   /// Add [[clang:::lifetimebound]] attr for std:: functions and methods.
   void inferLifetimeBoundAttribute(FunctionDecl *FD);
 
+  /// Add [[clang:::lifetime_capture(this)]] to std:: methods.
+  void inferLifetimeCaptureByAttribute(FunctionDecl *FD);
+
   /// Add [[gsl::Pointer]] attributes for std:: types.
   void inferGslPointerAttribute(TypedefNameDecl *TD);
 
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index cf2a5a622a3a4d..3b505043f5db3c 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -13,6 +13,8 @@
 
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/Attr.h"
+#include "clang/AST/Attrs.inc"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/Expr.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Lex/Preprocessor.h"
@@ -269,6 +271,27 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
   }
 }
 
+void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) {
+  auto *MD = dyn_cast<CXXMethodDecl>(FD);
+  if (!MD || !MD->isInStdNamespace())
+    return;
+  static const llvm::StringSet<> CapturingMethods{"insert", "push",
+                                                  "push_front", "push_back"};
+  if (!CapturingMethods.contains(MD->getName()))
+    return;
+  for (ParmVarDecl *PVD : MD->parameters()) {
+    if (PVD->hasAttr<LifetimeCaptureByAttr>())
+      continue;
+    // PVD->dumpColor();
+    // auto *RD = PVD->getType()->getAsCXXRecordDecl();
+    // if (RD && RD->hasAttr<PointerAttr>()) {
+    // int CaptureByThis[] = {0}; // Replace with THIS.
+    // PVD->addAttr(
+    //     LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1));
+    // }
+  }
+}
+
 void Sema::inferNullableClassAttribute(CXXRecordDecl *CRD) {
   static const llvm::StringSet<> Nullable{
       "auto_ptr",         "shared_ptr", "unique_ptr",         "exception_ptr",
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 71af8b1f22871a..4297ec2ccce701 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16636,8 +16636,9 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
     }
   }
 
-  inferLifetimeBoundAttribute(FD);
   LazyProcessLifetimeCaptureByParams(FD);
+  inferLifetimeBoundAttribute(FD);
+  inferLifetimeCaptureByAttribute(FD);
   AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD);
 
   // If C++ exceptions are enabled but we are told extern "C" functions cannot

>From 8ace66626fb165a814d5a84573b51e5b2a545b78 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 9 Oct 2024 14:50:12 +0000
Subject: [PATCH 5/8] define constants, fix false-positive for MTE of
 pointer-like types

---
 clang/include/clang/Basic/Attr.td             |  8 +--
 clang/include/clang/Basic/DiagnosticGroups.td |  1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |  5 +-
 clang/lib/Sema/CheckExprLifetime.cpp          |  8 ++-
 clang/lib/Sema/SemaDeclAttr.cpp               |  7 +--
 clang/test/SemaCXX/attr-lifetimebound.cpp     | 54 +++++++++++--------
 6 files changed, 50 insertions(+), 33 deletions(-)

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 5753b3cd79fdf9..af385802481a6d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1895,10 +1895,10 @@ private:
   SmallVector<SourceLocation, 1> ArgLocs;
 
 public:
-  static const int INVALID = -2;
-  static const int UNKNOWN = -1;
-  static const int GLOBAL = -1;
-  static const int THIS = 0;
+  static constexpr int INVALID = -2;
+  static constexpr int UNKNOWN = -1;
+  static constexpr int GLOBAL = -1;
+  static constexpr int THIS = 0;
 
   void setArgs(SmallVector<IdentifierInfo*, 1> Idents,
                SmallVector<SourceLocation, 1> Locs) { 
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 41e719d4d57816..a4c5f31dfec8d9 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -452,6 +452,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
 def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
 def DanglingAssignment: DiagGroup<"dangling-assignment">;
 def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
+def DanglingCapture : DiagGroup<"dangling-capture">;
 def DanglingElse: DiagGroup<"dangling-else">;
 def DanglingField : DiagGroup<"dangling-field">;
 def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ca5be51791cf44..9e40766675266a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10201,9 +10201,8 @@ def warn_dangling_pointer_assignment : Warning<
    "will be destroyed at the end of the full-expression">,
    InGroup<DanglingAssignment>;
 def warn_dangling_reference_captured : Warning<
-   "object captured by the '%0' "
-   "will be destroyed at the end of the full-expression">,
-   InGroup<DanglingAssignment>;
+   "object captured by '%0' will be destroyed at the end of the full-expression">,
+   InGroup<DanglingCapture>;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 851d4cdb6575f5..faa001e8b93470 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -1413,8 +1413,14 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
   if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *CEntity))
     Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});
-  else if (LK == LK_LifetimeCapture)
+  else if (LK == LK_LifetimeCapture) {
+    // Skip the top MaterializeTemoraryExpr if it is temporary object of the
+    // pointer-like type itself.
+    if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Init);
+        MTE && isPointerLikeType(Init->getType()))
+      Init = MTE->getSubExpr();
     Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});
+  }
 
   if (Init->isGLValue())
     visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index b56f69198c788d..e9677fa92176ba 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3893,7 +3893,8 @@ LifetimeCaptureByAttr *Sema::ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
     ParamIdents.push_back(IdLoc->Ident);
     ParamLocs.push_back(IdLoc->Loc);
   }
-  SmallVector<int, 1> FakeParamIndices(ParamIdents.size(), -2);
+  SmallVector<int, 1> FakeParamIndices(ParamIdents.size(),
+                                       LifetimeCaptureByAttr::INVALID);
   LifetimeCaptureByAttr *CapturedBy = ::new (Context) LifetimeCaptureByAttr(
       Context, AL, FakeParamIndices.data(), FakeParamIndices.size());
   CapturedBy->setArgs(std::move(ParamIdents), std::move(ParamLocs));
@@ -3919,8 +3920,8 @@ void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) {
   bool HasImplicitThisParam = isInstanceMethod(FD);
 
   llvm::StringMap<int> NameIdxMapping;
-  NameIdxMapping["global"] = -1;
-  NameIdxMapping["unknown"] = -1;
+  NameIdxMapping["global"] = LifetimeCaptureByAttr::GLOBAL;
+  NameIdxMapping["unknown"] = LifetimeCaptureByAttr::UNKNOWN;
   int Idx = 0;
   if (HasImplicitThisParam) {
     NameIdxMapping["this"] = 0;
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 8cd1d4e643b640..2435cfbb93f9cd 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -75,7 +75,6 @@ namespace usage_ok {
   }
 }
 
-# 1 "<std>" 1 3
 namespace std {
   using size_t = __SIZE_TYPE__;
   struct string {
@@ -84,14 +83,16 @@ namespace std {
 
     char &operator[](size_t) const [[clang::lifetimebound]];
   };
-  string operator""s(const char *, size_t);
+  string operator""s(const char *, size_t); // expected-warning {{}}
 
-  struct string_view {
-    string_view();
-    string_view(const char *p [[clang::lifetimebound]]);
-    string_view(const string &s [[clang::lifetimebound]]);
+  template<typename T>
+  struct basic_string_view {
+    basic_string_view();
+    basic_string_view(const T *p [[clang::lifetimebound]]);
+    basic_string_view(const string &s [[clang::lifetimebound]]);
   };
-  string_view operator""sv(const char *, size_t);
+  using string_view = basic_string_view<char>;
+  string_view operator""sv(const char *, size_t); // expected-warning {{}}
 
   struct vector {
     int *data();
@@ -100,7 +101,6 @@ namespace std {
 
   template<typename K, typename V> struct map {};
 }
-# 68 "attr-lifetimebound.cpp" 2
 
 using std::operator""s;
 using std::operator""sv;
@@ -238,11 +238,6 @@ template <class T> T *addressof(T &arg) {
         &const_cast<char &>(reinterpret_cast<const volatile char &>(arg)));
 }
 
-template<typename T>
-struct basic_string_view {
-  basic_string_view(const T *);
-};
-
 template <class T> struct span {
   template<size_t _ArrayExtent>
 	span(const T (&__arr)[_ArrayExtent]) noexcept;
@@ -360,22 +355,28 @@ struct ThisIsCaptured {
 // Detect dangling cases.
 ///////////////////////////
 void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
 void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
 
 std::string_view substr(const std::string& s [[clang::lifetimebound]]);
 std::string_view strcopy(const std::string& s);
 
 void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
-void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], S&s);
 void noCaptureSV(std::string_view x, S&s);
 
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+
 void use() {
   std::string_view local_sv;
   std::string local_s;
   S s;
+  // Capture an 'int'.
   int local;
-  captureInt(1, // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}}
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}}
             s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
   captureInt(local, s);
   
   noCaptureInt(1, s);
@@ -383,30 +384,39 @@ void use() {
 
   // Capture using std::string_view.
   captureSV(local_sv, s);
-  captureSV(std::string(), // expected-warning {{object captured by the 's'}}
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
             s);
   captureSV(substr(
-      std::string() // expected-warning {{object captured by the 's'}}
+      std::string() // expected-warning {{object captured by 's'}}
       ), s);
   captureSV(substr(local_s), s);
   captureSV(strcopy(std::string()), s);
 
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+
   noCaptureSV(local_sv, s);
   noCaptureSV(std::string(), s);
   noCaptureSV(substr(std::string()), s);
 
   // Capture using std::string.
-  captureS(std::string(), s); // expected-warning {{object captured by the 's'}}
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
   captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 's'}}
 
   // Member functions.  
-  s.captureInt(1); // expected-warning {{object captured by the 's' will be destroyed at the end of the full-expression}}
-  s.captureSV(std::string()); // expected-warning {{object captured by the 's'}}
-  s.captureSV(substr(std::string())); // expected-warning {{object captured by the 's'}}
+  s.captureInt(1); // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}}
   s.captureSV(strcopy(std::string()));
 
   // This is captured.
-  ThisIsCaptured{}.capture(s); //expected-warning {{object captured by the 's'}}
+  ThisIsCaptured{}.capture(s); //expected-warning {{object captured by 's'}}
   ThisIsCaptured TIS;
   TIS.capture(s);
 }

>From ff1404233d76f7a9cdae18f370858c48956ba853 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 9 Oct 2024 18:35:59 +0000
Subject: [PATCH 6/8] restructring tests

---
 clang/test/SemaCXX/attr-lifetimebound.cpp | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 2435cfbb93f9cd..fa235c384175ce 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -77,18 +77,20 @@ namespace usage_ok {
 
 namespace std {
   using size_t = __SIZE_TYPE__;
-  struct string {
-    string();
-    string(const char*);
+  template<typename T>
+  struct basic_string {
+    basic_string();
+    basic_string(const T*);
 
     char &operator[](size_t) const [[clang::lifetimebound]];
   };
+  using string =  basic_string<char>;
   string operator""s(const char *, size_t); // expected-warning {{}}
 
   template<typename T>
   struct basic_string_view {
     basic_string_view();
-    basic_string_view(const T *p [[clang::lifetimebound]]);
+    basic_string_view(const T *p);
     basic_string_view(const string &s [[clang::lifetimebound]]);
   };
   using string_view = basic_string_view<char>;
@@ -112,7 +114,7 @@ namespace p0936r0_examples {
   void f() {
     std::string_view sv = "hi";
     std::string_view sv2 = sv + sv; // expected-warning {{temporary}}
-    sv2 = sv + sv; // FIXME: can we infer that we should warn here too?
+    sv2 = sv + sv; // expected-warning {{object backing the pointer}}
   }
 
   struct X { int a, b; };

>From cb27d7d5fb3baa352e447c775902432b41a31448 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 14 Oct 2024 12:12:06 +0000
Subject: [PATCH 7/8] move test to existing file

---
 .../Sema/warn-lifetime-analysis-nocfg.cpp     |  87 +++++++++++++
 .../test/SemaCXX/attr-lifetime-capture-by.cpp |  40 ++++++
 clang/test/SemaCXX/attr-lifetimebound.cpp     | 122 ------------------
 3 files changed, 127 insertions(+), 122 deletions(-)
 create mode 100644 clang/test/SemaCXX/attr-lifetime-capture-by.cpp

diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 731639ab16a735..d551859b9ea639 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -778,3 +778,90 @@ void test13() {
 }
 
 } // namespace GH100526
+
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///////////////////////////
+// Detect dangling cases.
+///////////////////////////
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureSV(std::string_view x, S&s);
+
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}}
+};
+
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}}
+            s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
+            s);
+  captureSV(substr(
+      std::string() // expected-warning {{object captured by 's'}}
+      ), s);
+  captureSV(substr(local_s), s);
+  captureSV(strcopy(std::string()), s);
+
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 's'}}
+
+  // Member functions.
+  s.captureInt(1); // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}}
+  s.captureSV(strcopy(std::string()));
+
+  // This is captured.
+  ThisIsCaptured{}.capture(s); //expected-warning {{object captured by 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
+}
+} // namespace lifetime_capture_by_usage
+
+// Test for templated code.
+// 2 nested function calls foo(sv, bar(sv, setsv));
\ No newline at end of file
diff --git a/clang/test/SemaCXX/attr-lifetime-capture-by.cpp b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp
new file mode 100644
index 00000000000000..192ec9bec640a5
--- /dev/null
+++ b/clang/test/SemaCXX/attr-lifetime-capture-by.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -std=c++23 -verify %s
+
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; }
+};
+
+///////////////////////////
+// Test for valid usages.
+///////////////////////////
+[[clang::lifetime_capture_by(unknown)]] // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}}
+void nonMember(
+    const int &x1 [[clang::lifetime_capture_by(s, t)]],
+    S &s,
+    S &t,
+    const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument 12345 + 12 is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
+    const int &x3 [[clang::lifetime_capture_by(abcdefgh)]],   // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
+    const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument "abcdefgh" is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
+    const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}}
+    const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}}
+    const int& x7 [[clang::lifetime_capture_by(u, 
+                                               x7)]], // expected-error {{'lifetime_capture_by' argument references itself}}
+    const S& u
+  )
+{
+  s.captureInt(x1);
+}
+
+struct T {
+  void member(
+    const int &x [[clang::lifetime_capture_by(s)]], 
+    S &s,
+    S &t,            
+    const int &y [[clang::lifetime_capture_by(s)]],
+    const int &z [[clang::lifetime_capture_by(this, x, y)]],
+    const int &u [[clang::lifetime_capture_by(global, x, s)]])
+  {
+    s.captureInt(x);
+  }
+};
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index fa235c384175ce..edeb33eaeb1dd8 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -304,125 +304,3 @@ void test(StatusOr<FooView> foo1, StatusOr<NonAnnotatedFooView> foo2) {
   foo2 = NonAnnotatedFoo();
 }
 } // namespace GH106372
-
-namespace lifetime_capture_by {
-
-struct S {
-  const int *x;
-  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x = &x; }
-  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
-};
-
-///////////////////////////
-// Test for valid usages.
-///////////////////////////
-[[clang::lifetime_capture_by(unknown)]] // expected-error {{'lifetime_capture_by' attribute only applies to parameters and implicit object parameters}}
-void nonMember(
-    const int &x1 [[clang::lifetime_capture_by(s, t)]],
-    S &s,
-    S &t,
-    const int &x2 [[clang::lifetime_capture_by(12345 + 12)]], // expected-error {{'lifetime_capture_by' attribute argument 12345 + 12 is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
-    const int &x3 [[clang::lifetime_capture_by(abcdefgh)]],   // expected-error {{'lifetime_capture_by' attribute argument 'abcdefgh' is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
-    const int &x4 [[clang::lifetime_capture_by("abcdefgh")]], // expected-error {{'lifetime_capture_by' attribute argument "abcdefgh" is not a known function parameter. Must be a function parameter of one of 'this', 'global' or 'unknown'}}
-    const int &x5 [[clang::lifetime_capture_by(this)]], // expected-error {{'lifetime_capture_by' argument references unavailable implicit 'this'}}
-    const int &x6 [[clang::lifetime_capture_by()]], // expected-error {{'lifetime_capture_by' attribute specifies no capturing entity}}
-    const int& x7 [[clang::lifetime_capture_by(u, 
-                                               x7)]], // expected-error {{'lifetime_capture_by' argument references itself}}
-    const S& u
-  )
-{
-  s.captureInt(x1);
-}
-
-struct T {
-  void member(
-    const int &x [[clang::lifetime_capture_by(s)]], 
-    S &s,
-    S &t,            
-    const int &y [[clang::lifetime_capture_by(s)]],
-    const int &z [[clang::lifetime_capture_by(this, x, y)]],
-    const int &u [[clang::lifetime_capture_by(global, x, s)]])
-  {
-    s.captureInt(x);
-  }
-};
-
-struct ThisIsCaptured {
-  void capture(S& s) [[clang::lifetime_capture_by(s)]];
-  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}}
-  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}}
-};
-
-///////////////////////////
-// Detect dangling cases.
-///////////////////////////
-void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
-void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
-void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
-
-std::string_view substr(const std::string& s [[clang::lifetimebound]]);
-std::string_view strcopy(const std::string& s);
-
-void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
-void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], S&s);
-void noCaptureSV(std::string_view x, S&s);
-
-void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
-void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
-
-void use() {
-  std::string_view local_sv;
-  std::string local_s;
-  S s;
-  // Capture an 'int'.
-  int local;
-  captureInt(1, // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}}
-            s);
-  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
-  captureInt(local, s);
-  
-  noCaptureInt(1, s);
-  noCaptureInt(local, s);
-
-  // Capture using std::string_view.
-  captureSV(local_sv, s);
-  captureSV(std::string(), // expected-warning {{object captured by 's'}}
-            s);
-  captureSV(substr(
-      std::string() // expected-warning {{object captured by 's'}}
-      ), s);
-  captureSV(substr(local_s), s);
-  captureSV(strcopy(std::string()), s);
-
-  captureRValSV(std::move(local_sv), s);
-  captureRValSV(std::string(), s); // expected-warning {{object captured by 's'}}
-  captureRValSV(std::string_view{"abcd"}, s);
-  captureRValSV(substr(local_s), s);
-  captureRValSV(substr(std::string()), s); // expected-warning {{object captured by 's'}}
-  captureRValSV(strcopy(std::string()), s);
-
-  noCaptureSV(local_sv, s);
-  noCaptureSV(std::string(), s);
-  noCaptureSV(substr(std::string()), s);
-
-  // Capture using std::string.
-  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
-  captureS(local_s, s);
-  captureRValS(std::move(local_s), s);
-  captureRValS(std::string(), s); // expected-warning {{object captured by 's'}}
-
-  // Member functions.  
-  s.captureInt(1); // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}}
-  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
-  s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}}
-  s.captureSV(strcopy(std::string()));
-
-  // This is captured.
-  ThisIsCaptured{}.capture(s); //expected-warning {{object captured by 's'}}
-  ThisIsCaptured TIS;
-  TIS.capture(s);
-}
-} // namespace lifetime_capture_by_usage
-
-// Test for templated code.
-// 2 nested function calls foo(sv, bar(sv, setsv));
\ No newline at end of file

>From c4c0f5cdc2ba3bc517a2e83170e88c875269d108 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 15 Oct 2024 13:28:00 +0000
Subject: [PATCH 8/8] infer attribute for STL containers

---
 clang/lib/Sema/SemaAttr.cpp                   | 22 ++++++++++++-------
 clang/lib/Sema/SemaChecking.cpp               |  1 +
 clang/lib/Sema/SemaDecl.cpp                   |  1 +
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  6 +++--
 .../Sema/warn-lifetime-analysis-nocfg.cpp     | 20 +++++++++++++++--
 5 files changed, 38 insertions(+), 12 deletions(-)

diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 3b505043f5db3c..8d6496fea56aa6 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -272,8 +272,10 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
 }
 
 void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) {
+  if (!FD)
+    return;
   auto *MD = dyn_cast<CXXMethodDecl>(FD);
-  if (!MD || !MD->isInStdNamespace())
+  if (!MD || !MD->getIdentifier())
     return;
   static const llvm::StringSet<> CapturingMethods{"insert", "push",
                                                   "push_front", "push_back"};
@@ -281,14 +283,18 @@ void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) {
     return;
   for (ParmVarDecl *PVD : MD->parameters()) {
     if (PVD->hasAttr<LifetimeCaptureByAttr>())
+      return;
+    auto *RD = PVD->getType().getNonReferenceType()->getAsCXXRecordDecl();
+    if (!RD)
       continue;
-    // PVD->dumpColor();
-    // auto *RD = PVD->getType()->getAsCXXRecordDecl();
-    // if (RD && RD->hasAttr<PointerAttr>()) {
-    // int CaptureByThis[] = {0}; // Replace with THIS.
-    // PVD->addAttr(
-    //     LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1));
-    // }
+    RD = RD->getCanonicalDecl();
+    if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+      RD = CTSD->getSpecializedTemplate()->getTemplatedDecl();
+    if (RD && RD->hasAttr<PointerAttr>()) {
+      int CaptureByThis[] = {LifetimeCaptureByAttr::THIS};
+      PVD->addAttr(
+          LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1));
+    }
   }
 }
 
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 89214afde06a7d..815d47500572bb 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3246,6 +3246,7 @@ void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
       checkCaptureLifetime(*this, CE, Captured);
     }
   }
+  // Check when the 'this' object is captured.
   if (IsMemberFunction) {
     TypeSourceInfo *TSI = FD->getTypeSourceInfo();
     if (!TSI)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 4297ec2ccce701..09fb92c7acb572 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11863,6 +11863,7 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
   NamedDecl *OldDecl = nullptr;
   bool MayNeedOverloadableChecks = false;
 
+  inferLifetimeCaptureByAttribute(NewFD);
   // Merge or overload the declaration with an existing declaration of
   // the same name, if appropriate.
   if (!Previous.empty()) {
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index d29434486dcb06..1a40c56098a934 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4948,7 +4948,9 @@ FunctionDecl *Sema::InstantiateFunctionDeclaration(
   MultiLevelTemplateArgumentList MArgs(FTD, Args->asArray(),
                                        /*Final=*/false);
 
-  return cast_or_null<FunctionDecl>(SubstDecl(FD, FD->getParent(), MArgs));
+  auto *Res = cast_or_null<FunctionDecl>(SubstDecl(FD, FD->getParent(), MArgs));
+  inferLifetimeCaptureByAttribute(Res);
+  return Res;
 }
 
 void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
@@ -4958,7 +4960,7 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
                                          bool AtEndOfTU) {
   if (Function->isInvalidDecl() || isa<CXXDeductionGuideDecl>(Function))
     return;
-
+  inferLifetimeCaptureByAttribute(Function);
   // Never instantiate an explicit specialization except if it is a class scope
   // explicit specialization.
   TemplateSpecializationKind TSK =
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index d551859b9ea639..53f4232b468a3d 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -175,8 +175,12 @@ struct vector {
 
   template<typename InputIterator>
 	vector(InputIterator first, InputIterator __last);
+  // void push_back(const T&);
+  void push_back(T&&);
 
   T &at(int n);
+
+  void insert(iterator, T&&);
 };
 
 template<typename T>
@@ -857,11 +861,23 @@ void use() {
   s.captureSV(strcopy(std::string()));
 
   // This is captured.
-  ThisIsCaptured{}.capture(s); //expected-warning {{object captured by 's'}}
+  ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
   ThisIsCaptured TIS;
   TIS.capture(s);
 }
-} // namespace lifetime_capture_by_usage
+
+// Infer attribute in STL container of pointers.
+void container_of_pointers() {
+  std::vector<std::string> vs;
+  std::vector<std::string_view> vsv;
+
+  vs.push_back(std::string()); // Ok.
+  vsv.push_back(std::string()); // expected-warning {{bject captured by 'vsv' will be destroyed at the end of the full-expression}}
+
+  vs.insert(vs.begin(), std::string()); // Ok.
+  vsv.insert(vsv.begin(), std::string()); // expected-warning {{bject captured by 'vsv' will be destroyed at the end of the full-expression}}
+}
+} // namespace lifetime_capture_by
 
 // Test for templated code.
 // 2 nested function calls foo(sv, bar(sv, setsv));
\ No newline at end of file



More information about the cfe-commits mailing list