[clang] Support [[clang::lifetime_capture_by(X)]] in Plain Containers (PR #204361)
Kashika Akhouri via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 10 01:39:00 PDT 2026
https://github.com/kashika0112 updated https://github.com/llvm/llvm-project/pull/204361
>From 038c9bc70f18059da3097899f5bb3639faaabb0b Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Wed, 17 Jun 2026 14:33:44 +0000
Subject: [PATCH 1/3] 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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -2 {{destroyed here}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -2 {{destroyed here}}
+ // cfg-note at -3 {{result of call to 'getLifetimeBoundView' aliases the storage of temporary object}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -2 {{destroyed here}}
+ // cfg-note at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -2 {{destroyed here}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -2 {{destroyed here}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -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 at -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 at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{destroyed here}}
+ // cfg-note at -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 at -1 {{destroyed here}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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 at -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 at -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 at -1 {{temporary object does not live long enough}}
+ // cfg-note at -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 at -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,
>From 3eb0b54a7a0d41b72b96bf51d25d9a402c557a1a Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Tue, 30 Jun 2026 16:50:08 +0000
Subject: [PATCH 2/3] Resolve false positives
---
.../LifetimeSafety/FactsGenerator.cpp | 24 +-
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 26 +-
clang/test/Sema/LifetimeSafety/capture-by.cpp | 360 +++++++++---------
3 files changed, 213 insertions(+), 197 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9fbfaf8ae606b..3f1d9842f951d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -896,28 +896,34 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
const auto *Method = dyn_cast<CXXMethodDecl>(FD);
bool IsInstance =
Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD);
- auto getArgCaptureBy = [FD,
- IsInstance](unsigned I) -> LifetimeCaptureByAttr * {
- const ParmVarDecl *PVD = nullptr;
+ auto getParamDeclAt = [FD, IsInstance](unsigned I) -> const ParmVarDecl * {
if (IsInstance) {
// FIXME: Add support for I == 0 i.e. capture_by on function declarations
if (I > 0 && I - 1 < FD->getNumParams())
- PVD = FD->getParamDecl(I - 1);
+ return FD->getParamDecl(I - 1);
} else {
if (I < FD->getNumParams())
- PVD = FD->getParamDecl(I);
+ return FD->getParamDecl(I);
}
- return PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr;
+ return nullptr;
};
for (unsigned I = 0; I < Args.size(); ++I) {
- const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I);
+ const ParmVarDecl *PVD = getParamDeclAt(I);
+ if (!PVD)
+ continue;
+ const auto *Attr = PVD->getAttr<LifetimeCaptureByAttr>();
if (!Attr)
continue;
OriginList *CapturedOriginList = getOriginsList(*Args[I]);
if (!CapturedOriginList)
continue;
- if (!CapturedOriginList)
- continue;
+ if (QualType ParamType = PVD->getType();
+ ParamType->isReferenceType() &&
+ isGslPointerType(ParamType->getPointeeType())) {
+ if (CapturedOriginList->getLength() > 1) {
+ CapturedOriginList = CapturedOriginList->peelOuterOrigin();
+ }
+ }
for (int CapturingArgIdx : Attr->params()) {
// FIXME: Add support for capturing to Global/unknown.
if (CapturingArgIdx == LifetimeCaptureByAttr::Global ||
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 7aa188d22b781..f3248bdf3ba8e 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -57,13 +57,16 @@ class LifetimeAnnotatedOriginTypeCollector
bool VisitCallExpr(const CallExpr *CE) {
// Indirect calls (e.g., function pointers) are skipped because lifetime
// annotations currently apply to declarations, not types.
- if (const auto *FD = CE->getDirectCallee())
+ if (const auto *FD = CE->getDirectCallee()) {
collect(FD, FD->getReturnType());
+ collectCaptureBy(FD);
+ }
return true;
}
bool VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
collect(CCE->getConstructor(), CCE->getType());
+ collectCaptureBy(CCE->getConstructor());
return true;
}
@@ -81,9 +84,9 @@ class LifetimeAnnotatedOriginTypeCollector
if (!FD)
return;
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
- const auto *MD = dyn_cast<CXXMethodDecl>(FD);
- if (MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
implicitObjectParamIsLifetimeBound(MD)) {
CollectedTypes.push_back(RetType);
return;
@@ -94,6 +97,17 @@ class LifetimeAnnotatedOriginTypeCollector
CollectedTypes.push_back(RetType);
return;
}
+ }
+ }
+
+ void collectCaptureBy(const FunctionDecl *FD) {
+ if (!FD)
+ return;
+ FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+ const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ bool IsInstance = MD && MD->isInstance();
+ int Offset = (MD && MD->isImplicitObjectMemberFunction()) ? 1 : 0;
+ for (const auto *Param : FD->parameters()) {
if (auto *Attr = Param->getAttr<LifetimeCaptureByAttr>()) {
for (int Idx : Attr->params()) {
if (Idx == LifetimeCaptureByAttr::Global ||
@@ -101,11 +115,9 @@ class LifetimeAnnotatedOriginTypeCollector
Idx == LifetimeCaptureByAttr::Invalid)
continue;
if (Idx == LifetimeCaptureByAttr::This) {
- if (MD && MD->isInstance())
+ if (IsInstance)
CollectedTypes.push_back(MD->getFunctionObjectParameterType());
- } else if (int LogicalIdx =
- Idx -
- (MD && MD->isImplicitObjectMemberFunction() ? 1 : 0);
+ } else if (int LogicalIdx = Idx - Offset;
LogicalIdx >= 0 &&
(unsigned)LogicalIdx < FD->getNumParams()) {
CollectedTypes.push_back(
diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp
index 00571fa34d87a..05f3f6144b8a8 100644
--- a/clang/test/Sema/LifetimeSafety/capture-by.cpp
+++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp
@@ -13,14 +13,14 @@ void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ captureInt(1,x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
+( void)x; // cfg-note {{later used here}}
}
void local_int_capture() {
@@ -45,15 +45,16 @@ namespace capture_string {
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 noCaptureString(std::string s [[clang::lifetime_capture_by(x)]], X &x);
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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ captureString(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
(void)x; // cfg-note {{later used here}}
}
@@ -61,18 +62,24 @@ 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 at -1 {{result of call to 'move<std::basic_string<char> &>' aliases the storage of local variable 'local_string2'}}
+ captureRValString(std::move(local_string2), x); // cfg-warning {{local variable 'local_string2' does not live long enough}} \
+ // cfg-note {{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}}
}
+
+void safe_string_captures() {
+ noCaptureString(std::string(), x);
+ std::string local;
+ noCaptureString(local, x);
+}
} // namespace capture_string
// ****************************************************************************
// Capture std::string_view (gsl pointer types)
// ****************************************************************************
namespace capture_string_view {
-struct X {} x; // cfg-note 2 {{this global dangles}}
+struct X {} x;
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);
@@ -83,76 +90,74 @@ const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetime
const std::string& getLifetimeBoundString(std::string_view sv [[clang::lifetimebound]]);
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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -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 at -1 {{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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
}
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 at -1 {{result of call to 'getLifetimeBoundView' aliases the storage of local variable 'local_string'}}
+ captureStringView(getLifetimeBoundView(local_string), x); // cfg-warning {{local variable 'local_string' does not live long enough}} \
+ // cfg-note {{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}}
}
// 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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -1 {{destroyed here}}
+ std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
- // cfg-note at -3 {{result of call to 'getLifetimeBoundString' aliases the storage of temporary object}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{result of call to 'getLifetimeBoundView' aliases the storage of temporary object}} \
+ // cfg-note {{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 at -1 {{destroyed here}}
- }
+ captureRValStringView(getLifetimeBoundView(local_string), x); // cfg-warning {{local variable 'local_string' does not live long enough}} \
+ // cfg-note {{result of call to 'getLifetimeBoundView' aliases the storage of local variable 'local_string'}}
+ } // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
- // cfg-note at -3 {{result of call to 'getLifetimeBoundView' aliases the storage of temporary object}}
- // cfg-note at -4 {{result of call to 'getLifetimeBoundString' aliases the storage of temporary object}}
+ 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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{result of call to 'getLifetimeBoundView' aliases the storage of temporary object}} \
+ // cfg-note {{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); // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -1 {{destroyed here}}
- (void)x; // cfg-note {{later used here}}
+ std::string())), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
+ (void)x; // cfg-note {{later used here}}
}
void safe_captures() {
std::string_view local_string_view;
captureStringView(local_string_view, x);
+ captureRValStringView(std::move(local_string_view), x);
+ captureRValStringView(std::string_view{"abcd"}, x);
captureStringView(getNotLifetimeBoundView(std::string()), x);
+ captureRValStringView(getNotLifetimeBoundView(std::string()), x);
noCaptureStringView(local_string_view, x);
noCaptureStringView(std::string(), x);
noCaptureStringView(getLifetimeBoundView(std::string()), x);
@@ -170,17 +175,17 @@ struct X {} x; // cfg-note {{this global dangles}}
void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X &x);
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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
- // cfg-note at -3 {{result of call to 'getLifetimeBoundPointer' aliases the storage of temporary object}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{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); // cfg-warning at -1 {{stack memory associated with temporary object escapes to the global variable 'x' which will dangle}}
+ std::string())), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{stack memory associated with temporary object escapes to the global variable 'x' which will dangle}}
}
void safe_capture() {
@@ -200,13 +205,13 @@ void captureInitList(std::initializer_list<int> abc [[clang::lifetime_capture_by
std::initializer_list<int> getLifetimeBoundInitList(std::initializer_list<int> abc [[clang::lifetimebound]]);
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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
(void)x; // cfg-note {{later used here}}
}
@@ -225,8 +230,8 @@ void local_array_capture() {
// 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}}
+ 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
@@ -302,27 +307,27 @@ const std::string& getLifetimeBoundString(const std::string &s [[clang::lifetime
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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ s.captureInt(1); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ s.captureView(std::string()); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
- // cfg-note at -3 {{esult of call to 'getLifetimeBoundView' aliases the storage of temporary object}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
- // cfg-note at -3 {{result of call to 'getLifetimeBoundString' aliases the storage of temporary object}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{result of call to 'getLifetimeBoundString' aliases the storage of temporary object}}
(void)s; // cfg-note {{later used here}}
s.captureView(getNotLifetimeBoundView(std::string()));
}
@@ -376,18 +381,17 @@ 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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{destroyed here}}
- (void)set_of_sv; // cfg-note {{later used here}}
+ set_of_sv.insert(std::string_view());
+ (void)set_of_sv;
}
} // namespace containers_no_distinction
@@ -417,29 +421,30 @@ void use_container() {
vector_of_string.push_back(std::string()); // Ok.
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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{result of call to 'getLifetimeBoundView' aliases the storage of temporary object}}
(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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{destroyed here}}
+ vector_of_pointer.push_back(getLifetimeBoundPointer(local)); // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{destroyed here}}
+ vector_of_pointer.push_back(getNotLifetimeBoundPointer(std::string())); // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
(void)vector_of_pointer; // cfg-note {{later used here}}
}
@@ -471,36 +476,34 @@ 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()); // cfg-warning {{temporary object does not live long enough}}
- // cfg-note at -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 at -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 at -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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ vector_of_my_view.push_back(getMySV());
+ (void)vector_of_my_view;
+ vector_of_my_view.push_back(MyStringView{});
+ (void)vector_of_my_view;
+ vector_of_my_view.push_back(std::string_view{});
+ (void)vector_of_my_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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{result of call to 'getLifetimeBoundView' aliases the storage of temporary object}}
(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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{esult of call to 'getLifetimeBoundView' aliases the storage of temporary object}} \
+ // cfg-note {{result of call to 'getLifetimeBoundString' aliases the storage of temporary object}}
(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 at -1 {{destroyed here}}
- (void)vector_of_my_view; // cfg-note {{later used here}}
+ vector_of_my_view.push_back(getNotLifetimeBoundView(getLifetimeBoundString(getLifetimeBoundView(std::string{}))));
+ (void)vector_of_my_view;
// Use with container of other view types.
MyVector<std::string_view> vector_of_view;
- vector_of_view.push_back(getMySV()); // cfg-warning {{temporary object does not live long enough}}
- // cfg-note at -1 {{destroyed here}}
- (void)vector_of_view; // cfg-note {{later used here}}
+ vector_of_view.push_back(getMySV());
+ (void)vector_of_view;
vector_of_view.push_back(getMySVNotP());
(void)vector_of_view;
}
@@ -513,18 +516,15 @@ 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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -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 at -1 {{destroyed here}}
- // cfg-note at -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 at -1 {{destroyed here}}
- // cfg-note at -2 {{result of call to 'value' aliases the storage of temporary object}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{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(getOptionalSV().value());
+ (void)vector_of_view;
+ vector_of_view.push_back(getOptionalMySV().value());
+ (void)vector_of_view;
vector_of_view.push_back(getOptionalMySVNotP().value());
}
} // namespace conatiners_with_different
@@ -542,30 +542,28 @@ 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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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); // cfg-warning {{temporary object does not live long enough}}
- // cfg-note at -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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ capture2(std::string_view(), x2);
+ (void)x2;
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
(void)x2; // cfg-note {{later used here}}
std::vector<std::string_view> x3;
- capture3(std::string_view(), x3); // cfg-warning {{temporary object does not live long enough}}
- // cfg-note at -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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ capture3(std::string_view(), x3);
+ (void)x3;
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}}
(void)x3; // cfg-note {{later used here}}
}
} // namespace temporary_views
@@ -581,40 +579,41 @@ std::string_view getLifetimeBoundView(const std::string& s [[clang::lifetimeboun
std::string_view getNotLifetimeBoundView(const std::string& s);
void use() {
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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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 {{temporary object does not live long enough}} \
+ // cfg-note {{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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ std::string()); // expected-warning {{object whose reference is captured by 'views' will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed 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 {{temporary object does not live long enough}} \
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{result of call to 'getLifetimeBoundView' aliases the storage of temporary object}}
(void)views; // cfg-note {{later used here}}
- views.push_back(getNotLifetimeBoundView(std::string())); // cfg-warning {{temporary object does not live long enough}}
- // cfg-note at -1 {{destroyed here}}
- (void)views; // cfg-note {{later used here}}
+ views.push_back(getNotLifetimeBoundView(std::string()));
+ (void)views;
{
std::string local1, local2;
- views.push_back(local1); // cfg-warning {{temporary object does not live long enough}}
- // cfg-note at -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 at -1 {{destroyed here}}
- }
- (void)views; // cfg-note {{later used here}}
+ views.push_back(local1); // cfg-warning {{local variable 'local1' does not live long enough}}
+ (void)views;
+ views.insert(views.end(), local2); // cfg-warning {{local variable 'local2' does not live long enough}}
+ } // cfg-note 2 {{destroyed here}}
+ (void)views; // cfg-note 2 {{later used here}}
std::vector<std::string> strings;
strings.push_back(std::string());
+ (void)views;
strings.insert(strings.begin(), std::string());
std::vector<const std::string*> pointers;
pointers.push_back(getLifetimeBoundPointer(std::string()));
+ (void)views;
std::string local;
pointers.push_back(&local);
+ (void)views;
}
namespace with_span {
@@ -626,15 +625,14 @@ 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}}
- // cfg-warning at -1 {{temporary object does not live long enough}}
- // cfg-note at -2 {{destroyed here}}
+ 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}} \
+ // cfg-warning {{temporary object does not live long enough}} \
+ // cfg-note {{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 at -1 {{destroyed here}}
- }
+ spans.push_back(local); // cfg-warning {{local variable 'local' does not live long enough}}
+ } // cfg-note {{destroyed here}}
(void)spans; // cfg-note {{later used here}}
}
} // namespace with_span
>From 8cdc4297fb2c0b6801b5e4820ab584e7f93f65ab Mon Sep 17 00:00:00 2001
From: Kashika Akhouri <akhourik at google.com>
Date: Mon, 10 Aug 2026 08:38:10 +0000
Subject: [PATCH 3/3] Add support for all pointer types
---
.../LifetimeSafety/FactsGenerator.cpp | 12 ++++----
clang/test/Sema/LifetimeSafety/capture-by.cpp | 28 +++++++++----------
2 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 6a119a235b797..c5f990c46a37a 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -1066,13 +1066,13 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
OriginList *CapturedOriginList = getOriginsList(*Args[I]);
if (!CapturedOriginList)
continue;
+ // For references to pointer-like types, peel the outer origin (the pointer
+ // object itself) so that we capture the underlying data (the inner origin).
if (QualType ParamType = PVD->getType();
- ParamType->isReferenceType() &&
- isGslPointerType(ParamType->getPointeeType())) {
- if (CapturedOriginList->getLength() > 1) {
- CapturedOriginList = CapturedOriginList->peelOuterOrigin();
- }
- }
+ (ParamType->isReferenceType() &&
+ isPointerLikeType(ParamType->getPointeeType())) &&
+ CapturedOriginList->getLength() > 1)
+ CapturedOriginList = CapturedOriginList->peelOuterOrigin();
for (int CapturingArgIdx : Attr->params()) {
// FIXME: Add support for capturing to Global/unknown.
if (CapturingArgIdx == LifetimeCaptureByAttr::Global ||
diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp
index 57b1ffbcbc6e6..e9e745b52ec0b 100644
--- a/clang/test/Sema/LifetimeSafety/capture-by.cpp
+++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp
@@ -63,9 +63,9 @@ 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 {{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}}
+ // cfg-note {{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}}
}
void safe_string_captures() {
@@ -165,14 +165,15 @@ void safe_captures() {
} // namespace capture_string_view
namespace multiple_capture_by_attrs {
-struct X {} x1;
+struct X {} x1; // cfg-note {{this global dangles}}
void capture(std::string_view s [[clang::lifetime_capture_by(x1),
clang::lifetime_capture_by_global]],
X &x1);
void use() {
- capture(std::string(), // expected-warning {{object whose reference is captured by 'x1' will be destroyed at the end of the full-expression}} expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}}
- x1);
+ capture(std::string(), x1); // expected-warning {{object whose reference is captured by 'x1' will be destroyed at the end of the full-expression}} \
+ // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{stack memory associated with temporary object escapes to the global variable 'x1' which will dangle}}
}
} // namespace multiple_capture_by_attrs
@@ -446,18 +447,17 @@ void use_container() {
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 {{temporary object does not live long enough}} \
- // cfg-note {{destroyed here}}
+ // cfg-note {{destroyed here}} \
+ // cfg-note {{result of call to 'getLifetimeBoundPointer' aliases the storage of temporary object}}
(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}} \
// cfg-warning {{temporary object does not live long enough}} \
- // cfg-note {{destroyed here}}
+ // cfg-note {{destroyed here}} \
+ // cfg-note 2 {{result of call to 'getLifetimeBoundPointer' aliases the storage of temporary object}}
(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 {{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 {{destroyed here}}
- (void)vector_of_pointer; // cfg-note {{later used here}}
+ vector_of_pointer.push_back(getLifetimeBoundPointer(local));
+ vector_of_pointer.push_back(getNotLifetimeBoundPointer(std::string()));
+ (void)vector_of_pointer;
}
// ****************************************************************************
More information about the cfe-commits
mailing list