https://github.com/usx95 created https://github.com/llvm/llvm-project/pull/117122
This is behind `-Wdangling-capture` warning which is disabled by default. >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] [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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits