https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/117122
>From 9a57223b06a8331a0ef123739a430863dee19d98 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Thu, 21 Nov 2024 07:00:56 +0000 Subject: [PATCH 1/2] [clang] Infer lifetime_capture_by for STL containers --- clang/include/clang/Sema/Sema.h | 3 + clang/lib/Sema/SemaAttr.cpp | 34 ++++++++ clang/lib/Sema/SemaDecl.cpp | 2 + clang/test/Sema/Inputs/lifetime-analysis.h | 5 ++ .../warn-lifetime-analysis-capture-by.cpp | 79 +++++++++++++++++++ 5 files changed, 123 insertions(+) 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 >From 2c8f6700adffeb4c82d1c7a32b13ead911910735 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Fri, 22 Nov 2024 08:32:35 +0000 Subject: [PATCH 2/2] add ast tests --- clang/include/clang/Sema/Sema.h | 2 +- clang/lib/Sema/SemaAttr.cpp | 13 ++- clang/test/AST/attr-lifetime-capture-by.cpp | 103 ++++++++++++++++++ .../warn-lifetime-analysis-capture-by.cpp | 58 ++-------- 4 files changed, 125 insertions(+), 51 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 9bafcfec5d4786..5fe23e0d0efd3b 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1757,7 +1757,7 @@ 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. + /// Add [[clang:::lifetime_capture_by(this)]] to STL container methods. void inferLifetimeCaptureByAttribute(FunctionDecl *FD); /// Add [[gsl::Pointer]] attributes for std:: types. diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp index 507f7c40d58782..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,14 +269,13 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) { } } -static bool IsPointerLikeType(QualType QT) { +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>(); @@ -287,14 +287,19 @@ void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) { 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; - for (ParmVarDecl *PVD : MD->parameters()) { + // Do not infer if any parameter is explicitly annotated. + for (ParmVarDecl *PVD : MD->parameters()) if (PVD->hasAttr<LifetimeCaptureByAttr>()) return; - if (IsPointerLikeType(PVD->getType())) { + for (ParmVarDecl *PVD : MD->parameters()) { + if (isPointerLikeType(PVD->getType())) { int CaptureByThis[] = {LifetimeCaptureByAttr::THIS}; PVD->addAttr( LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1)); diff --git a/clang/test/AST/attr-lifetime-capture-by.cpp b/clang/test/AST/attr-lifetime-capture-by.cpp index da2eb0cf3d592e..11133c6047efb2 100644 --- a/clang/test/AST/attr-lifetime-capture-by.cpp +++ b/clang/test/AST/attr-lifetime-capture-by.cpp @@ -7,3 +7,106 @@ 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 +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 \ No newline at end of file diff --git a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp index 462cb2d3f3fd6e..4d562bac1e305b 100644 --- a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp @@ -374,7 +374,6 @@ 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() { @@ -391,57 +390,24 @@ void use() { 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())); + pointers.push_back(&local); } -} // 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(); +namespace with_span { +// Templated view types. +template<typename T> +struct [[gsl::Pointer]] Span { + Span(const std::vector<T> &V); +}; 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{}); + 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_optional +} // namespace with_span } // namespace inferred_capture_by _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits