[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)
Gábor Horváth via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 14 03:44:34 PST 2024
================
@@ -793,3 +793,108 @@ void test13() {
}
} // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+ 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&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], S&s);
+struct ThisIsCaptured {
+ void capture(S& s) [[clang::lifetime_capture_by(s)]];
+ void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error {{'lifetime_capture_by' attribute argument 'abcd' is not a known function parameter}}
+ void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error {{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+ std::string_view local_sv;
+ std::string local_s;
+ S s;
+ // Capture an 'int'.
+ int local;
+ captureInt(1, // expected-warning {{object captured by 's' will be destroyed at the end of the full-expression}}
+ s);
+ captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+ captureInt(local, s);
+ noCaptureInt(1, s);
+ noCaptureInt(local, s);
+
+ // Capture lifetimebound pointer.
+ capturePointer(getPointerLB(std::string()), s); // expected-warning {{object captured by 's'}}
+ capturePointer(getPointerLB(*getPointerLB(std::string())), s); // expected-warning {{object captured by 's'}}
+ capturePointer(getPointerNoLB(std::string()), s);
+
+ // Capture using std::string_view.
+ captureSV(local_sv, s);
+ captureSV(std::string(), // expected-warning {{object captured by 's'}}
+ s);
+ captureSV(substr(
+ std::string() // expected-warning {{object captured by 's'}}
+ ), s);
+ captureSV(substr(local_s), s);
+ captureSV(strcopy(std::string()), s);
+ captureRValSV(std::move(local_sv), s);
+ captureRValSV(std::string(), s); // expected-warning {{object captured by 's'}}
+ captureRValSV(std::string_view{"abcd"}, s);
+ captureRValSV(substr(local_s), s);
+ captureRValSV(substr(std::string()), s); // expected-warning {{object captured by 's'}}
+ captureRValSV(strcopy(std::string()), s);
+ noCaptureSV(local_sv, s);
+ noCaptureSV(std::string(), s);
+ noCaptureSV(substr(std::string()), s);
+
+ // Capture using std::string.
+ captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+ captureS(local_s, s);
+ captureRValS(std::move(local_s), s);
+ captureRValS(std::string(), s); // expected-warning {{object captured by 's'}}
+
+ // Member functions.
+ s.captureInt(1); // expected-warning {{object captured by 's'}}
+ s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+ s.captureSV(substr(std::string())); // expected-warning {{object captured by 's'}}
+ s.captureSV(strcopy(std::string()));
+
+ // 'this' is captured.
+ ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
+ ThisIsCaptured TIS;
+ TIS.capture(s);
+}
+class [[gsl::Pointer()]] my_string_view : public std::string_view {};
+class my_string_view_not_pointer : public std::string_view {};
+std::optional<std::string_view> getOptionalSV();
+std::optional<std::string> getOptionalS();
+std::optional<my_string_view> getOptionalMySV();
+std::optional<my_string_view_not_pointer> getOptionalMySVNotP();
+my_string_view getMySV();
+my_string_view_not_pointer getMySVNotP();
+
+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 captured by 'set_of_int' will be destroyed}}
----------------
Xazax-hun wrote:
I would argue that either the annotation on `MySet` is wrong, or if we want that annotation to be correct, we need to handle these cases without false positives. I think this is the case for `lifetimebound` as well. For the equivalent false positives we either should not have the lifetimebound annotation in the first place since it is incorrect for some of the instantiations, or we should have a way to handle it correctly.
I think currently I am leaning in the direction of saying that the annotation on a template is not correct if it is not correct for some of the instantiations.
We either should tell our users to have ifdefed versions of these APIs and only add these annotations to the overloads where they are correct for all instantiations, or we need to have a conditional version of these annotations where the user can somehow select what specializations should this be applied to.
Overall, I don't think this problem is a blocker for this PR, but I would definitely oppose any automatic inference for templated types where the annotation might be incorrect for some of the instantiations.
https://github.com/llvm/llvm-project/pull/115921
More information about the cfe-commits
mailing list