[clang] [clang] Infer lifetime_capture_by for STL containers (PR #117122)

via cfe-commits cfe-commits at lists.llvm.org
Wed Nov 20 23:23:01 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Utkarsh Saxena (usx95)

<details>
<summary>Changes</summary>

This is behind `-Wdangling-capture` warning which is disabled by default.

---
Full diff: https://github.com/llvm/llvm-project/pull/117122.diff


5 Files Affected:

- (modified) clang/include/clang/Sema/Sema.h (+3) 
- (modified) clang/lib/Sema/SemaAttr.cpp (+34) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+2) 
- (modified) clang/test/Sema/Inputs/lifetime-analysis.h (+5) 
- (modified) clang/test/Sema/warn-lifetime-analysis-capture-by.cpp (+79) 


``````````diff
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 6ea6c67447b6f0..9bafcfec5d4786 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1757,6 +1757,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 STL container 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 9fbad7ed67ccbe..507f7c40d58782 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -268,6 +268,40 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
   }
 }
 
+static bool IsPointerLikeType(QualType QT) {
+  QT = QT.getNonReferenceType();
+  if (QT->isPointerType())
+    return true;
+  auto *RD = QT->getAsCXXRecordDecl();
+  if (!RD)
+    return false;
+  RD = RD->getCanonicalDecl();
+  if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+    RD = CTSD->getSpecializedTemplate()->getTemplatedDecl();
+  return RD->hasAttr<PointerAttr>();
+}
+
+void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) {
+  if (!FD)
+    return;
+  auto *MD = dyn_cast<CXXMethodDecl>(FD);
+  if (!MD || !MD->getIdentifier() || !MD->getParent()->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>())
+      return;
+    if (IsPointerLikeType(PVD->getType())) {
+      int CaptureByThis[] = {LifetimeCaptureByAttr::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 be570f3a1829d0..5b30d0f2c22d16 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11913,6 +11913,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()) {
@@ -16716,6 +16717,7 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
 
   LazyProcessLifetimeCaptureByParams(FD);
   inferLifetimeBoundAttribute(FD);
+  inferLifetimeCaptureByAttribute(FD);
   AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD);
 
   // If C++ exceptions are enabled but we are told extern "C" functions cannot
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 41d1e2f074cc83..5c151385b1fe5a 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -49,6 +49,11 @@ struct vector {
 	vector(InputIterator first, InputIterator __last);
 
   T &at(int n);
+
+  void push_back(const T&);
+  void push_back(T&&);
+  
+  void insert(iterator, T&&);
 };
 
 template<typename T>
diff --git a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
index b3fde386b8616c..462cb2d3f3fd6e 100644
--- a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
@@ -366,3 +366,82 @@ void use() {
   capture3(std::string(), x3); // expected-warning {{object whose reference is captured by 'x3' will be destroyed at the end of the full-expression}}
 }
 } // namespace temporary_views
+
+// ****************************************************************************
+// Inferring annotation for STL containers
+// ****************************************************************************
+namespace inferred_capture_by {
+const std::string* getLifetimeBoundPointer(const std::string &s [[clang::lifetimebound]]);
+const std::string* getNotLifetimeBoundPointer(const std::string &s);
+
+namespace with_string_views {
+std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
+std::string_view getNotLifetimeBoundView(const std::string& s);
+void use() {
+  std::string local;
+  std::vector<std::string_view> views;
+  views.push_back(std::string()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
+  views.insert(views.begin(), 
+            std::string()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
+  views.push_back(getLifetimeBoundView(std::string())); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
+  views.push_back(getNotLifetimeBoundView(std::string()));
+  views.push_back(local);
+  views.insert(views.end(), local);
+
+  std::vector<std::string> strings;
+  strings.push_back(std::string());
+  strings.insert(strings.begin(), std::string());
+}
+} // namespace with_string_views
+
+namespace with_pointers {
+const std::string* getLifetimeBoundPointer(const std::string &s [[clang::lifetimebound]]);
+const std::string* getLifetimeBoundPointer(std::string_view s [[clang::lifetimebound]]);
+const std::string* getNotLifetimeBoundPointer(const std::string &s);
+std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
+
+void use() {
+  std::string local;
+  std::vector<const std::string*> pointers;
+  pointers.push_back(getLifetimeBoundPointer(std::string())); // expected-warning {{object whose reference is captured by 'pointers' will be destroyed at the end of the full-expression}}
+  pointers.push_back(getLifetimeBoundPointer(*getLifetimeBoundPointer(std::string()))); // expected-warning {{object whose reference is captured by 'pointers' will be destroyed at the end of the full-expression}}
+  pointers.push_back(getLifetimeBoundPointer(getLifetimeBoundView(std::string()))); // expected-warning {{object whose reference is captured by 'pointers' will be destroyed at the end of the full-expression}}
+  pointers.push_back(getLifetimeBoundPointer(local));
+
+  pointers.push_back(getLifetimeBoundPointer(*getNotLifetimeBoundPointer(std::string())));
+  pointers.push_back(getNotLifetimeBoundPointer(std::string()));
+}
+} // namespace with_pointers
+
+namespace with_optional {
+class [[gsl::Pointer()]] my_view : public std::string_view {};
+class non_pointer_view : public std::string_view {};
+
+std::optional<std::string> getOptionalString();
+std::optional<std::string_view> getOptionalView();
+std::optional<std::string_view> getOptionalViewLifetimebound(const std::string& s [[clang::lifetimebound]]);
+std::optional<my_view> getOptionalMyView();
+std::optional<non_pointer_view> getOptionalNonPointerView();
+my_view getMyView();
+non_pointer_view getNonPointerView();
+
+void use() {
+  std::string local;
+  std::vector<std::string_view> views;
+  
+  std::optional<std::string_view> optional;
+  views.push_back(optional.value());
+  views.push_back(getOptionalString().value()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
+  views.push_back(getOptionalView().value());
+  views.push_back(getOptionalViewLifetimebound(std::string()).value()); // FIXME: Diagnose it.
+  views.push_back(getOptionalMyView().value());
+
+  views.push_back(getOptionalNonPointerView().value());
+  views.push_back(getMyView());
+  views.push_back(getNonPointerView());
+  views.push_back(std::string_view{});
+  views.push_back(my_view{});
+  views.push_back(non_pointer_view{});
+}
+} // namespace with_optional
+} // namespace inferred_capture_by

``````````

</details>


https://github.com/llvm/llvm-project/pull/117122


More information about the cfe-commits mailing list