[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