https://github.com/kashika0112 created
https://github.com/llvm/llvm-project/pull/204361
This PR implements support for `[[clang::lifetime_capture_by(X)]]` to enable
tracking lifetimes for plain structs and containers like `std::vector` without
requiring manual `[[gsl::Pointer]]` or `[[gsl::Owner]]` annotations. The
implementation extends the `LifetimeAnnotatedOriginTypeCollector` to register
types in capture_by contracts for origin tracking.
This PR also enables the intra-procedural analysis in existing tests using
-Wlifetime-safety and updated expectations to handle the more detailed
flow-sensitive diagnostics.
```cpp
struct MyContainer {
const char* stored_ptr;
};
void captureInto(std::string_view v [[clang::lifetime_capture_by(c)]],
MyContainer& c);
void test_parameter_capture() {
MyContainer container;
{
std::string local = "temporary data";
captureInto(local, container);
}
(void)container;
}
```
Warnings Generated:
```cpp
b10.cpp:14:17: warning: local variable 'local' does not live long enough
[-Wlifetime-safety-use-after-scope]
14 | captureInto(local, container);
| ^~~~~
b10.cpp:15:3: note: destroyed here
15 | }
| ^
b10.cpp:16:9: note: later used here
16 | (void)container;
| ^~~~~~~~~
```
>From 038c9bc70f18059da3097899f5bb3639faaabb0b Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <[email protected]>
Date: Wed, 17 Jun 2026 14:33:44 +0000
Subject: [PATCH] Add support for simple structs without annotation
---
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 23 +-
clang/test/Sema/LifetimeSafety/capture-by.cpp | 362 ++++++++++++++----
clang/test/Sema/LifetimeSafety/safety.cpp | 7 +-
3 files changed, 312 insertions(+), 80 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 55d3b36e3163a..7aa188d22b781 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -81,9 +81,9 @@ class LifetimeAnnotatedOriginTypeCollector
if (!FD)
return;
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+ const auto *MD = dyn_cast<CXXMethodDecl>(FD);
- if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
- MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
+ if (MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
implicitObjectParamIsLifetimeBound(MD)) {
CollectedTypes.push_back(RetType);
return;
@@ -94,6 +94,25 @@ class LifetimeAnnotatedOriginTypeCollector
CollectedTypes.push_back(RetType);
return;
}
+ if (auto *Attr = Param->getAttr<LifetimeCaptureByAttr>()) {
+ for (int Idx : Attr->params()) {
+ if (Idx == LifetimeCaptureByAttr::Global ||
+ Idx == LifetimeCaptureByAttr::Unknown ||
+ Idx == LifetimeCaptureByAttr::Invalid)
+ continue;
+ if (Idx == LifetimeCaptureByAttr::This) {
+ if (MD && MD->isInstance())
+ CollectedTypes.push_back(MD->getFunctionObjectParameterType());
+ } else if (int LogicalIdx =
+ Idx -
+ (MD && MD->isImplicitObjectMemberFunction() ? 1 : 0);
+ LogicalIdx >= 0 &&
+ (unsigned)LogicalIdx < FD->getNumParams()) {
+ CollectedTypes.push_back(
+ FD->getParamDecl(LogicalIdx)->getType().getNonReferenceType());
+ }
+ }
+ }
}
}
};
diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp
b/clang/test/Sema/LifetimeSafety/capture-by.cpp
index 2877d8d6cd5f9..00571fa34d87a 100644
--- a/clang/test/Sema/LifetimeSafety/capture-by.cpp
+++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 --std=c++20 -fsyntax-only -verify -Wdangling-capture %s
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wno-dangling -verify=cfg
-Wlifetime-safety %s
#include "Inputs/lifetime-analysis.h"
@@ -11,13 +12,28 @@ void captureInt(const int &i
[[clang::lifetime_capture_by(x)]], X &x);
void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
-void use() {
- int local;
- captureInt(1, // expected-warning {{object whose reference is captured by
'x' will be destroyed at the end of the full-expression}}
- x);
+void temporary_int_capture() {
+ captureInt(1,x); // expected-warning {{object whose reference is captured by
'x' will be destroyed at the end of the full-expression}}
+ // cfg-warning@-1 {{temporary object does not live long
enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
captureRValInt(1, x); // expected-warning {{object whose reference is
captured by 'x' will be destroyed at the end of the full-expression}}
- captureInt(local, x);
+ // cfg-warning@-1 {{temporary object does not live long
enough}}
+ // cfg-note@-2 {{destroyed here}}
+( void)x; // cfg-note {{later used here}}
+}
+
+void local_int_capture() {
+ {
+ int local;
+ captureInt(local, x); // cfg-warning {{local variable 'local' does not
live long enough}}
+ } // cfg-note {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+}
+
+void safe_int_captures() {
noCaptureInt(1, x);
+ int local;
noCaptureInt(local, x);
}
} // namespace capture_int
@@ -30,12 +46,25 @@ struct X {} x;
void captureString(const std::string &s [[clang::lifetime_capture_by(x)]], X
&x);
void captureRValString(std::string &&s [[clang::lifetime_capture_by(x)]], X
&x);
-void use() {
- std::string local_string;
+void temporary_string_capture() {
captureString(std::string(), x); // expected-warning {{object whose
reference is captured by 'x' will be destroyed at the end of the
full-expression}}
- captureString(local_string, x);
- captureRValString(std::move(local_string), x);
+ // cfg-warning@-1 {{temporary object does
not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
captureRValString(std::string(), x); // expected-warning {{object whose
reference is captured by 'x' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1 {{temporary object
does not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+}
+
+void local_string_capture() {
+ {
+ std::string local_string1, local_string2;
+ captureString(local_string1, x); // cfg-warning {{local
variable 'local_string1' does not live long enough}}
+ captureRValString(std::move(local_string2), x); // cfg-warning {{local
variable 'local_string2' does not live long enough}}
+ // cfg-note@-1 {{result of call to
'move<std::basic_string<char> &>' aliases the storage of local variable
'local_string2'}}
+ } // cfg-note 2 {{destroyed here}}
+ (void)x; // cfg-note 2 {{later used here}}
}
} // namespace capture_string
@@ -43,7 +72,7 @@ void use() {
// Capture std::string_view (gsl pointer types)
// ****************************************************************************
namespace capture_string_view {
-struct X {} x;
+struct X {} x; // cfg-note 2 {{this global dangles}}
void captureStringView(std::string_view s [[clang::lifetime_capture_by(x)]], X
&x);
void captureRValStringView(std::string_view &&sv
[[clang::lifetime_capture_by(x)]], X &x);
void noCaptureStringView(std::string_view sv, X &x);
@@ -53,35 +82,80 @@ std::string_view getNotLifetimeBoundView(const std::string&
s);
const std::string& getLifetimeBoundString(const std::string &s
[[clang::lifetimebound]]);
const std::string& getLifetimeBoundString(std::string_view sv
[[clang::lifetimebound]]);
-void use() {
- std::string_view local_string_view;
- std::string local_string;
- captureStringView(local_string_view, x);
- captureStringView(std::string(), // expected-warning {{object whose
reference is captured by 'x' will be destroyed at the end of the
full-expression}}
- x);
-
- captureStringView(getLifetimeBoundView(local_string), x);
- captureStringView(getNotLifetimeBoundView(std::string()), x);
- captureRValStringView(std::move(local_string_view), x);
+void temporary_string_capture() {
+ captureStringView(std::string(), x); // expected-warning {{object whose
reference is captured by 'x' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1 {{temporary object
does not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
captureRValStringView(std::string(), x); // expected-warning {{object whose
reference is captured by 'x' will be destroyed at the end of the
full-expression}}
- captureRValStringView(std::string_view{"abcd"}, x);
+ // cfg-warning@-1 {{temporary
object does not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+ captureRValStringView(std::string_view{"abcd"}, x); // cfg-warning
{{temporary object does not live long enough}}
+ // cfg-note@-1
{{destroyed here}}
+ (void)x; // cfg-note {{later used
here}}
+}
- noCaptureStringView(local_string_view, x);
- noCaptureStringView(std::string(), x);
+void local_string_capture() {
+ {
+ std::string local_string;
+ captureStringView(getLifetimeBoundView(local_string), x); // cfg-warning
{{local variable 'local_string' does not live long enough}}
+ // cfg-note@-1
{{result of call to 'getLifetimeBoundView' aliases the storage of local
variable 'local_string'}}
+ } // cfg-note
{{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+ std::string_view local_string_view;
+ captureRValStringView(std::move(local_string_view), x); // cfg-warning
{{stack memory associated with local variable 'local_string_view' escapes to
the global variable 'x' which will dangle}}
+}
- // With lifetimebound functions.
- captureStringView(getLifetimeBoundView(
- std::string() // expected-warning {{object whose reference is captured by
'x' will be destroyed at the end of the full-expression}}
- ), x);
- captureRValStringView(getLifetimeBoundView(local_string), x);
+// Lifetimebound captures
+void temporary_string_view_lifetimebound_capture() {
+ captureStringView(getLifetimeBoundView( // cfg-note {{result of call to
'getLifetimeBoundView' aliases the storage of temporary object}}
+ std::string() // expected-warning {{object whose
reference is captured by 'x' will be destroyed at the end of the
full-expression}}
+ ), x); // cfg-warning@-1 {{temporary object
does not live long enough}}
+ // cfg-note@-1 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+ captureStringView(getLifetimeBoundString(std::string()), x); //
expected-warning {{object whose reference is captured by 'x' will be destroyed
at the end of the full-expression}}
+ //
cfg-warning@-1 {{temporary object does not live long enough}}
+ //
cfg-note@-2 {{destroyed here}}
+ //
cfg-note@-3 {{result of call to 'getLifetimeBoundString' aliases the storage of
temporary object}}
+ (void)x; // cfg-note
{{later used here}}
captureRValStringView(getLifetimeBoundView(std::string()), x); //
expected-warning {{object whose reference is captured by 'x' will be destroyed
at the end of the full-expression}}
- captureRValStringView(getNotLifetimeBoundView(std::string()), x);
- noCaptureStringView(getLifetimeBoundView(std::string()), x);
- captureStringView(getLifetimeBoundString(std::string()), x); //
expected-warning {{object whose reference is captured by 'x' will be destroyed
at the end of the full-expression}}
+ //
cfg-warning@-1 {{temporary object does not live long enough}}
+ //
cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note
{{later used here}}
+ captureRValStringView(getNotLifetimeBoundView(std::string()), x); //
cfg-warning {{stack memory associated with temporary object escapes to the
global variable 'x' which will dangle}}
+}
+
+void local_string_lifetimebound_capture() {
+ {
+ std::string local_string;
+ captureRValStringView(getLifetimeBoundView(local_string), x); //
cfg-warning {{temporary object does not live long enough}}
+ //
cfg-note@-1 {{destroyed here}}
+ }
+ (void)x; // cfg-note
{{later used here}}
+}
+
+void temporary_nested_lifetimebound_capture() {
captureStringView(getLifetimeBoundString(getLifetimeBoundView(std::string())),
x); // expected-warning {{object whose reference is captured by 'x' will be
destroyed at the end of the full-expression}}
- captureStringView(getLifetimeBoundString(getLifetimeBoundString(
+
// cfg-warning@-1 {{temporary object does not live long enough}}
+
// cfg-note@-2 {{destroyed here}}
+
// cfg-note@-3 {{result of call to 'getLifetimeBoundView' aliases the
storage of temporary object}}
+
// cfg-note@-4 {{result of call to 'getLifetimeBoundString' aliases the
storage of temporary object}}
+ (void)x;
// cfg-note {{later used here}}
+ captureStringView(getLifetimeBoundString(getLifetimeBoundString(
// cfg-note 2 {{result of call to 'getLifetimeBoundString' aliases the
storage of temporary object}}
std::string() // expected-warning {{object whose reference is captured by
'x' will be destroyed at the end of the full-expression}}
- )), x);
+ )), x); // cfg-warning@-1 {{temporary object does not live long
enough}}
+ // cfg-note@-1 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+}
+
+void safe_captures() {
+ std::string_view local_string_view;
+ captureStringView(local_string_view, x);
+ captureStringView(getNotLifetimeBoundView(std::string()), x);
+ noCaptureStringView(local_string_view, x);
+ noCaptureStringView(std::string(), x);
+ noCaptureStringView(getLifetimeBoundView(std::string()), x);
}
} // namespace capture_string_view
@@ -92,15 +166,25 @@ const std::string* getLifetimeBoundPointer(const
std::string &s [[clang::lifetim
const std::string* getNotLifetimeBoundPointer(const std::string &s);
namespace capture_pointer {
-struct X {} x;
+struct X {} x; // cfg-note {{this global dangles}}
void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X
&x);
-void use() {
+
+void temporary_pointer_lifetimebound_capture() {
capturePointer(getLifetimeBoundPointer(std::string()), x); //
expected-warning {{object whose reference is captured by 'x' will be destroyed
at the end of the full-expression}}
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2
{{destroyed here}}
+ // cfg-note@-3
{{result of call to 'getLifetimeBoundPointer' aliases the storage of temporary
object}}
+ (void)x; // cfg-note
{{later used here}}
+}
+
+void temporary_nested_lifetimebound_capture() {
capturePointer(getLifetimeBoundPointer(*getLifetimeBoundPointer(
std::string() // expected-warning {{object whose reference is captured by
'x' will be destroyed at the end of the full-expression}}
- )), x);
- capturePointer(getNotLifetimeBoundPointer(std::string()), x);
+ )), x); // cfg-warning@-1 {{stack memory associated with temporary
object escapes to the global variable 'x' which will dangle}}
+}
+void safe_capture() {
+ capturePointer(getNotLifetimeBoundPointer(std::string()), x);
}
} // namespace capture_pointer
@@ -108,23 +192,41 @@ void use() {
// Arrays and initializer lists.
// ****************************************************************************
namespace init_lists {
-struct X {} x;
+struct X {} x; // cfg-note {{this global dangles}}
void captureVector(const std::vector<int> &a
[[clang::lifetime_capture_by(x)]], X &x);
void captureArray(int array [[clang::lifetime_capture_by(x)]] [2], X &x);
void captureInitList(std::initializer_list<int> abc
[[clang::lifetime_capture_by(x)]], X &x);
-
std::initializer_list<int> getLifetimeBoundInitList(std::initializer_list<int>
abc [[clang::lifetimebound]]);
-void use() {
+void temporary_vector_capture() {
captureVector({1, 2, 3}, x); // expected-warning {{object whose reference is
captured by 'x' will be destroyed at the end of the full-expression}}
+ // cfg-warning@-1 {{temporary object does not
live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
captureVector(std::vector<int>{}, x); // expected-warning {{object whose
reference is captured by 'x' will be destroyed at the end of the
full-expression}}
- std::vector<int> local_vector;
- captureVector(local_vector, x);
+ // cfg-warning@-1 {{temporary object
does not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+}
+
+void local_vector_capture() {
+ {
+ std::vector<int> local_vector;
+ captureVector(local_vector, x); // cfg-warning {{local variable
'local_vector' does not live long enough}}
+ } // cfg-note {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
+}
+
+void local_array_capture() {
int local_array[2];
- captureArray(local_array, x);
- captureInitList({1, 2}, x); // expected-warning {{object whose reference is
captured by 'x' will be destroyed at the end of the full-expression}}
- captureInitList(getLifetimeBoundInitList({1, 2}), x); // expected-warning
{{object whose reference is captured by 'x' will be destroyed at the end of the
full-expression}}
+ captureArray(local_array, x); // cfg-warning {{stack memory associated
with local variable 'local_array' escapes to the global variable 'x' which will
dangle}}
+}
+
+// FIXME: Add support for initializer lists in -Wlifetime-safety
+void initializer_list_capture() {
+ captureInitList({1, 2}, x); // expected-warning {{object whose reference is
captured by 'x' will be destroyed at the end of the full-expression}}
+ captureInitList(getLifetimeBoundInitList({1, 2}), x); // expected-warning
{{object whose reference is captured by 'x' will be destroyed at the end of the
full-expression}}
}
} // namespace init_lists
@@ -136,6 +238,8 @@ struct X {} x;
struct S {
void capture(X &x) [[clang::lifetime_capture_by(x)]];
};
+
+// FIXME: Add support for capture of method declarations in -Wlifetime-safety
void use() {
S{}.capture(x); // expected-warning {{object whose reference is captured by
'x' will be destroyed at the end of the full-expression}}
S s;
@@ -166,6 +270,7 @@ void captureByUnknown(std::string_view s
[[clang::lifetime_capture_by(unknown)]]
std::string_view getLifetimeBoundView(const std::string& s
[[clang::lifetimebound]]);
+// FIXME: Add support for capture by global and unknown in -Wlifetime-safety
void use() {
std::string_view local_string_view;
std::string local_string;
@@ -195,12 +300,30 @@ std::string_view getLifetimeBoundView(const std::string&
s [[clang::lifetimeboun
std::string_view getNotLifetimeBoundView(const std::string& s);
const std::string& getLifetimeBoundString(const std::string &s
[[clang::lifetimebound]]);
-void use() {
+void temporary_capture_by_this() {
S s;
s.captureInt(1); // expected-warning {{object whose reference is captured by
's' will be destroyed at the end of the full-expression}}
+ // cfg-warning@-1 {{temporary object does not live long
enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)s; // cfg-note {{later used here}}
s.captureView(std::string()); // expected-warning {{object whose reference
is captured by 's' will be destroyed at the end of the full-expression}}
- s.captureView(getLifetimeBoundView(std::string())); // expected-warning
{{object whose reference is captured by 's' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1 {{temporary object does not
live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)s; // cfg-note {{later used here}}
+}
+
+void lifetimebound_capture_by_this() {
+ S s;
+ s.captureView(getLifetimeBoundView(std::string())); // expected-warning
{{object whose reference is captured by 's' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2
{{destroyed here}}
+ // cfg-note@-3
{{esult of call to 'getLifetimeBoundView' aliases the storage of temporary
object}}
+ (void)s; // cfg-note {{later
used here}}
s.captureView(getLifetimeBoundString(std::string())); // expected-warning
{{object whose reference is captured by 's' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2
{{destroyed here}}
+ // cfg-note@-3
{{result of call to 'getLifetimeBoundString' aliases the storage of temporary
object}}
+ (void)s; // cfg-note {{later
used here}}
s.captureView(getNotLifetimeBoundView(std::string()));
}
} // namespace capture_by_this
@@ -254,9 +377,18 @@ struct MySet {
void user_defined_containers() {
MySet<int> set_of_int;
set_of_int.insert(1); // expected-warning {{object whose reference is
captured by 'set_of_int' will be destroyed at the end of the full-expression}}
+ // cfg-warning@-1 {{temporary object does not live
long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)set_of_int; // cfg-note {{later used here}}
MySet<std::string_view> set_of_sv;
- set_of_sv.insert(std::string()); // expected-warning {{object whose
reference is captured by 'set_of_sv' will be destroyed at the end of the
full-expression}}
- set_of_sv.insert(std::string_view());
+ set_of_sv.insert(std::string()); // expected-warning {{object whose
reference is captured by 'set_of_sv' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1 {{temporary object
does not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)set_of_sv; // cfg-note {{later used here}}
+ set_of_sv.insert(std::string_view()); // cfg-warning {{temporary object
does not live long enough}}
+ // cfg-note@-1 {{destroyed here}}
+ (void)set_of_sv; // cfg-note {{later used here}}
+
}
} // namespace containers_no_distinction
@@ -286,13 +418,29 @@ void use_container() {
MyVector<std::string_view> vector_of_view;
vector_of_view.push_back(std::string()); // expected-warning {{object whose
reference is captured by 'vector_of_view' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1 {{temporary
object does not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)vector_of_view; // cfg-note {{later used here}}
vector_of_view.push_back(getLifetimeBoundView(std::string())); //
expected-warning {{object whose reference is captured by 'vector_of_view' will
be destroyed at the end of the full-expression}}
-
+ //
cfg-warning@-1 {{temporary object does not live long enough}}
+ //
cfg-note@-2 {{destroyed here}}
+ (void)vector_of_view; // cfg-note
{{later used here}}
+
MyVector<const std::string*> vector_of_pointer;
vector_of_pointer.push_back(getLifetimeBoundPointer(std::string())); //
expected-warning {{object whose reference is captured by 'vector_of_pointer'
will be destroyed at the end of the full-expression}}
+ //
cfg-warning@-1 {{temporary object does not live long enough}}
+ //
cfg-note@-2 {{destroyed here}}
+ (void)vector_of_pointer; //
cfg-note {{later used here}}
vector_of_pointer.push_back(getLifetimeBoundPointer(*getLifetimeBoundPointer(std::string())));
// expected-warning {{object whose reference is captured by
'vector_of_pointer' will be destroyed at the end of the full-expression}}
- vector_of_pointer.push_back(getLifetimeBoundPointer(local));
- vector_of_pointer.push_back(getNotLifetimeBoundPointer(std::string()));
+
// cfg-warning@-1 {{temporary object does not live long
enough}}
+
// cfg-note@-2 {{destroyed here}}
+ (void)vector_of_pointer;
// cfg-note {{later used here}}
+ vector_of_pointer.push_back(getLifetimeBoundPointer(local)); //
cfg-warning {{temporary object does not live long enough}}
+ //
cfg-note@-1 {{destroyed here}}
+ (void)vector_of_pointer; //
cfg-note {{later used here}}
+ vector_of_pointer.push_back(getNotLifetimeBoundPointer(std::string())); //
cfg-warning {{temporary object does not live long enough}}
+ //
cfg-note@-1 {{destroyed here}}
+ (void)vector_of_pointer; //
cfg-note {{later used here}}
}
// ****************************************************************************
@@ -323,18 +471,38 @@ const std::string&
getLifetimeBoundString(std::string_view sv [[clang::lifetimeb
void use_my_view() {
std::string local;
MyVector<MyStringView> vector_of_my_view;
- vector_of_my_view.push_back(getMySV());
- vector_of_my_view.push_back(MyStringView{});
- vector_of_my_view.push_back(std::string_view{});
- vector_of_my_view.push_back(std::string{}); // expected-warning {{object
whose reference is captured by 'vector_of_my_view' will be destroyed at the end
of the full-expression}}
+ vector_of_my_view.push_back(getMySV()); // cfg-warning {{temporary
object does not live long enough}}
+ // cfg-note@-1 {{destroyed
here}}
+ (void)vector_of_my_view; // cfg-note {{later used
here}}
+ vector_of_my_view.push_back(MyStringView{}); // cfg-warning {{temporary
object does not live long enough}}
+ // cfg-note@-1 {{destroyed
here}}
+ (void)vector_of_my_view; // cfg-note {{later used
here}}
+ vector_of_my_view.push_back(std::string_view{}); // cfg-warning {{temporary
object does not live long enough}}
+ // cfg-note@-1 {{destroyed
here}}
+ (void)vector_of_my_view; // cfg-note {{later used
here}}
+ vector_of_my_view.push_back(std::string{}); // expected-warning
{{object whose reference is captured by 'vector_of_my_view' will be destroyed
at the end of the full-expression}}
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2 {{destroyed
here}}
+ (void)vector_of_my_view; // cfg-note {{later used
here}}
vector_of_my_view.push_back(getLifetimeBoundView(std::string{})); //
expected-warning {{object whose reference is captured by 'vector_of_my_view'
will be destroyed at the end of the full-expression}}
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2 {{destroyed
here}}
+ (void)vector_of_my_view; // cfg-note {{later used
here}}
vector_of_my_view.push_back(getLifetimeBoundString(getLifetimeBoundView(std::string{})));
// expected-warning {{object whose reference is captured by
'vector_of_my_view' will be destroyed at the end of the full-expression}}
-
vector_of_my_view.push_back(getNotLifetimeBoundView(getLifetimeBoundString(getLifetimeBoundView(std::string{}))));
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2 {{destroyed
here}}
+ (void)vector_of_my_view; // cfg-note {{later used
here}}
+
vector_of_my_view.push_back(getNotLifetimeBoundView(getLifetimeBoundString(getLifetimeBoundView(std::string{}))));
// cfg-warning {{temporary object does not live long enough}}
+ // cfg-note@-1 {{destroyed
here}}
+ (void)vector_of_my_view; // cfg-note {{later used
here}}
// Use with container of other view types.
MyVector<std::string_view> vector_of_view;
- vector_of_view.push_back(getMySV());
+ vector_of_view.push_back(getMySV()); // cfg-warning {{temporary
object does not live long enough}}
+ // cfg-note@-1 {{destroyed
here}}
+ (void)vector_of_view; // cfg-note {{later used
here}}
vector_of_view.push_back(getMySVNotP());
+ (void)vector_of_view;
}
// ****************************************************************************
@@ -345,10 +513,18 @@ void use_with_optional_view() {
std::optional<std::string_view> optional_of_view;
vector_of_view.push_back(optional_of_view.value());
- vector_of_view.push_back(getOptionalS().value()); // expected-warning
{{object whose reference is captured by 'vector_of_view' will be destroyed at
the end of the full-expression}}
-
- vector_of_view.push_back(getOptionalSV().value());
- vector_of_view.push_back(getOptionalMySV().value());
+ vector_of_view.push_back(getOptionalS().value()); // expected-warning
{{object whose reference is captured by 'vector_of_view' will be destroyed at
the end of the full-expression}}
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2
{{destroyed here}}
+ (void)vector_of_view; // cfg-note {{later
used here}}
+ vector_of_view.push_back(getOptionalSV().value()); // cfg-warning
{{temporary object does not live long enough}}
+ // cfg-note@-1
{{destroyed here}}
+ // cfg-note@-2
{{result of call to 'value' aliases the storage of temporary object}}
+ (void)vector_of_view; // cfg-note {{later
used here}}
+ vector_of_view.push_back(getOptionalMySV().value()); // cfg-warning
{{temporary object does not live long enough}}
+ // cfg-note@-1
{{destroyed here}}
+ // cfg-note@-2
{{result of call to 'value' aliases the storage of temporary object}}
+ (void)vector_of_view; // cfg-note {{later
used here}}
vector_of_view.push_back(getOptionalMySVNotP().value());
}
} // namespace conatiners_with_different
@@ -367,17 +543,30 @@ void capture3(const std::string_view& s
[[clang::lifetime_capture_by(x)]], std::
void use() {
std::vector<std::string_view> x1;
capture1(std::string(), x1); // expected-warning {{object whose reference is
captured by 'x1' will be destroyed at the end of the full-expression}}
+ // cfg-warning@-1 {{temporary object does not
live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x1; // cfg-note {{later used here}}
capture1(std::string_view(), x1);
std::vector<std::string_view*> x2;
// Clang considers 'const std::string_view&' to refer to the owner
// 'std::string' and not 'std::string_view'. Therefore no diagnostic here.
- capture2(std::string_view(), x2);
- capture2(std::string(), x2); // expected-warning {{object whose reference is
captured by 'x2' will be destroyed at the end of the full-expression}}
+ capture2(std::string_view(), x2); // cfg-warning {{temporary object does
not live long enough}}
+ // cfg-note@-1 {{destroyed here}}
+ (void)x2; // cfg-note {{later used here}}
+ capture2(std::string(), x2); // expected-warning {{object whose
reference is captured by 'x2' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1 {{temporary object does
not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x2; // cfg-note {{later used here}}
std::vector<std::string_view> x3;
- capture3(std::string_view(), x3);
- capture3(std::string(), x3); // expected-warning {{object whose reference is
captured by 'x3' will be destroyed at the end of the full-expression}}
+ capture3(std::string_view(), x3); // cfg-warning {{temporary object does
not live long enough}}
+ // cfg-note@-1 {{destroyed here}}
+ (void)x3; // cfg-note {{later used here}}
+ capture3(std::string(), x3); // expected-warning {{object whose
reference is captured by 'x3' will be destroyed at the end of the
full-expression}}
+ // cfg-warning@-1 {{temporary object does
not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)x3; // cfg-note {{later used here}}
}
} // namespace temporary_views
@@ -391,15 +580,32 @@ 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}}
+ // cfg-warning@-1 {{temporary object does
not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)views; // cfg-note {{later used here}}
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::string()); // expected-warning {{object whose reference
is captured by 'views' will be destroyed at the end of the full-expression}}
+ // cfg-warning@-1 {{temporary object does
not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)views; // cfg-note {{later used here}}
+ 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}}
+ // cfg-warning@-1
{{temporary object does not live long enough}}
+ // cfg-note@-2
{{destroyed here}}
+ (void)views; // cfg-note {{later
used here}}
+ views.push_back(getNotLifetimeBoundView(std::string())); // cfg-warning
{{temporary object does not live long enough}}
+ // cfg-note@-1
{{destroyed here}}
+ (void)views; // cfg-note {{later
used here}}
+ {
+ std::string local1, local2;
+ views.push_back(local1); // cfg-warning {{temporary object
does not live long enough}}
+ // cfg-note@-1 {{destroyed here}}
+ (void)views; // cfg-note {{later used here}}
+ views.insert(views.end(), local2); // cfg-warning {{temporary object
does not live long enough}}
+ // cfg-note@-1 {{destroyed here}}
+ }
+ (void)views; // cfg-note {{later used here}}
std::vector<std::string> strings;
strings.push_back(std::string());
@@ -407,6 +613,7 @@ void use() {
std::vector<const std::string*> pointers;
pointers.push_back(getLifetimeBoundPointer(std::string()));
+ std::string local;
pointers.push_back(&local);
}
@@ -420,8 +627,15 @@ struct [[gsl::Pointer]] Span {
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);
+ // cfg-warning@-1 {{temporary
object does not live long enough}}
+ // cfg-note@-2 {{destroyed here}}
+ (void)spans; // cfg-note {{later used here}}
+ {
+ std::vector<int> local;
+ spans.push_back(local); // cfg-warning {{temporary object does not live
long enough}}
+ // cfg-note@-1 {{destroyed here}}
+ }
+ (void)spans; // cfg-note {{later used here}}
}
} // namespace with_span
} // namespace inferred_capture_by
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp
b/clang/test/Sema/LifetimeSafety/safety.cpp
index c838918eb556d..372eb48fdca6c 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -3644,7 +3644,6 @@ void member_capture() {
(void)c.stored; // expected-note {{later used here}}
}
-// FIXME: Add support for simple containers without annotations.
struct SimpleContainer {
View stored;
void set(View s [[clang::lifetime_capture_by(this)]]);
@@ -3654,9 +3653,9 @@ void member_capture_simple_container() {
SimpleContainer c;
{
MyObj local;
- c.set(local);
- }
- (void)c.stored;
+ c.set(local); // expected-warning {{local variable 'local' does not live
long enough}}
+ } // expected-note {{destroyed here}}
+ (void)c.stored; // expected-note {{later used here}}
}
void captureTwo(View& into,
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits