[clang] 912c502 - [clang] Infer lifetime_capture_by for STL containers (#117122)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Nov 22 07:01:21 PST 2024
Author: Utkarsh Saxena
Date: 2024-11-22T16:01:16+01:00
New Revision: 912c502a9e4bab8e07de4419f8cbae35c98b112f
URL: https://github.com/llvm/llvm-project/commit/912c502a9e4bab8e07de4419f8cbae35c98b112f
DIFF: https://github.com/llvm/llvm-project/commit/912c502a9e4bab8e07de4419f8cbae35c98b112f.diff
LOG: [clang] Infer lifetime_capture_by for STL containers (#117122)
This is behind `-Wdangling-capture` warning which is disabled by default.
Added:
Modified:
clang/include/clang/Sema/Sema.h
clang/lib/Sema/SemaAttr.cpp
clang/lib/Sema/SemaDecl.cpp
clang/test/AST/attr-lifetime-capture-by.cpp
clang/test/Sema/Inputs/lifetime-analysis.h
clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 6ea6c67447b6f0..5fe23e0d0efd3b 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_by(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..716d8ed1fae4f8 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -11,6 +11,7 @@
//
//===----------------------------------------------------------------------===//
+#include "CheckExprLifetime.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Expr.h"
@@ -268,6 +269,44 @@ 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;
+ 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;
+ // FIXME: Infer for operator[] for map-like containers. For example:
+ // std::map<string_view, ...> m;
+ // m[ReturnString(..)] = ...;
+ static const llvm::StringSet<> CapturingMethods{"insert", "push",
+ "push_front", "push_back"};
+ if (!CapturingMethods.contains(MD->getName()))
+ return;
+ // Do not infer if any parameter is explicitly annotated.
+ for (ParmVarDecl *PVD : MD->parameters())
+ if (PVD->hasAttr<LifetimeCaptureByAttr>())
+ return;
+ for (ParmVarDecl *PVD : MD->parameters()) {
+ 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 223a532df41d9b..74b0e5ad23bd48 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11916,6 +11916,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()) {
@@ -16719,6 +16720,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/AST/attr-lifetime-capture-by.cpp b/clang/test/AST/attr-lifetime-capture-by.cpp
index da2eb0cf3d592e..c3afe267301ad7 100644
--- a/clang/test/AST/attr-lifetime-capture-by.cpp
+++ b/clang/test/AST/attr-lifetime-capture-by.cpp
@@ -7,3 +7,109 @@ struct S {
};
// CHECK: CXXMethodDecl {{.*}}clang::lifetime_capture_by(a, b, global)
+
+// ****************************************************************************
+// Infer annotation for STL container methods.
+// ****************************************************************************
+namespace __gnu_cxx {
+template <typename T>
+struct basic_iterator {};
+}
+
+namespace std {
+template<typename T> class allocator {};
+template <typename T, typename Alloc = allocator<T>>
+struct vector {
+ typedef __gnu_cxx::basic_iterator<T> iterator;
+ iterator begin();
+
+ vector();
+
+ void push_back(const T&);
+ void push_back(T&&);
+
+ void insert(iterator, T&&);
+};
+} // namespace std
+
+// CHECK-NOT: LifetimeCaptureByAttr
+
+struct [[gsl::Pointer()]] View {};
+std::vector<View> views;
+// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
+// CHECK: TemplateArgument type 'View'
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (const View &)'
+// CHECK: ParmVarDecl {{.*}} 'const View &'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (View &&)'
+// CHECK: ParmVarDecl {{.*}} 'View &&'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+
+// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, View &&)'
+// CHECK: ParmVarDecl {{.*}} 'iterator'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK: ParmVarDecl {{.*}} 'View &&'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK-NOT: LifetimeCaptureByAttr
+
+template <class T> struct [[gsl::Pointer()]] ViewTemplate {};
+std::vector<ViewTemplate<int>> templated_views;
+// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
+// CHECK: TemplateArgument type 'ViewTemplate<int>'
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (const ViewTemplate<int> &)'
+// CHECK: ParmVarDecl {{.*}} 'const ViewTemplate<int> &'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (ViewTemplate<int> &&)'
+// CHECK: ParmVarDecl {{.*}} 'ViewTemplate<int> &&'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+
+// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, ViewTemplate<int> &&)'
+// CHECK: ParmVarDecl {{.*}} 'iterator'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK: ParmVarDecl {{.*}} 'ViewTemplate<int> &&'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK-NOT: LifetimeCaptureByAttr
+
+std::vector<int*> pointers;
+// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
+// CHECK: TemplateArgument type 'int *'
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (int *const &)'
+// CHECK: ParmVarDecl {{.*}} 'int *const &'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (int *&&)'
+// CHECK: ParmVarDecl {{.*}} 'int *&&'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+
+// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, int *&&)'
+// CHECK: ParmVarDecl {{.*}} 'iterator'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK: ParmVarDecl {{.*}} 'int *&&'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK-NOT: LifetimeCaptureByAttr
+
+std::vector<int> ints;
+// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
+// CHECK: TemplateArgument type 'int'
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (const int &)'
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} push_back 'void (int &&)'
+// CHECK-NOT: LifetimeCaptureByAttr
+
+// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, int &&)'
+// CHECK: ParmVarDecl {{.*}} 'iterator'
+// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
+// CHECK-NOT: LifetimeCaptureByAttr
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..4d562bac1e305b 100644
--- a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp
@@ -366,3 +366,48 @@ 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);
+
+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());
+
+ 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(&local);
+}
+
+namespace with_span {
+// Templated view types.
+template<typename T>
+struct [[gsl::Pointer]] Span {
+ Span(const std::vector<T> &V);
+};
+
+void use() {
+ std::vector<Span<int>> spans;
+ spans.push_back(std::vector<int>{1, 2, 3}); // expected-warning {{object whose reference is captured by 'spans' will be destroyed at the end of the full-expression}}
+ std::vector<int> local;
+ spans.push_back(local);
+}
+} // namespace with_span
+} // namespace inferred_capture_by
More information about the cfe-commits
mailing list