Author: Kashika Akhouri Date: 2026-08-17T15:55:09+05:30 New Revision: a14f953e22f6eb2468645a478827be2ea1b63c8e
URL: https://github.com/llvm/llvm-project/commit/a14f953e22f6eb2468645a478827be2ea1b63c8e DIFF: https://github.com/llvm/llvm-project/commit/a14f953e22f6eb2468645a478827be2ea1b63c8e.diff LOG: Support [[clang::lifetime_capture_by(X)]] in Plain Containers (#204361) This PR implements support for `[[clang::lifetime_capture_by(X)]]` to enable tracking lifetimes for plain structs and containers like `std::vector` without requiring manual `[[gsl::Pointer]]` or `[[gsl::Owner]]` annotations. The implementation extends the `LifetimeAnnotatedOriginTypeCollector` to register types in capture_by contracts for origin tracking. This PR also enables the intra-procedural analysis in existing tests using -Wlifetime-safety and updated expectations to handle the more detailed flow-sensitive diagnostics. ```cpp struct MyContainer { const char* stored_ptr; }; void captureInto(std::string_view v [[clang::lifetime_capture_by(c)]], MyContainer& c); void test_parameter_capture() { MyContainer container; { std::string local = "temporary data"; captureInto(local, container); } (void)container; } ``` Warnings Generated: ```cpp b10.cpp:14:17: warning: local variable 'local' does not live long enough [-Wlifetime-safety-use-after-scope] 14 | captureInto(local, container); | ^~~~~ b10.cpp:15:3: note: destroyed here 15 | } | ^ b10.cpp:16:9: note: later used here 16 | (void)container; | ^~~~~~~~~ ``` Added: Modified: clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp clang/lib/Analysis/LifetimeSafety/Origins.cpp clang/test/Sema/LifetimeSafety/capture-by.cpp clang/test/Sema/LifetimeSafety/safety.cpp Removed: ################################################################################ diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index fa63ec936e50e..ce3039528b283 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -1033,26 +1033,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; + // 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() && + 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/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index c837f246fa17b..0c0c280d73cb6 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; } @@ -96,6 +99,34 @@ class LifetimeAnnotatedOriginTypeCollector } } } + + 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 || + Idx == LifetimeCaptureByAttr::Unknown || + Idx == LifetimeCaptureByAttr::Invalid) + continue; + if (Idx == LifetimeCaptureByAttr::This) { + if (IsInstance) + CollectedTypes.push_back(MD->getFunctionObjectParameterType()); + } else if (int LogicalIdx = Idx - Offset; + LogicalIdx >= 0 && + (unsigned)LogicalIdx < FD->getNumParams()) { + CollectedTypes.push_back( + FD->getParamDecl(LogicalIdx)->getType().getNonReferenceType()); + } + } + } + } + } }; } // namespace diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp index e81669fe30782..e9e745b52ec0b 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); - 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); +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 {{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 {{temporary object does not live long enough}} \ + // cfg-note {{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 @@ -29,13 +45,33 @@ 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 {{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 {{temporary object does not live long enough}} \ + // cfg-note {{destroyed here}} + (void)x; // cfg-note {{later used here}} +} -void use() { - std::string local_string; - 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); - captureRValString(std::string(), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} +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}} +} + +void safe_string_captures() { + noCaptureString(std::string(), x); + std::string local; + noCaptureString(local, x); } } // namespace capture_string @@ -53,47 +89,91 @@ 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() { +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 {{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 {{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 {{result of call to 'getLifetimeBoundView' aliases the storage of local variable 'local_string'}} + } // cfg-note {{destroyed here}} + (void)x; // cfg-note {{later used here}} +} + +// 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()), 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 {{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 {{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}} +} + +void local_string_lifetimebound_capture() { + { + std::string local_string; + 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 {{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())), 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; - 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); - 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); - + 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); - - // 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); - 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}} - 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( - std::string() // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} - )), x); } } // 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 @@ -104,15 +184,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() { - capturePointer(getLifetimeBoundPointer(std::string()), x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} + +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 {{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); - capturePointer(getNotLifetimeBoundPointer(std::string()), x); + 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() { + capturePointer(getNotLifetimeBoundPointer(std::string()), x); } } // namespace capture_pointer @@ -120,23 +210,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() { - captureVector({1, 2, 3}, x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}} - 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); +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 {{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 {{temporary object does not live long enough}} \ + // cfg-note {{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); + 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}} + 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 @@ -148,6 +256,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; @@ -178,6 +288,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; @@ -207,12 +318,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 {{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 {{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.captureInt(1); // expected-warning {{object whose reference is captured by 's' will be destroyed at the end of the full-expression}} - 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}} - s.captureView(getLifetimeBoundString(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 {{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 {{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())); } } // namespace capture_by_this @@ -265,10 +394,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}} + 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}} + 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()); + (void)set_of_sv; + } } // namespace containers_no_distinction @@ -297,14 +434,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}} - 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}} - + 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 {{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}} - 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(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 {{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 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)); vector_of_pointer.push_back(getNotLifetimeBoundPointer(std::string())); + (void)vector_of_pointer; } // **************************************************************************** @@ -336,17 +489,35 @@ void use_my_view() { std::string local; MyVector<MyStringView> vector_of_my_view; 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{}); - 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(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(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}} + (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 {{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 {{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{})))); + (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()); + (void)vector_of_view; vector_of_view.push_back(getMySVNotP()); + (void)vector_of_view; } // **************************************************************************** @@ -357,10 +528,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}} - + 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_ diff erent @@ -378,18 +554,29 @@ 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}} + 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); - capture2(std::string(), x2); // expected-warning {{object whose reference is captured by 'x2' will be destroyed at the end of the full-expression}} + (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); - capture3(std::string(), x3); // expected-warning {{object whose reference is captured by 'x3' will be destroyed at the end of the full-expression}} + (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 @@ -403,23 +590,42 @@ 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}} + 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}} - 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}} + 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 {{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())); - views.push_back(local); - views.insert(views.end(), local); + (void)views; + { + std::string local1, local2; + 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 { @@ -431,9 +637,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); + 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 {{local variable 'local' does not live long enough}} + } // cfg-note {{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 9d83db1eaa6f4..b49829775777d 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -4004,7 +4004,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]]); @@ -4014,9 +4013,9 @@ void member_capture_simple_container() { SimpleContainer c; { MyObj local; - c.set(local); - } - (void)c.stored; + c.set(local); // expected-warning {{local variable 'local' does not live long enough}} + } // expected-note {{destroyed here}} + (void)c.stored; // expected-note {{later used here}} } void captureTwo(View& into, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
