llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Kashika Akhouri (kashika0112)

<details>
<summary>Changes</summary>

This PR implements support for `[[clang::lifetime_capture_by(X)]]` to enable 
tracking lifetimes for plain structs and containers like `std::vector` without 
requiring manual `[[gsl::Pointer]]` or `[[gsl::Owner]]` annotations. The 
implementation extends the `LifetimeAnnotatedOriginTypeCollector` to register 
types in capture_by contracts for origin tracking. 

This PR also enables the intra-procedural analysis in existing tests using 
-Wlifetime-safety and updated expectations to handle the more detailed 
flow-sensitive diagnostics.

```cpp
struct MyContainer {
  const char* stored_ptr;
};

void captureInto(std::string_view v [[clang::lifetime_capture_by(c)]], 
MyContainer&amp; c);

void test_parameter_capture() {
  MyContainer container;
  {
    std::string local = "temporary data";
    captureInto(local, container); 
  }
  (void)container;
}
```

Warnings Generated:
```cpp
b10.cpp:14:17: warning: local variable 'local' does not live long enough 
[-Wlifetime-safety-use-after-scope]
   14 |     captureInto(local, container); 
      |                 ^~~~~
b10.cpp:15:3: note: destroyed here
   15 |   }
      |   ^
b10.cpp:16:9: note: later used here
   16 |   (void)container;
      |         ^~~~~~~~~
```

---

Patch is 39.96 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/204361.diff


3 Files Affected:

- (modified) clang/lib/Analysis/LifetimeSafety/Origins.cpp (+21-2) 
- (modified) clang/test/Sema/LifetimeSafety/capture-by.cpp (+288-74) 
- (modified) clang/test/Sema/LifetimeSafety/safety.cpp (+3-4) 


``````````diff
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 55d3b36e3163a..7aa188d22b781 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -81,9 +81,9 @@ class LifetimeAnnotatedOriginTypeCollector
     if (!FD)
       return;
     FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+    const auto *MD = dyn_cast<CXXMethodDecl>(FD);
 
-    if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
-        MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
+    if (MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
         implicitObjectParamIsLifetimeBound(MD)) {
       CollectedTypes.push_back(RetType);
       return;
@@ -94,6 +94,25 @@ class LifetimeAnnotatedOriginTypeCollector
         CollectedTypes.push_back(RetType);
         return;
       }
+      if (auto *Attr = Param->getAttr<LifetimeCaptureByAttr>()) {
+        for (int Idx : Attr->params()) {
+          if (Idx == LifetimeCaptureByAttr::Global ||
+              Idx == LifetimeCaptureByAttr::Unknown ||
+              Idx == LifetimeCaptureByAttr::Invalid)
+            continue;
+          if (Idx == LifetimeCaptureByAttr::This) {
+            if (MD && MD->isInstance())
+              CollectedTypes.push_back(MD->getFunctionObjectParameterType());
+          } else if (int LogicalIdx =
+                         Idx -
+                         (MD && MD->isImplicitObjectMemberFunction() ? 1 : 0);
+                     LogicalIdx >= 0 &&
+                     (unsigned)LogicalIdx < FD->getNumParams()) {
+            CollectedTypes.push_back(
+                FD->getParamDecl(LogicalIdx)->getType().getNonReferenceType());
+          }
+        }
+      }
     }
   }
 };
diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp 
b/clang/test/Sema/LifetimeSafety/capture-by.cpp
index 2877d8d6cd5f9..00571fa34d87a 100644
--- a/clang/test/Sema/LifetimeSafety/capture-by.cpp
+++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 --std=c++20 -fsyntax-only -verify -Wdangling-capture %s
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wno-dangling -verify=cfg 
-Wlifetime-safety %s
 
 #include "Inputs/lifetime-analysis.h"
 
@@ -11,13 +12,28 @@ void captureInt(const int &i 
[[clang::lifetime_capture_by(x)]], X &x);
 void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
 void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
 
-void use() {
-  int local;
-  captureInt(1, // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
-            x);
+void temporary_int_capture() {
+  captureInt(1,x); // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
+                   // cfg-warning@-1 {{temporary object does not live long 
enough}}
+                   // cfg-note@-2 {{destroyed here}}
+  (void)x;         // cfg-note {{later used here}} 
   captureRValInt(1, x); // expected-warning {{object whose reference is 
captured by 'x' will be destroyed at the end of the full-expression}}
-  captureInt(local, x);
+                      // cfg-warning@-1 {{temporary object does not live long 
enough}}
+                      // cfg-note@-2 {{destroyed here}}
+( void)x;              // cfg-note {{later used here}} 
+}
+
+void local_int_capture() {
+  {
+    int local;
+    captureInt(local, x); // cfg-warning {{local variable 'local' does not 
live long enough}}
+  }                       // cfg-note {{destroyed here}}
+  (void)x;                // cfg-note {{later used here}} 
+}
+
+void safe_int_captures() {
   noCaptureInt(1, x);
+  int local;
   noCaptureInt(local, x);
 }
 } // namespace capture_int
@@ -30,12 +46,25 @@ struct X {} x;
 void captureString(const std::string &s [[clang::lifetime_capture_by(x)]], X 
&x);
 void captureRValString(std::string &&s [[clang::lifetime_capture_by(x)]], X 
&x);
 
-void use() {
-  std::string local_string;
+void temporary_string_capture() {
   captureString(std::string(), x); // expected-warning {{object whose 
reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
-  captureString(local_string, x);
-  captureRValString(std::move(local_string), x);
+                                   // cfg-warning@-1 {{temporary object does 
not live long enough}}
+                                   // cfg-note@-2 {{destroyed here}}
+  (void)x;                         // cfg-note {{later used here}}
   captureRValString(std::string(), x); // expected-warning {{object whose 
reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
+                                       // cfg-warning@-1 {{temporary object 
does not live long enough}}
+                                       // cfg-note@-2 {{destroyed here}}
+  (void)x;                             // cfg-note {{later used here}}   
+}
+
+void local_string_capture() {
+  {
+    std::string local_string1, local_string2;
+    captureString(local_string1, x);                // cfg-warning {{local 
variable 'local_string1' does not live long enough}}
+    captureRValString(std::move(local_string2), x); // cfg-warning {{local 
variable 'local_string2' does not live long enough}}
+                                    // cfg-note@-1 {{result of call to 
'move<std::basic_string<char> &>' aliases the storage of local variable 
'local_string2'}}
+  }                                 // cfg-note 2 {{destroyed here}}           
                      
+  (void)x;                          // cfg-note 2 {{later used here}}          
               
 }
 } // namespace capture_string
 
@@ -43,7 +72,7 @@ void use() {
 // Capture std::string_view (gsl pointer types)
 // ****************************************************************************
 namespace capture_string_view {
-struct X {} x;
+struct X {} x;   // cfg-note 2 {{this global dangles}}
 void captureStringView(std::string_view s [[clang::lifetime_capture_by(x)]], X 
&x);
 void captureRValStringView(std::string_view &&sv 
[[clang::lifetime_capture_by(x)]], X &x);
 void noCaptureStringView(std::string_view sv, X &x);
@@ -53,35 +82,80 @@ std::string_view getNotLifetimeBoundView(const std::string& 
s);
 const std::string& getLifetimeBoundString(const std::string &s 
[[clang::lifetimebound]]);
 const std::string& getLifetimeBoundString(std::string_view sv 
[[clang::lifetimebound]]);
 
-void use() {
-  std::string_view local_string_view;
-  std::string local_string;
-  captureStringView(local_string_view, x);
-  captureStringView(std::string(), // expected-warning {{object whose 
reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
-            x);
-
-  captureStringView(getLifetimeBoundView(local_string), x);
-  captureStringView(getNotLifetimeBoundView(std::string()), x);
-  captureRValStringView(std::move(local_string_view), x);
+void temporary_string_capture() {
+  captureStringView(std::string(), x); // expected-warning {{object whose 
reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
+                                       // cfg-warning@-1 {{temporary object 
does not live long enough}}
+                                       // cfg-note@-2 {{destroyed here}}
+  (void)x;                             // cfg-note {{later used here}}       
   captureRValStringView(std::string(), x); // expected-warning {{object whose 
reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
-  captureRValStringView(std::string_view{"abcd"}, x);
+                                           // cfg-warning@-1 {{temporary 
object does not live long enough}}
+                                           // cfg-note@-2 {{destroyed here}}
+  (void)x;                                 // cfg-note {{later used here}}
+  captureRValStringView(std::string_view{"abcd"}, x); // cfg-warning 
{{temporary object does not live long enough}}
+                                                      // cfg-note@-1 
{{destroyed here}}
+  (void)x;                                            // cfg-note {{later used 
here}}                
+}
 
-  noCaptureStringView(local_string_view, x);
-  noCaptureStringView(std::string(), x);
+void local_string_capture() {
+  {
+    std::string local_string;
+    captureStringView(getLifetimeBoundView(local_string), x);  // cfg-warning 
{{local variable 'local_string' does not live long enough}}
+                                                               // cfg-note@-1 
{{result of call to 'getLifetimeBoundView' aliases the storage of local 
variable 'local_string'}}
+  }                                                            // cfg-note 
{{destroyed here}}
+  (void)x;      // cfg-note {{later used here}}
+  std::string_view local_string_view;
+  captureRValStringView(std::move(local_string_view), x); // cfg-warning 
{{stack memory associated with local variable 'local_string_view' escapes to 
the global variable 'x' which will dangle}}
+}
 
-  // With lifetimebound functions.
-  captureStringView(getLifetimeBoundView(
-  std::string() // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
-  ), x);
-  captureRValStringView(getLifetimeBoundView(local_string), x);
+// Lifetimebound captures
+void temporary_string_view_lifetimebound_capture() {
+  captureStringView(getLifetimeBoundView( // cfg-note {{result of call to 
'getLifetimeBoundView' aliases the storage of temporary object}}
+  std::string()                           // expected-warning {{object whose 
reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
+  ), x);                                  // cfg-warning@-1 {{temporary object 
does not live long enough}}
+                                          // cfg-note@-1 {{destroyed here}}
+  (void)x;                                // cfg-note {{later used here}}
+  captureStringView(getLifetimeBoundString(std::string()), x);   // 
expected-warning {{object whose reference is captured by 'x' will be destroyed 
at the end of the full-expression}}
+                                                                 // 
cfg-warning@-1 {{temporary object does not live long enough}}
+                                                                 // 
cfg-note@-2 {{destroyed here}}
+                                                                 // 
cfg-note@-3 {{result of call to 'getLifetimeBoundString' aliases the storage of 
temporary object}}
+  (void)x;                                                       // cfg-note 
{{later used here}}
   captureRValStringView(getLifetimeBoundView(std::string()), x); // 
expected-warning {{object whose reference is captured by 'x' will be destroyed 
at the end of the full-expression}}
-  captureRValStringView(getNotLifetimeBoundView(std::string()), x);
-  noCaptureStringView(getLifetimeBoundView(std::string()), x);
-  captureStringView(getLifetimeBoundString(std::string()), x); // 
expected-warning {{object whose reference is captured by 'x' will be destroyed 
at the end of the full-expression}}
+                                                                 // 
cfg-warning@-1 {{temporary object does not live long enough}}
+                                                                 // 
cfg-note@-2 {{destroyed here}}
+  (void)x;                                                       // cfg-note 
{{later used here}}                                                             
  
+  captureRValStringView(getNotLifetimeBoundView(std::string()), x);  // 
cfg-warning {{stack memory associated with temporary object escapes to the 
global variable 'x' which will dangle}}
+}
+
+void local_string_lifetimebound_capture() {
+ {
+    std::string local_string;
+    captureRValStringView(getLifetimeBoundView(local_string), x); // 
cfg-warning {{temporary object does not live long enough}}
+                                                                  // 
cfg-note@-1 {{destroyed here}}
+ }
+ (void)x;                                                         // cfg-note 
{{later used here}}
+}
+
+void temporary_nested_lifetimebound_capture() {
   
captureStringView(getLifetimeBoundString(getLifetimeBoundView(std::string())), 
x); // expected-warning {{object whose reference is captured by 'x' will be 
destroyed at the end of the full-expression}}
-  captureStringView(getLifetimeBoundString(getLifetimeBoundString(
+                                                                               
      // cfg-warning@-1 {{temporary object does not live long enough}}
+                                                                               
      // cfg-note@-2 {{destroyed here}}
+                                                                               
      // cfg-note@-3 {{result of call to 'getLifetimeBoundView' aliases the 
storage of temporary object}}
+                                                                               
      // cfg-note@-4 {{result of call to 'getLifetimeBoundString' aliases the 
storage of temporary object}}
+  (void)x;                                                                     
      // cfg-note {{later used here}}
+  captureStringView(getLifetimeBoundString(getLifetimeBoundString(             
      // cfg-note 2 {{result of call to 'getLifetimeBoundString' aliases the 
storage of temporary object}}
     std::string()  // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
-    )), x);
+    )), x);        // cfg-warning@-1 {{temporary object does not live long 
enough}}
+                   // cfg-note@-1 {{destroyed here}}
+  (void)x;         // cfg-note {{later used here}}
+}
+
+void safe_captures() {
+  std::string_view local_string_view;
+  captureStringView(local_string_view, x);
+  captureStringView(getNotLifetimeBoundView(std::string()), x);
+  noCaptureStringView(local_string_view, x);
+  noCaptureStringView(std::string(), x);
+  noCaptureStringView(getLifetimeBoundView(std::string()), x);
 }
 } // namespace capture_string_view
 
@@ -92,15 +166,25 @@ const std::string* getLifetimeBoundPointer(const 
std::string &s [[clang::lifetim
 const std::string* getNotLifetimeBoundPointer(const std::string &s);
 
 namespace capture_pointer {
-struct X {} x;
+struct X {} x;   // cfg-note {{this global dangles}}
 void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X 
&x);
-void use() {
+
+void temporary_pointer_lifetimebound_capture() {
   capturePointer(getLifetimeBoundPointer(std::string()), x); // 
expected-warning {{object whose reference is captured by 'x' will be destroyed 
at the end of the full-expression}}
+                                                             // cfg-warning@-1 
{{temporary object does not live long enough}}
+                                                             // cfg-note@-2 
{{destroyed here}}
+                                                             // cfg-note@-3 
{{result of call to 'getLifetimeBoundPointer' aliases the storage of temporary 
object}}
+  (void)x;                                                   // cfg-note 
{{later used here}}
+}
+
+void temporary_nested_lifetimebound_capture() {
   capturePointer(getLifetimeBoundPointer(*getLifetimeBoundPointer(
     std::string()  // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
-    )), x);
-  capturePointer(getNotLifetimeBoundPointer(std::string()), x);
+    )), x);        // cfg-warning@-1 {{stack memory associated with temporary 
object escapes to the global variable 'x' which will dangle}}
+}
 
+void safe_capture() {
+  capturePointer(getNotLifetimeBoundPointer(std::string()), x);
 }
 } // namespace capture_pointer
 
@@ -108,23 +192,41 @@ void use() {
 // Arrays and initializer lists.
 // ****************************************************************************
 namespace init_lists {
-struct X {} x;
+struct X {} x;    // cfg-note {{this global dangles}}
 void captureVector(const std::vector<int> &a 
[[clang::lifetime_capture_by(x)]], X &x);
 void captureArray(int array [[clang::lifetime_capture_by(x)]] [2], X &x);
 void captureInitList(std::initializer_list<int> abc 
[[clang::lifetime_capture_by(x)]], X &x);
 
-
 std::initializer_list<int> getLifetimeBoundInitList(std::initializer_list<int> 
abc [[clang::lifetimebound]]);
 
-void use() {
+void temporary_vector_capture() {
   captureVector({1, 2, 3}, x); // expected-warning {{object whose reference is 
captured by 'x' will be destroyed at the end of the full-expression}}
+                               // cfg-warning@-1 {{temporary object does not 
live long enough}}
+                               // cfg-note@-2 {{destroyed here}}
+  (void)x;                     // cfg-note {{later used here}}       
   captureVector(std::vector<int>{}, x); // expected-warning {{object whose 
reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
-  std::vector<int> local_vector;
-  captureVector(local_vector, x);
+                                        // cfg-warning@-1 {{temporary object 
does not live long enough}}
+                                        // cfg-note@-2 {{destroyed here}}
+  (void)x;                              // cfg-note {{later used here}}        
+}
+
+void local_vector_capture() {
+  {
+    std::vector<int> local_vector;
+    captureVector(local_vector, x);    // cfg-warning {{local variable 
'local_vector' does not live long enough}}
+  }                                    // cfg-note {{destroyed here}}
+  (void)x;                             // cfg-note {{later used here}}
+}
+
+void local_array_capture() {
   int local_array[2]; 
-  captureArray(local_array, x);
-  captureInitList({1, 2}, x); // expected-warning {{object whose reference is 
captured by 'x' will be destroyed at the end of the full-expression}}
-  captureInitList(getLifetimeBoundInitList({1, 2}), x); // expected-warning 
{{object whose reference is captured by 'x' will be destroyed at the end of the 
full-expression}}
+  captureArray(local_array, x);      // cfg-warning {{stack memory associated 
with local variable 'local_array' escapes to the global variable 'x' which will 
dangle}}
+}
+
+// FIXME: Add support for initializer lists in -Wlifetime-safety
+void initializer_list_capture() {
+  captureInitList({1, 2}, x); // expected-warning {{object whose reference is 
captured by 'x' will be destroyed at the end of the full-expression}}         
+  captureInitList(getLifetimeBoundInitList({1, 2}), x); // expected-warning 
{{object whose reference is captured by 'x' will be destroyed at the end of the 
full-expression}}                                     
 }
 } // namespace init_lists
 
@@ -136,6 +238,8 @@ struct X {} x;
 struct S {
   void capture(X &x) [[clang::lifetime_capture_by(x)]];
 };
+
+// FIXME: Add support for capture of method declarations in -Wlifetime-safety
 void use() {
   S{}.capture(x); // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
   S s;
@@ -166,6 +270,7 @@ void captureByUnknown(std::string_view s 
[[clang::lifetime_capture_by(unknown)]]
 
 std::string_view getLifetimeBoundView(const std::string& s 
[[clang::lifetimebound]]);
 
+// FIXME: Add support for capture by global and unknown in -Wlifetime-safety
 void use() {  
   std::string_view local_string_view;
   std::string local_string;
@@ -195,12 +300,30 @@ std::string_view getLifetimeBoundView(const std::string& 
s [[clang::lifetimeboun
 std::string_view getNotLifetimeBoundView(const std::string& s);
 const std::string& getLifetimeBoundString(const std::string &s 
[[clang::lifetimebound]]);
 
-void use() {
+void temporary_capture_by_this() {
   S s;
   s.captureInt(1); // expected-warning {{object whose reference is captured by 
's' will be destroyed at the end of the full-expression}}
+                   // cfg-warning@-1 {{temporary object does not live long 
enough}}
+                   // cfg-note@-2 {{destroyed here}}
+  (void)s;         // cfg-note {{later used here}}       
   s.captureView(std::string()); // expected-warning {{object whose reference 
is captured by 's' will be destroyed at the end of the full-expression}}
-  s.captureView(getLifetimeBoundView(std::string())); // expected-warning 
{{object whose reference is captured by 's' will be destroyed at the end of the 
fu...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/204361
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to