Skip to content

Commit 912c502

Browse files
authored
[clang] Infer lifetime_capture_by for STL containers (#117122)
This is behind `-Wdangling-capture` warning which is disabled by default.
1 parent 875b10f commit 912c502

File tree

6 files changed

+200
-0
lines changed

6 files changed

+200
-0
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,9 @@ class Sema final : public SemaBase {
17571757
/// Add [[clang:::lifetimebound]] attr for std:: functions and methods.
17581758
void inferLifetimeBoundAttribute(FunctionDecl *FD);
17591759

1760+
/// Add [[clang:::lifetime_capture_by(this)]] to STL container methods.
1761+
void inferLifetimeCaptureByAttribute(FunctionDecl *FD);
1762+
17601763
/// Add [[gsl::Pointer]] attributes for std:: types.
17611764
void inferGslPointerAttribute(TypedefNameDecl *TD);
17621765

clang/lib/Sema/SemaAttr.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313

14+
#include "CheckExprLifetime.h"
1415
#include "clang/AST/ASTConsumer.h"
1516
#include "clang/AST/Attr.h"
1617
#include "clang/AST/Expr.h"
@@ -268,6 +269,44 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
268269
}
269270
}
270271

272+
static bool isPointerLikeType(QualType QT) {
273+
QT = QT.getNonReferenceType();
274+
if (QT->isPointerType())
275+
return true;
276+
auto *RD = QT->getAsCXXRecordDecl();
277+
if (!RD)
278+
return false;
279+
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
280+
RD = CTSD->getSpecializedTemplate()->getTemplatedDecl();
281+
return RD->hasAttr<PointerAttr>();
282+
}
283+
284+
void Sema::inferLifetimeCaptureByAttribute(FunctionDecl *FD) {
285+
if (!FD)
286+
return;
287+
auto *MD = dyn_cast<CXXMethodDecl>(FD);
288+
if (!MD || !MD->getIdentifier() || !MD->getParent()->isInStdNamespace())
289+
return;
290+
// FIXME: Infer for operator[] for map-like containers. For example:
291+
// std::map<string_view, ...> m;
292+
// m[ReturnString(..)] = ...;
293+
static const llvm::StringSet<> CapturingMethods{"insert", "push",
294+
"push_front", "push_back"};
295+
if (!CapturingMethods.contains(MD->getName()))
296+
return;
297+
// Do not infer if any parameter is explicitly annotated.
298+
for (ParmVarDecl *PVD : MD->parameters())
299+
if (PVD->hasAttr<LifetimeCaptureByAttr>())
300+
return;
301+
for (ParmVarDecl *PVD : MD->parameters()) {
302+
if (isPointerLikeType(PVD->getType())) {
303+
int CaptureByThis[] = {LifetimeCaptureByAttr::THIS};
304+
PVD->addAttr(
305+
LifetimeCaptureByAttr::CreateImplicit(Context, CaptureByThis, 1));
306+
}
307+
}
308+
}
309+
271310
void Sema::inferNullableClassAttribute(CXXRecordDecl *CRD) {
272311
static const llvm::StringSet<> Nullable{
273312
"auto_ptr", "shared_ptr", "unique_ptr", "exception_ptr",

clang/lib/Sema/SemaDecl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11916,6 +11916,7 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
1191611916
NamedDecl *OldDecl = nullptr;
1191711917
bool MayNeedOverloadableChecks = false;
1191811918

11919+
inferLifetimeCaptureByAttribute(NewFD);
1191911920
// Merge or overload the declaration with an existing declaration of
1192011921
// the same name, if appropriate.
1192111922
if (!Previous.empty()) {
@@ -16719,6 +16720,7 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) {
1671916720

1672016721
LazyProcessLifetimeCaptureByParams(FD);
1672116722
inferLifetimeBoundAttribute(FD);
16723+
inferLifetimeCaptureByAttribute(FD);
1672216724
AddKnownFunctionAttributesForReplaceableGlobalAllocationFunction(FD);
1672316725

1672416726
// If C++ exceptions are enabled but we are told extern "C" functions cannot

clang/test/AST/attr-lifetime-capture-by.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,109 @@ struct S {
77
};
88

99
// CHECK: CXXMethodDecl {{.*}}clang::lifetime_capture_by(a, b, global)
10+
11+
// ****************************************************************************
12+
// Infer annotation for STL container methods.
13+
// ****************************************************************************
14+
namespace __gnu_cxx {
15+
template <typename T>
16+
struct basic_iterator {};
17+
}
18+
19+
namespace std {
20+
template<typename T> class allocator {};
21+
template <typename T, typename Alloc = allocator<T>>
22+
struct vector {
23+
typedef __gnu_cxx::basic_iterator<T> iterator;
24+
iterator begin();
25+
26+
vector();
27+
28+
void push_back(const T&);
29+
void push_back(T&&);
30+
31+
void insert(iterator, T&&);
32+
};
33+
} // namespace std
34+
35+
// CHECK-NOT: LifetimeCaptureByAttr
36+
37+
struct [[gsl::Pointer()]] View {};
38+
std::vector<View> views;
39+
// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
40+
// CHECK: TemplateArgument type 'View'
41+
// CHECK-NOT: LifetimeCaptureByAttr
42+
43+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (const View &)'
44+
// CHECK: ParmVarDecl {{.*}} 'const View &'
45+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
46+
// CHECK-NOT: LifetimeCaptureByAttr
47+
48+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (View &&)'
49+
// CHECK: ParmVarDecl {{.*}} 'View &&'
50+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
51+
52+
// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, View &&)'
53+
// CHECK: ParmVarDecl {{.*}} 'iterator'
54+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
55+
// CHECK: ParmVarDecl {{.*}} 'View &&'
56+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
57+
// CHECK-NOT: LifetimeCaptureByAttr
58+
59+
template <class T> struct [[gsl::Pointer()]] ViewTemplate {};
60+
std::vector<ViewTemplate<int>> templated_views;
61+
// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
62+
// CHECK: TemplateArgument type 'ViewTemplate<int>'
63+
// CHECK-NOT: LifetimeCaptureByAttr
64+
65+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (const ViewTemplate<int> &)'
66+
// CHECK: ParmVarDecl {{.*}} 'const ViewTemplate<int> &'
67+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
68+
// CHECK-NOT: LifetimeCaptureByAttr
69+
70+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (ViewTemplate<int> &&)'
71+
// CHECK: ParmVarDecl {{.*}} 'ViewTemplate<int> &&'
72+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
73+
74+
// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, ViewTemplate<int> &&)'
75+
// CHECK: ParmVarDecl {{.*}} 'iterator'
76+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
77+
// CHECK: ParmVarDecl {{.*}} 'ViewTemplate<int> &&'
78+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
79+
// CHECK-NOT: LifetimeCaptureByAttr
80+
81+
std::vector<int*> pointers;
82+
// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
83+
// CHECK: TemplateArgument type 'int *'
84+
// CHECK-NOT: LifetimeCaptureByAttr
85+
86+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (int *const &)'
87+
// CHECK: ParmVarDecl {{.*}} 'int *const &'
88+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
89+
// CHECK-NOT: LifetimeCaptureByAttr
90+
91+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (int *&&)'
92+
// CHECK: ParmVarDecl {{.*}} 'int *&&'
93+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
94+
95+
// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, int *&&)'
96+
// CHECK: ParmVarDecl {{.*}} 'iterator'
97+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
98+
// CHECK: ParmVarDecl {{.*}} 'int *&&'
99+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
100+
// CHECK-NOT: LifetimeCaptureByAttr
101+
102+
std::vector<int> ints;
103+
// CHECK: ClassTemplateSpecializationDecl {{.*}} struct vector definition implicit_instantiation
104+
// CHECK: TemplateArgument type 'int'
105+
106+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (const int &)'
107+
// CHECK-NOT: LifetimeCaptureByAttr
108+
109+
// CHECK: CXXMethodDecl {{.*}} push_back 'void (int &&)'
110+
// CHECK-NOT: LifetimeCaptureByAttr
111+
112+
// CHECK: CXXMethodDecl {{.*}} insert 'void (iterator, int &&)'
113+
// CHECK: ParmVarDecl {{.*}} 'iterator'
114+
// CHECK: LifetimeCaptureByAttr {{.*}} Implicit
115+
// CHECK-NOT: LifetimeCaptureByAttr

clang/test/Sema/Inputs/lifetime-analysis.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ struct vector {
4949
vector(InputIterator first, InputIterator __last);
5050

5151
T &at(int n);
52+
53+
void push_back(const T&);
54+
void push_back(T&&);
55+
56+
void insert(iterator, T&&);
5257
};
5358

5459
template<typename T>

clang/test/Sema/warn-lifetime-analysis-capture-by.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,48 @@ void use() {
366366
capture3(std::string(), x3); // expected-warning {{object whose reference is captured by 'x3' will be destroyed at the end of the full-expression}}
367367
}
368368
} // namespace temporary_views
369+
370+
// ****************************************************************************
371+
// Inferring annotation for STL containers
372+
// ****************************************************************************
373+
namespace inferred_capture_by {
374+
const std::string* getLifetimeBoundPointer(const std::string &s [[clang::lifetimebound]]);
375+
const std::string* getNotLifetimeBoundPointer(const std::string &s);
376+
377+
std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimebound]]);
378+
std::string_view getNotLifetimeBoundView(const std::string& s);
379+
void use() {
380+
std::string local;
381+
std::vector<std::string_view> views;
382+
views.push_back(std::string()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
383+
views.insert(views.begin(),
384+
std::string()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}}
385+
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}}
386+
views.push_back(getNotLifetimeBoundView(std::string()));
387+
views.push_back(local);
388+
views.insert(views.end(), local);
389+
390+
std::vector<std::string> strings;
391+
strings.push_back(std::string());
392+
strings.insert(strings.begin(), std::string());
393+
394+
std::vector<const std::string*> pointers;
395+
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}}
396+
pointers.push_back(&local);
397+
}
398+
399+
namespace with_span {
400+
// Templated view types.
401+
template<typename T>
402+
struct [[gsl::Pointer]] Span {
403+
Span(const std::vector<T> &V);
404+
};
405+
406+
void use() {
407+
std::vector<Span<int>> spans;
408+
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}}
409+
std::vector<int> local;
410+
spans.push_back(local);
411+
}
412+
} // namespace with_span
413+
} // namespace inferred_capture_by

0 commit comments

Comments
 (0)