[clang] [llvm] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Mon Nov 18 00:41:58 PST 2024


================
@@ -0,0 +1,233 @@
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field -Wreturn-stack-address -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x = &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///////////////////////////
+// Detect dangling cases.
+///////////////////////////
+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);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+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);
+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);
+
+// Return reference to the argument through lifetimebound.
+const std::string& getLB(const std::string &s [[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv [[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string &s [[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string &s);
+
+void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X &x);
+
+struct ThisIsCaptured {
+  void capture(X &x) [[clang::lifetime_capture_by(x)]];
+};
+
+void captureByGlobal(std::string_view s [[clang::lifetime_capture_by(global)]]);
+void captureByUnknown(std::string_view s [[clang::lifetime_capture_by(unknown)]]);
+
+void use() {
+  std::string_view local_string_view;
+  std::string local_string;
+  X x;
+  // Capture an 'int'.
+  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'}}
+  captureInt(local, x);
+  noCaptureInt(1, x);
+  noCaptureInt(local, x);
+
+  // Capture using std::string_view.
+  captureStringView(local_string_view, x);
+  captureStringView(std::string(), // expected-warning {{object whose reference is captured by 'x'}}
+            x);
+  captureStringView(substr(
+      std::string() // expected-warning {{object whose reference is captured by 'x'}}
+      ), x);
+  captureStringView(substr(local_string), x);
+  captureStringView(strcopy(std::string()), x);
+  captureRValStringView(std::move(local_string_view), x);
+  captureRValStringView(std::string(), x); // expected-warning {{object whose reference is captured by 'x'}}
+  captureRValStringView(std::string_view{"abcd"}, x);
+  captureRValStringView(substr(local_string), x);
+  captureRValStringView(substr(std::string()), x); // expected-warning {{object whose reference is captured by 'x'}}
+  captureRValStringView(strcopy(std::string()), x);
+  noCaptureStringView(local_string_view, x);
+  noCaptureStringView(std::string(), x);
+  noCaptureStringView(substr(std::string()), x);
+
+  // Capture using std::string.
+  captureString(std::string(), x); // expected-warning {{object whose reference is captured by 'x'}}
+  captureString(local_string, x);
+  captureRValString(std::move(local_string), x);
+  captureRValString(std::string(), x); // expected-warning {{object whose reference is captured by 'x'}}
+
+  // Capture with lifetimebound.
+  captureStringView(getLB(std::string()), x); // expected-warning {{object whose reference is captured by 'x'}}
+  captureStringView(getLB(substr(std::string())), x); // expected-warning {{object whose reference is captured by 'x'}}
+  captureStringView(getLB(getLB(
+    std::string()  // expected-warning {{object whose reference is captured by 'x'}}
+    )), x);
+  capturePointer(getPointerLB(std::string()), x); // expected-warning {{object whose reference is captured by 'x'}}
+  capturePointer(getPointerLB(*getPointerLB(
+    std::string()  // expected-warning {{object whose reference is captured by 'x'}}
+    )), x);
+  capturePointer(getPointerNoLB(std::string()), x);
+
+  // Member functions.
+  x.captureInt(1); // expected-warning {{object whose reference is captured by 'x'}}
+  x.captureSV(std::string()); // expected-warning {{object whose reference is captured by 'x'}}
+  x.captureSV(substr(std::string())); // expected-warning {{object whose reference is captured by 'x'}}
+  x.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(x); // expected-warning {{object whose reference is captured by 'x'}}
+  ThisIsCaptured TIS;
+  TIS.capture(x);
+
+  // capture by global.
+  captureByGlobal(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}}
+  captureByGlobal(substr(std::string())); // expected-warning {{captured}}
+  captureByGlobal(local_string);
+  captureByGlobal(local_string_view);
+
+  // // capture by unknown.
+  captureByGlobal(std::string()); // expected-warning {{object whose reference is captured will be destroyed at the end of the full-expression}}
+  captureByGlobal(substr(std::string())); // expected-warning {{captured}}
+  captureByGlobal(local_string);
+  captureByGlobal(local_string_view);
+}
+
+void captureDefaultArg(X &x, std::string_view s [[clang::lifetime_capture_by(x)]] = std::string());
+void useCaptureDefaultArg() {
+  X x;
+  captureDefaultArg(x); // FIXME: Diagnose temporary default arg.
+  captureDefaultArg(x, std::string("temp")); // expected-warning {{captured}}
+  std::string local;
+  captureDefaultArg(x, local);
+}
+
+template<typename T> struct IsPointerLikeTypeImpl : std::false_type {};
+template<> struct IsPointerLikeTypeImpl<std::string_view> : std::true_type {};
+template<typename T> concept IsPointerLikeType = std::is_pointer<T>::value || IsPointerLikeTypeImpl<T>::value;
+
+// Templated containers having no distinction between pointer-like and other element type.
+template<class T>
+struct MySet {
+  void insert(T&& t [[clang::lifetime_capture_by(this)]]);
+  void insert(const T& t [[clang::lifetime_capture_by(this)]]);
+};
+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}}
+  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}}
+}
+
+// Templated containers having **which distinguishes** between pointer-like and other element type.
+template<class T>
+struct MyVector {
+  void push_back(T&& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>;
+  void push_back(const T& t [[clang::lifetime_capture_by(this)]]) requires IsPointerLikeType<T>;
+
+  void push_back(T&& t) requires (!IsPointerLikeType<T>);
+  void push_back(const T& t) requires (!IsPointerLikeType<T>);
+};
+
+// Container of pointers.
+struct [[gsl::Pointer()]] MyStringView : public std::string_view {
+  MyStringView();
+  MyStringView(std::string_view&&);
+  MyStringView(const MyStringView&);
+  MyStringView(const std::string&);
+};
+template<> struct IsPointerLikeTypeImpl<MyStringView> : std::true_type {};
+
+std::optional<std::string_view> getOptionalSV();
+std::optional<std::string> getOptionalS();
+std::optional<MyStringView> getOptionalMySV();
+MyStringView getMySV();
+
+class MyStringViewNotPointer : public std::string_view {};
+std::optional<MyStringViewNotPointer> getOptionalMySVNotP();
+MyStringViewNotPointer getMySVNotP();
+
+void container_of_pointers() {
+  std::string local;
+  MyVector<std::string> vector_of_string;
+  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'}}
+  vector_of_view.push_back(substr(std::string())); // expected-warning {{captured}}
+  
+  MyVector<const std::string*> vector_of_pointer;
+  vector_of_pointer.push_back(getPointerLB(std::string())); // expected-warning {{captured}}
+  vector_of_pointer.push_back(getPointerLB(*getPointerLB(std::string()))); // expected-warning {{captured}}
+  vector_of_pointer.push_back(getPointerLB(local));
+  vector_of_pointer.push_back(getPointerNoLB(std::string()));
+  
+  // User-defined [[gsl::Pointer]]
+  vector_of_view.push_back(getMySV());
+  vector_of_view.push_back(getMySVNotP());
+
+  // Vector of user defined gsl::Pointer.
+  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'}}
+  vector_of_my_view.push_back(substr(std::string{})); // expected-warning {{captured}}
+  vector_of_my_view.push_back(getLB(substr(std::string{}))); // expected-warning {{captured}}
+  vector_of_my_view.push_back(strcopy(getLB(substr(std::string{}))));
+
+  // With std::optional container.
+  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 {{captured}}
+  
+  // FIXME: Following 2 cases are false positives:
+  vector_of_view.push_back(getOptionalSV().value()); // expected-warning {{captured}}
+  vector_of_view.push_back(getOptionalMySV().value());  // expected-warning {{captured}}
+
+  // (maybe) FIXME: We may choose to diagnose the following case.
+  // This happens because 'MyStringViewNotPointer' is not marked as a [[gsl::Pointer]] but is derived from one.
+  vector_of_view.push_back(getOptionalMySVNotP().value()); // expected-warning {{captured}}
+}
+
+namespace temporary_views {
+void capture1(std::string_view s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view>& x);
+
+// Intended to capture the "string_view" itself
+void capture2(const std::string_view& s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view*>& x);
+// Intended to capture the pointee of the "string_view"
+void capture3(const std::string_view& s [[clang::lifetime_capture_by(x)]], std::vector<std::string_view>& x);
+
+void test1() {
+  std::vector<std::string_view> x1;
+  capture1(std::string(), x1); // expected-warning {{captured by 'x1'}}
+  capture1(std::string_view(), x1);
+
+  std::vector<std::string_view*> x2;
+  capture2(std::string_view(), x2); // FIXME: Warn when the temporary view itself is captured.
----------------
hokein wrote:

I'm not certain we can resolve this FIXME. It seems more like a design decision -- I don't see a way to make the current implementation support both the capture2 and capture3 cases.

https://github.com/llvm/llvm-project/pull/115921


More information about the cfe-commits mailing list