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