llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Kashika Akhouri (kashika0112)
<details>
<summary>Changes</summary>
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;
| ^~~~~~~~~
```
---
Patch is 39.96 KiB, truncated to 20.00 KiB below, full version:
https://github.com/llvm/llvm-project/pull/204361.diff
3 Files Affected:
- (modified) clang/lib/Analysis/LifetimeSafety/Origins.cpp (+21-2)
- (modified) clang/test/Sema/LifetimeSafety/capture-by.cpp (+288-74)
- (modified) clang/test/Sema/LifetimeSafety/safety.cpp (+3-4)
``````````diff
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
fu...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/204361
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits