[clang] [LifetimeSafety] Revamp how accesses and uses are handled (PR #225799)
Gábor Horváth via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 24 06:56:10 PDT 2026
https://github.com/Xazax-hun updated https://github.com/llvm/llvm-project/pull/225799
>From 5c83923e820da51d4066ced5efde89139db6d412 Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Wed, 23 Sep 2026 14:59:57 +0100
Subject: [PATCH 1/5] [LifetimeSafety] Mark uses in tests with use(X) instead
of (void)X
In C++ `(void)X` is a discarded-value expression with no lvalue-to-rvalue
conversion, so it only counts as a use because the analysis currently treats
every DeclRefExpr as one. Pass the value to a `use` helper instead, which is a
use under any definition. Tests of escapes through fields keep `(void)`, since
there the absence of a use is the point.
This is a mechanical change with no effect on the analysis.
Assisted by: Opus 5.5
---
.../LifetimeSafety/Inputs/lifetime-analysis.h | 4 +
.../LifetimeSafety/annotation-suggestions.cpp | 14 +-
clang/test/Sema/LifetimeSafety/capture-by.cpp | 132 +++---
.../test/Sema/LifetimeSafety/cfg-bailout.cpp | 4 +-
.../Sema/LifetimeSafety/dangling-global.cpp | 2 +-
.../inapplicable-lifetimebound.cpp | 2 +-
.../Sema/LifetimeSafety/invalidations.cpp | 70 ++--
.../lifetimebound-violation.cpp | 2 +-
clang/test/Sema/LifetimeSafety/nocfg.cpp | 5 +-
.../LifetimeSafety/noescape-violation.cpp | 2 +-
clang/test/Sema/LifetimeSafety/safety.cpp | 389 +++++++++---------
.../unittests/Analysis/LifetimeSafetyTest.cpp | 17 +-
12 files changed, 323 insertions(+), 320 deletions(-)
diff --git a/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h b/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
index 024c3c2bc51b79..a24e1a7abcf86c 100644
--- a/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
@@ -362,3 +362,7 @@ void *operator new[](std::size_t, void *) noexcept;
void *operator new(std::size_t, const std::nothrow_t &) noexcept;
void *operator new(std::size_t, std::align_val_t,
const std::nothrow_t &) noexcept;
+
+// Marks a use for -Wlifetime-safety. `(void)v` is not one: a discarded-value
+// expression performs no lvalue-to-rvalue conversion in C++.
+template <typename... Ts> void use(const Ts &...vs);
diff --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
index 51d16ddcbf7673..22fe5e6bddfb59 100644
--- a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
+++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
@@ -279,26 +279,26 @@ void test_get_on_temporary_pointer() {
const ReturnsSelf* s_ref = &ReturnsSelf().get(); // expected-warning {{temporary object does not live long enough}}.
// expected-note at -1 {{temporary object is destroyed here}}
// expected-note at -2 {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
- (void)s_ref; // expected-note {{later used here}}
+ use(s_ref); // expected-note {{later used here}}
}
void test_get_on_temporary_ref() {
const ReturnsSelf& s_ref = ReturnsSelf().get(); // expected-warning {{temporary object does not live long enough}}.
// expected-note at -1 {{temporary object is destroyed here}}
// expected-note at -2 {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
- (void)s_ref; // expected-note {{later used here}}
+ use(s_ref); // expected-note {{later used here}}
}
void test_getView_on_temporary() {
View sv = ViewProvider{1}.getView(); // expected-warning {{temporary object does not live long enough}}.
// expected-note at -1 {{temporary object is destroyed here}}
// expected-note at -2 {{result of call to 'getView' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
- (void)sv; // expected-note {{later used here}}
+ use(sv); // expected-note {{later used here}}
}
void test_get_on_temporary_copy() {
ReturnsSelf copy = ReturnsSelf().get();
- (void)copy;
+ use(copy);
}
struct MemberReturn {
@@ -605,7 +605,7 @@ void uaf_via_inferred_lifetimebound() {
f = return_lambda_capturing_param(local); // expected-warning {{local variable 'local' does not live long enough}} \
// expected-note {{result of call to 'return_lambda_capturing_param' aliases the storage of local variable 'local' because parameter 'x' is inferred as lifetimebound}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)f; // expected-note {{later used here}}
+ use(f); // expected-note {{later used here}}
}
} // namespace callable_wrappers
@@ -629,7 +629,7 @@ void test_inference() {
ptr = create_target(obj); // expected-warning {{local variable 'obj' does not live long enough}} \
// expected-note {{result of call to 'create_target' aliases the storage of local variable 'obj' because parameter 'obj' is inferred as lifetimebound}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)ptr; // expected-note {{later used here}}
+ use(ptr); // expected-note {{later used here}}
}
} // namespace make_unique_suggestion
@@ -643,7 +643,7 @@ void test_new_allocation() {
View* v = MakeView(MyObj{}); // expected-warning {{temporary object does not live long enough}} \
// expected-note {{temporary object is destroyed here}} \
// expected-note {{result of call to 'MakeView' aliases the storage of temporary object because parameter 'in' is inferred as lifetimebound}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
struct LifetimeBoundCtor {
diff --git a/clang/test/Sema/LifetimeSafety/capture-by.cpp b/clang/test/Sema/LifetimeSafety/capture-by.cpp
index e9e745b52ec0b7..99924bed821457 100644
--- a/clang/test/Sema/LifetimeSafety/capture-by.cpp
+++ b/clang/test/Sema/LifetimeSafety/capture-by.cpp
@@ -16,11 +16,11 @@ 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}}
+ use(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}}
+use(x); // cfg-note {{later used here}}
}
void local_int_capture() {
@@ -28,7 +28,7 @@ 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}}
+ use(x); // cfg-note {{later used here}}
}
void safe_int_captures() {
@@ -51,11 +51,11 @@ 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}}
+ use(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}}
+ use(x); // cfg-note {{later used here}}
}
void local_string_capture() {
@@ -65,7 +65,7 @@ void local_string_capture() {
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}}
+ use(x); // cfg-note 2 {{later used here}}
}
void safe_string_captures() {
@@ -93,11 +93,11 @@ 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}}
+ use(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}}
+ use(x); // cfg-note {{later used here}}
}
void local_string_capture() {
@@ -106,7 +106,7 @@ void local_string_capture() {
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}}
+ use(x); // cfg-note {{later used here}}
}
// Lifetimebound captures
@@ -115,17 +115,17 @@ void temporary_string_view_lifetimebound_capture() {
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}}
+ use(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}}
+ use(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}}
+ use(x); // cfg-note {{later used here}}
}
void local_string_lifetimebound_capture() {
@@ -134,7 +134,7 @@ void local_string_lifetimebound_capture() {
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}}
+ use(x); // cfg-note {{later used here}}
}
void temporary_nested_lifetimebound_capture() {
@@ -143,12 +143,12 @@ void temporary_nested_lifetimebound_capture() {
// 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}}
+ use(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}}
+ use(x); // cfg-note {{later used here}}
}
void safe_captures() {
@@ -170,7 +170,7 @@ void capture(std::string_view s [[clang::lifetime_capture_by(x1),
clang::lifetime_capture_by_global]],
X &x1);
-void use() {
+void test() {
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}}
@@ -192,7 +192,7 @@ void temporary_pointer_lifetimebound_capture() {
// 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}}
+ use(x); // cfg-note {{later used here}}
}
void temporary_nested_lifetimebound_capture() {
@@ -221,11 +221,11 @@ 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}}
+ use(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}}
+ use(x); // cfg-note {{later used here}}
}
void local_vector_capture() {
@@ -233,7 +233,7 @@ 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}}
+ use(x); // cfg-note {{later used here}}
}
void local_array_capture() {
@@ -258,7 +258,7 @@ struct S {
};
// FIXME: Add support for capture of method declarations in -Wlifetime-safety
-void use() {
+void test() {
S{}.capture(x); // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
S s;
s.capture(x);
@@ -289,7 +289,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() {
+void test() {
std::string_view local_string_view;
std::string local_string;
// capture by global.
@@ -323,11 +323,11 @@ void temporary_capture_by_this() {
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}}
+ use(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}}
+ use(s); // cfg-note {{later used here}}
}
void lifetimebound_capture_by_this() {
@@ -336,12 +336,12 @@ void lifetimebound_capture_by_this() {
// 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}}
+ use(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}}
+ use(s); // cfg-note {{later used here}}
s.captureView(getNotLifetimeBoundView(std::string()));
}
} // namespace capture_by_this
@@ -355,7 +355,7 @@ struct Foo {
const int& b;
};
void captureField(Foo param [[clang::lifetime_capture_by(x)]], X &x);
-void use() {
+void test() {
captureField(Foo{
1 // expected-warning {{object whose reference is captured by 'x' will be destroyed at the end of the full-expression}}
}, x);
@@ -397,14 +397,14 @@ void user_defined_containers() {
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}}
+ use(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 {{temporary object does not live long enough}} \
// cfg-note {{destroyed here}}
- (void)set_of_sv; // cfg-note {{later used here}}
+ use(set_of_sv); // cfg-note {{later used here}}
set_of_sv.insert(std::string_view());
- (void)set_of_sv;
+ use(set_of_sv);
}
} // namespace containers_no_distinction
@@ -437,27 +437,27 @@ void use_container() {
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}}
+ use(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}}
+ use(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 {{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}}
+ use(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}}
+ use(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;
+ use(vector_of_pointer);
}
// ****************************************************************************
@@ -489,35 +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;
+ use(vector_of_my_view);
vector_of_my_view.push_back(MyStringView{});
- (void)vector_of_my_view;
+ use(vector_of_my_view);
vector_of_my_view.push_back(std::string_view{});
- (void)vector_of_my_view;
+ use(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}}
+ use(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}}
+ use(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}}
+ use(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(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;
+ use(vector_of_view);
vector_of_view.push_back(getMySVNotP());
- (void)vector_of_view;
+ use(vector_of_view);
}
// ****************************************************************************
@@ -532,11 +532,11 @@ void use_with_optional_view() {
// 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}}
+ use(vector_of_view); // cfg-note {{later used here}}
vector_of_view.push_back(getOptionalSV().value());
- (void)vector_of_view;
+ use(vector_of_view);
vector_of_view.push_back(getOptionalMySV().value());
- (void)vector_of_view;
+ use(vector_of_view);
vector_of_view.push_back(getOptionalMySVNotP().value());
}
} // namespace conatiners_with_different
@@ -552,31 +552,31 @@ void capture2(const std::string_view& s [[clang::lifetime_capture_by(x)]], std::
// 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 use() {
+void test() {
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 {{temporary object does not live long enough}} \
// cfg-note {{destroyed here}}
- (void)x1; // cfg-note {{later used here}}
+ use(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);
- (void)x2;
+ use(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}}
+ use(x2); // cfg-note {{later used here}}
std::vector<std::string_view> x3;
capture3(std::string_view(), x3);
- (void)x3;
+ use(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}}
+ use(x3); // cfg-note {{later used here}}
}
} // namespace temporary_views
@@ -589,43 +589,43 @@ 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() {
+void test() {
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 {{temporary object does not live long enough}} \
// cfg-note {{destroyed here}}
- (void)views; // cfg-note {{later used here}}
+ use(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 {{temporary object does not live long enough}} \
// cfg-note {{destroyed here}}
- (void)views; // cfg-note {{later used here}}
+ use(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}}
+ use(views); // cfg-note {{later used here}}
views.push_back(getNotLifetimeBoundView(std::string()));
- (void)views;
+ use(views);
{
std::string local1, local2;
views.push_back(local1); // cfg-warning {{local variable 'local1' does not live long enough}}
- (void)views;
+ use(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}}
+ use(views); // cfg-note 2 {{later used here}}
std::vector<std::string> strings;
strings.push_back(std::string());
- (void)views;
+ use(views);
strings.insert(strings.begin(), std::string());
std::vector<const std::string*> pointers;
pointers.push_back(getLifetimeBoundPointer(std::string()));
- (void)views;
+ use(views);
std::string local;
pointers.push_back(&local);
- (void)views;
+ use(views);
}
namespace with_span {
@@ -635,17 +635,17 @@ struct [[gsl::Pointer]] Span {
Span(const std::vector<T> &V);
};
-void use() {
+void test() {
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 {{temporary object does not live long enough}} \
// cfg-note {{destroyed here}}
- (void)spans; // cfg-note {{later used here}}
+ use(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}}
+ use(spans); // cfg-note {{later used here}}
}
} // namespace with_span
} // namespace inferred_capture_by
diff --git a/clang/test/Sema/LifetimeSafety/cfg-bailout.cpp b/clang/test/Sema/LifetimeSafety/cfg-bailout.cpp
index 6f2c06fdf6a723..644763763e0407 100644
--- a/clang/test/Sema/LifetimeSafety/cfg-bailout.cpp
+++ b/clang/test/Sema/LifetimeSafety/cfg-bailout.cpp
@@ -1,6 +1,8 @@
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -lifetime-safety-max-cfg-blocks=3 -Wno-dangling -verify=bailout %s
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify=bailout -verify=nobailout %s
+template <typename... Ts> void use(const Ts &...);
+
struct MyObj {
int id;
~MyObj() {} // Non-trivial destructor
@@ -29,7 +31,7 @@ void single_block_cfg() {
MyObj s;
p = &s; // bailout-warning {{local variable 's' does not live long enough}}
} // bailout-note {{destroyed here}}
- (void)*p; // bailout-note {{later used here}}
+ use(*p); // bailout-note {{later used here}}
}
void multiple_block_cfg() {
diff --git a/clang/test/Sema/LifetimeSafety/dangling-global.cpp b/clang/test/Sema/LifetimeSafety/dangling-global.cpp
index d0cb03ede38f7c..ef00582fb9c122 100644
--- a/clang/test/Sema/LifetimeSafety/dangling-global.cpp
+++ b/clang/test/Sema/LifetimeSafety/dangling-global.cpp
@@ -72,7 +72,7 @@ void conditional_no_escape(int c) {
int local = 7;
if (c)
global = nullptr; // no-warning
- (void)local;
+ use(local);
}
// Pointer compound assignment and increment/decrement keep the pointer in the
diff --git a/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp b/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp
index 624a8e9bb00cc6..8a91e264d39cfc 100644
--- a/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp
+++ b/clang/test/Sema/LifetimeSafety/inapplicable-lifetimebound.cpp
@@ -74,7 +74,7 @@ Owner *template_value(T t [[clang::lifetimebound]]) {
void instantiate_template() {
Owner o;
- (void)template_value(o);
+ use(template_value(o));
}
struct S {
diff --git a/clang/test/Sema/LifetimeSafety/invalidations.cpp b/clang/test/Sema/LifetimeSafety/invalidations.cpp
index 593ba92cf7443c..bfe161f7970c25 100644
--- a/clang/test/Sema/LifetimeSafety/invalidations.cpp
+++ b/clang/test/Sema/LifetimeSafety/invalidations.cpp
@@ -273,7 +273,7 @@ void IteratorUsedAfterPreIncrement() {
// expected-note {{result of call to 'begin' aliases the storage of local variable 'v'}}
auto next = ++it; // expected-note {{result of call to 'operator++' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
- (void)*next; // expected-note {{later used here}}
+ use(*next); // expected-note {{later used here}}
}
void IteratorUsedAfterPostDecrement(std::vector<int> v) {
@@ -281,7 +281,7 @@ void IteratorUsedAfterPostDecrement(std::vector<int> v) {
// expected-note {{result of call to 'rbegin' aliases the storage of parameter 'v'}}
auto prev = it--; // expected-note {{result of call to 'operator--' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
- (void)*prev; // expected-note {{later used here}}
+ use(*prev); // expected-note {{later used here}}
}
void IteratorUsedAfterAddition() {
@@ -290,7 +290,7 @@ void IteratorUsedAfterAddition() {
// expected-note {{result of call to 'cbegin' aliases the storage of local variable 'v'}}
auto next = it + 5; // expected-note {{result of call to 'operator+' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
- (void)*next; // expected-note {{later used here}}
+ use(*next); // expected-note {{later used here}}
}
void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
@@ -299,7 +299,7 @@ void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
auto prev = 5 - it; // expected-note {{local variable 'it' aliases the storage of parameter 'v'}} \
// expected-note {{result of call to 'operator-<__gnu_cxx::basic_iterator<const int>>' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
- (void)*prev; // expected-note {{later used here}}
+ use(*prev); // expected-note {{later used here}}
}
void IteratorUsedAfterAddAdd(std::vector<int> v) {
@@ -307,7 +307,7 @@ void IteratorUsedAfterAddAdd(std::vector<int> v) {
// expected-note {{result of call to 'cbegin' aliases the storage of parameter 'v'}}
auto next = (it + 5) + 5; // expected-note 2 {{result of call to 'operator+' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
- (void)*next; // expected-note {{later used here}}
+ use(*next); // expected-note {{later used here}}
}
void IteratorUsedAfterMixedAddition() {
@@ -318,7 +318,7 @@ void IteratorUsedAfterMixedAddition() {
// expected-note 2 {{result of call to 'operator+' aliases the storage of local variable 'v'}} \
// expected-note {{local variable 'it' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
- (void)*next; // expected-note {{later used here}}
+ use(*next); // expected-note {{later used here}}
}
void IteratorUsedAfterPreIncrementAddAssign(std::vector<int> v) {
@@ -327,7 +327,7 @@ void IteratorUsedAfterPreIncrementAddAssign(std::vector<int> v) {
it = ++it + 1 + 2; // expected-note {{result of call to 'operator++' aliases the storage of parameter 'v'}} \
// expected-note 2 {{result of call to 'operator+' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
- (void)*it; // expected-note {{later used here}}
+ use(*it); // expected-note {{later used here}}
}
void IteratorUsedAfterBeginAddAssign() {
@@ -336,7 +336,7 @@ void IteratorUsedAfterBeginAddAssign() {
// expected-note {{result of call to 'operator+' aliases the storage of local variable 'v'}} \
// expected-note {{result of call to 'begin' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
- (void)*it; // expected-note {{later used here}}
+ use(*it); // expected-note {{later used here}}
}
void IteratorUsedAfterStdBeginAddAssign() {
@@ -346,7 +346,7 @@ void IteratorUsedAfterStdBeginAddAssign() {
// expected-note {{result of call to 'operator+' aliases the storage of local variable 'v'}} \
// expected-note {{result of call to 'begin<std::vector<int>>' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
- (void)*it; // expected-note {{later used here}}
+ use(*it); // expected-note {{later used here}}
}
} // namespace SimpleInvalidIterators
@@ -357,13 +357,13 @@ void IteratorInvalidatedThroughLocalReferenceAlias() {
auto it = vv.begin(); // expected-warning {{local variable 'vv' is later invalidated}} \
// expected-note {{result of call to 'begin' aliases the storage of local variable 'vv'}}
v.push_back(42); // expected-note {{local variable 'vv' is invalidated here}}
- (void)it; // expected-note {{later used here}}
+ use(it); // expected-note {{later used here}}
}
void IteratorInvalidatedThroughPointerParameter(std::vector<int> *v) { // expected-warning {{parameter 'v' is later invalidated}}
auto it = v->begin(); // expected-note {{result of call to 'begin' aliases the storage of parameter 'v'}}
v->push_back(42); // expected-note {{parameter 'v' is invalidated here}}
- (void)it; // expected-note {{later used here}}
+ use(it); // expected-note {{later used here}}
}
void ParenthesizedContainerInvalidatesIterator() {
@@ -371,7 +371,7 @@ void ParenthesizedContainerInvalidatesIterator() {
std::vector<int> v;
auto it = v.begin();
(v).push_back(42);
- (void)it;
+ use(it);
}
} // namespace InvalidatingThroughContainerAliases
@@ -380,7 +380,7 @@ namespace ContainerObjectAliases {
// FIXME: Distinguish owner-borrow from content-borrow.
void PointerParameterObjectUseIsOk(std::vector<int> *v) { // expected-warning {{parameter 'v' is later invalidated}}
v->push_back(42); // expected-note {{parameter 'v' is invalidated here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
// FIXME: Distinguish owner-borrow from content-borrow.
@@ -388,7 +388,7 @@ void LocalPointerAliasObjectUseIsOk() {
std::vector<int> vv;
std::vector<int> *v = &vv; // expected-warning {{local variable 'vv' is later invalidated}}
v->push_back(42); // expected-note {{local variable 'vv' is invalidated here}}
- (void)*v; // expected-note {{later used here}}
+ use(*v); // expected-note {{later used here}}
}
// FIXME: Distinguish owner-borrow from content-borrow.
@@ -396,7 +396,7 @@ void LocalReferenceAliasObjectUseIsOk() {
std::vector<int> vv;
std::vector<int> &v = vv; // expected-warning {{local variable 'vv' is later invalidated}}
v.push_back(42); // expected-note {{local variable 'vv' is invalidated here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
} // namespace ContainerObjectAliases
@@ -409,7 +409,7 @@ void ReferenceToVectorElement() {
// expected-note {{result of call to 'operator[]' aliases the storage of local variable 'v'}}
v.push_back(4); // expected-note {{local variable 'v' is invalidated here}}
ref = 10; // expected-note {{later used here}}
- (void)ref;
+ use(ref);
}
void PointerRefToVectorElement() {
@@ -469,12 +469,12 @@ namespace Strings {
void append(std::string str) {
std::string_view view = str; // expected-warning {{parameter 'str' is later invalidated}}
str += "456"; // expected-note {{parameter 'str' is invalidated here}}
- (void)view; // expected-note {{later used here}}
+ use(view); // expected-note {{later used here}}
}
void reassign(std::string str, std::string str2) {
std::string_view view = str; // expected-warning {{parameter 'str' is later invalidated}}
str = str2; // expected-note {{parameter 'str' is invalidated here}}
- (void)view; // expected-note {{later used here}}
+ use(view); // expected-note {{later used here}}
}
} // namespace Strings
@@ -484,7 +484,7 @@ void ReassigningAfterMove(std::string str, std::string str2) {
std::vector<std::string> someStorage;
someStorage.push_back(std::move(str));
str = str2; // expected-note {{parameter 'str' is invalidated here}}
- (void)view; // expected-note {{later used here}}
+ use(view); // expected-note {{later used here}}
}
namespace ContainersAsFields {
@@ -512,7 +512,7 @@ void ConditionalContainerInvalidatesIterator(bool flag) {
std::vector<int> v1, v2;
auto it = v1.begin();
(flag ? v1 : v2).push_back(42);
- (void)it;
+ use(it);
}
void ConditionalFieldInvalidatesIterator(bool flag) {
// FIXME: Support conditional invalidation through field expressions.
@@ -534,14 +534,14 @@ void Invalidate1UseSIsOk() {
S s;
S* p = &s;
s.strings2.push_back("1");
- (void)*p;
+ use(*p);
}
// FIXME: Distinguish owner-borrow from content-borrow.
void PointerToContainerIsOk() {
std::vector<std::string> s;
std::vector<std::string>* p = &s; // expected-warning {{local variable 's' is later invalidated}}
p->push_back("1"); // expected-note {{local variable 's' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void IteratorFromPointerToContainerIsInvalidated() {
std::vector<std::string> s;
@@ -869,7 +869,7 @@ void function_captured_ref_invalidated() {
std::function<void()> f = [&r = v[0]]() { (void)r; }; // expected-warning {{local variable 'v' is later invalidated}} \
// expected-note {{result of call to 'operator[]' aliases the storage of local variable 'v'}}
v.push_back(2); // expected-note {{local variable 'v' is invalidated here}}
- (void)f; // expected-note {{later used here}}
+ use(f); // expected-note {{later used here}}
}
} // namespace callable_wrappers
@@ -882,7 +882,7 @@ void explicit_destructor_invalidates_pointer() {
const char *p = s.data(); // expected-warning {{local variable 's' is later invalidated}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 's'}}
s.~basic_string(); // expected-note {{local variable 's' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void pointer_destructor_invalidates_pointer() {
@@ -891,7 +891,7 @@ void pointer_destructor_invalidates_pointer() {
const char *p = obj->data(); // expected-note {{local variable 'obj' aliases the storage of local variable 'storage'}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 'storage'}}
obj->~basic_string(); // expected-note {{local variable 'storage' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void destroy_at_invalidates_pointer() {
@@ -900,7 +900,7 @@ void destroy_at_invalidates_pointer() {
const char *p = obj->data(); // expected-note {{local variable 'obj' aliases the storage of local variable 'storage'}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 'storage'}}
std::destroy_at(obj); // expected-note {{local variable 'storage' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void destroy_at_then_placement_new_rescues_pointer() {
@@ -910,7 +910,7 @@ void destroy_at_then_placement_new_rescues_pointer() {
std::destroy_at(obj);
obj = new (storage) std::string("23");
p = obj->data();
- (void)*p;
+ use(*p);
}
void destroy_at_invalidates_array_pointer() {
@@ -919,7 +919,7 @@ void destroy_at_invalidates_array_pointer() {
const char *p = arr[0].data(); // expected-warning {{local variable 'arr' is later invalidated}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 'arr'}}
std::destroy_at(&arr_ref); // expected-note {{local variable 'arr' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void reference_destructor_invalidates_pointer() {
@@ -928,7 +928,7 @@ void reference_destructor_invalidates_pointer() {
const char *p = ref.data(); // expected-note {{local variable 'ref' aliases the storage of local variable 's'}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 's'}}
std::destroy_at(&ref); // expected-note {{local variable 's' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void destroy_at_ternary_operator(bool flag) {
@@ -937,7 +937,7 @@ void destroy_at_ternary_operator(bool flag) {
const char *p = str1->data(); // expected-note {{local variable 'str1' aliases the storage of allocated object}} \
// expected-note {{result of call to 'data' aliases the storage of allocated object}}
std::destroy_at(flag ? str1 : str2); // expected-note {{allocated object is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
struct StringOwner {
@@ -950,7 +950,7 @@ void member_destructor_invalidates_pointer() {
const char *p = owner.s.data(); // expected-warning {{local variable 'owner' is later invalidated}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 'owner'}}
owner.t.~basic_string(); // expected-note {{local variable 'owner' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
} // namespace explicit_destructor
@@ -962,7 +962,7 @@ void invalid_after_reset() {
int *p = up.get(); // expected-warning {{local variable 'up' is later invalidated}} \
// expected-note {{result of call to 'get' aliases the storage of local variable 'up'}}
up.reset(); // expected-note {{local variable 'up' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void invalid_after_move_assign() {
@@ -971,7 +971,7 @@ void invalid_after_move_assign() {
int *p = up.get(); // expected-warning {{local variable 'up' is later invalidated}} \
// expected-note {{result of call to 'get' aliases the storage of local variable 'up'}}
up = std::move(other); // expected-note {{local variable 'up' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void invalid_after_null_assign() {
@@ -979,7 +979,7 @@ void invalid_after_null_assign() {
int *p = up.get(); // expected-warning {{local variable 'up' is later invalidated}} \
// expected-note {{result of call to 'get' aliases the storage of local variable 'up'}}
up = nullptr; // expected-note {{local variable 'up' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void invalid_after_ternary_reset(bool flag) {
@@ -988,7 +988,7 @@ void invalid_after_ternary_reset(bool flag) {
int *p = flag ? up.get() : other.get(); // expected-warning {{local variable 'up' is later invalidated}} \
// expected-note {{result of call to 'get' aliases the storage of local variable 'up'}}
up.reset(); // expected-note {{local variable 'up' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
} // namespace unique_ptr_invalidation
diff --git a/clang/test/Sema/LifetimeSafety/lifetimebound-violation.cpp b/clang/test/Sema/LifetimeSafety/lifetimebound-violation.cpp
index fcf8e496de3d36..2d154154d10772 100644
--- a/clang/test/Sema/LifetimeSafety/lifetimebound-violation.cpp
+++ b/clang/test/Sema/LifetimeSafety/lifetimebound-violation.cpp
@@ -164,7 +164,7 @@ struct AssignementIncorr {
};
void implicit_lifetimebound_in_nested_std_namespace() {
- (void)std::basic_string_view<char>("hello");
+ use(std::basic_string_view<char>("hello"));
}
struct ViewWithMember {
// No warning. A lifetimebound constructor parameter may escape into a field of the constructed object.
diff --git a/clang/test/Sema/LifetimeSafety/nocfg.cpp b/clang/test/Sema/LifetimeSafety/nocfg.cpp
index 7ef209572c4376..7f4a58c1836dd9 100644
--- a/clang/test/Sema/LifetimeSafety/nocfg.cpp
+++ b/clang/test/Sema/LifetimeSafety/nocfg.cpp
@@ -44,7 +44,6 @@ struct [[gsl::Owner(long)]] MyLongOwnerWithConversion {
long *releaseAsRawPointer();
};
-template<class... T> void use(T... arg);
void danglingHeapObject() {
new MyLongPointerFromConversion(MyLongOwnerWithConversion{}); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
@@ -77,7 +76,7 @@ struct Y {
void dangligGslPtrFromTemporary() {
MyIntPointer p = Y{}.a; // cfg-warning {{temporary object does not live long enough}} \
// cfg-note {{destroyed here}}
- (void)p; // cfg-note {{later used here}}
+ use(p); // cfg-note {{later used here}}
}
struct DanglingGslPtrField {
@@ -195,7 +194,7 @@ void modelIterators() {
std::vector<int>::iterator it = std::vector<int>().begin(); // expected-warning {{object backing the 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 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
- (void)it; // cfg-note {{later used here}}
+ use(it); // cfg-note {{later used here}}
}
std::vector<int>::iterator modelIteratorReturn() {
diff --git a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
index 048c500239b4fd..9978850a92e51b 100644
--- a/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
+++ b/clang/test/Sema/LifetimeSafety/noescape-violation.cpp
@@ -47,7 +47,7 @@ View mixed_noescape_lifetimebound(
View mixed_only_noescape_escapes(
const MyObj& a [[clang::noescape]],
const MyObj& b [[clang::lifetimebound]]) {
- (void)a;
+ use(a);
return b;
}
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index c87acc9c58aeb1..952e9cdf052020 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -44,7 +44,6 @@ MyTrivialObj trivially_destructed_temporary();
View construct_view(const MyObj &obj [[clang::lifetimebound]]) {
return View(obj);
}
-void use(View);
//===----------------------------------------------------------------------===//
// Basic Use-After-Free
@@ -56,7 +55,7 @@ void simple_case() {
MyObj s;
p = &s; // expected-warning {{local variable 's' does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void simple_case_gsl() {
@@ -94,7 +93,7 @@ void pointer_chain() {
p = &s; // expected-warning {{does not live long enough}}
q = p; // expected-note {{local variable 'p' aliases the storage of local variable 's'}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*q; // expected-note {{later used here}}
+ use(*q); // expected-note {{later used here}}
}
void propagation_gsl() {
@@ -113,11 +112,11 @@ void multiple_uses_one_warning() {
MyObj s;
p = &s; // expected-warning {{does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
// No second warning for the same loan.
p->id = 1;
MyObj* q = p;
- (void)*q;
+ use(*q);
}
void multiple_pointers() {
@@ -128,9 +127,9 @@ void multiple_pointers() {
q = &s; // expected-warning {{does not live long enough}}
r = &s; // expected-warning {{does not live long enough}}
} // expected-note 3 {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
- (void)*q; // expected-note {{later used here}}
- (void)*r; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
+ use(*q); // expected-note {{later used here}}
+ use(*r); // expected-note {{later used here}}
}
void multiple_pointers_chained() {
@@ -140,7 +139,7 @@ void multiple_pointers_chained() {
MyObj* obj1, *obj2;
p = obj1 = obj2 = &s; // expected-warning {{does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void multiple_pointers_chained_safe() {
@@ -150,7 +149,7 @@ void multiple_pointers_chained_safe() {
MyObj* obj1, *obj2;
p = obj1 = obj2 = &s;
}
- (void)*p;
+ use(*p);
}
void single_pointer_multiple_loans(bool cond) {
@@ -163,7 +162,7 @@ void single_pointer_multiple_loans(bool cond) {
MyObj t;
p = &t; // expected-warning {{does not live long enough}}
} // expected-note {{local variable 't' is destroyed here}}
- (void)*p; // expected-note 2 {{later used here}}
+ use(*p); // expected-note 2 {{later used here}}
}
void single_pointer_multiple_loans_gsl(bool cond) {
@@ -186,7 +185,7 @@ void if_branch(bool cond) {
MyObj temp;
p = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void if_branch_potential(bool cond) {
@@ -197,7 +196,7 @@ void if_branch_potential(bool cond) {
p = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
if (!cond)
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
else
p = &safe;
}
@@ -224,9 +223,9 @@ void potential_together(bool cond) {
if (cond)
p_maybe = &s; // expected-warning {{does not live long enough}}
} // expected-note 2 {{local variable 's' is destroyed here}}
- (void)*p_definite; // expected-note {{later used here}}
+ use(*p_definite); // expected-note {{later used here}}
if (!cond)
- (void)*p_maybe; // expected-note {{later used here}}
+ use(*p_maybe); // expected-note {{later used here}}
}
void overrides_potential(bool cond) {
@@ -245,9 +244,9 @@ void overrides_potential(bool cond) {
}
// The use of 'p' dominates expiry of 's' error because it was never rescued.
- (void)*q;
- (void)*p; // expected-note {{later used here}}
- (void)*q;
+ use(*q);
+ use(*p); // expected-note {{later used here}}
+ use(*q);
}
void due_to_conditional_killing(bool cond) {
@@ -261,7 +260,7 @@ void due_to_conditional_killing(bool cond) {
// 'q' is conditionally "rescued". 'p' is not.
q = &safe;
}
- (void)*q; // expected-note {{later used here}}
+ use(*q); // expected-note {{later used here}}
}
void for_loop_use_after_loop_body(MyObj safe) {
@@ -270,7 +269,7 @@ void for_loop_use_after_loop_body(MyObj safe) {
MyObj s;
p = &s; // expected-warning {{does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void safe_for_loop_gsl() {
@@ -297,11 +296,11 @@ void for_loop_use_before_loop_body(MyObj safe) {
MyObj* p = &safe;
// Prefer the earlier use for diagnsotics.
for (int i = 0; i < 1; ++i) {
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
MyObj s;
p = &s; // expected-warning {{does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p;
+ use(*p);
}
void loop_with_break(bool cond) {
@@ -314,7 +313,7 @@ void loop_with_break(bool cond) {
break; // expected-note {{local variable 'temp' is destroyed here}}
}
}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void loop_with_break_gsl(bool cond) {
@@ -341,7 +340,7 @@ void multiple_expiry_of_same_loan(bool cond) {
break; // expected-note {{local variable 'unsafe' is destroyed here}}
}
}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
p = &safe;
for (int i = 0; i < 10; ++i) {
@@ -352,7 +351,7 @@ void multiple_expiry_of_same_loan(bool cond) {
break; // expected-note {{local variable 'unsafe' is destroyed here}}
}
}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
p = &safe;
for (int i = 0; i < 10; ++i) {
@@ -362,7 +361,7 @@ void multiple_expiry_of_same_loan(bool cond) {
break; // expected-note {{local variable 'unsafe2' is destroyed here}}
}
}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
p = &safe;
for (int i = 0; i < 10; ++i) {
@@ -372,7 +371,7 @@ void multiple_expiry_of_same_loan(bool cond) {
if (cond)
break; // expected-note {{local variable 'unsafe' is destroyed here}}
}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void switch_potential(int mode) {
@@ -390,7 +389,7 @@ void switch_potential(int mode) {
}
}
if (mode == 2)
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void switch_uaf(int mode) {
@@ -414,7 +413,7 @@ void switch_uaf(int mode) {
break; // expected-note {{local variable 'temp2' is destroyed here}}
}
}
- (void)*p; // expected-note 3 {{later used here}}
+ use(*p); // expected-note 3 {{later used here}}
}
void switch_gsl(int mode) {
@@ -449,8 +448,8 @@ void loan_from_previous_iteration(MyObj safe, bool condition) {
if (condition)
q = p; // expected-note {{local variable 'p' aliases the storage of local variable 'x'}}
- (void)*p;
- (void)*q; // expected-note {{later used here}}
+ use(*p);
+ use(*q); // expected-note {{later used here}}
} // expected-note {{local variable 'x' is destroyed here}}
}
@@ -460,7 +459,7 @@ void trivial_int_uaf() {
int b = 1;
a = &b; // expected-warning {{local variable 'b' does not live long enough}}
} // expected-note {{local variable 'b' is destroyed here}}
- (void)*a; // expected-note {{later used here}}
+ use(*a); // expected-note {{later used here}}
}
void trivial_class_uaf() {
@@ -469,7 +468,7 @@ void trivial_class_uaf() {
TriviallyDestructedClass s;
ptr = &s; // expected-warning {{local variable 's' does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)ptr; // expected-note {{later used here}}
+ use(ptr); // expected-note {{later used here}}
}
void small_scope_reference_var_no_error() {
@@ -699,7 +698,7 @@ void test_lifetimebound_multi_level() {
result = return_inner_ptr_addr(ppp); // expected-note {{local variable 'ppp' aliases the storage of local variable 'pp'}} \
// expected-note {{result of call to 'return_inner_ptr_addr' aliases the storage of local variable 'pp' because parameter 'ppp' is marked as lifetimebound}}
} // expected-note {{local variable 'pp' is destroyed here}}
- (void)**result; // expected-note {{used here}}
+ use(**result); // expected-note {{used here}}
}
// FIXME: Assignment does not track the dereference of a pointer.
@@ -711,7 +710,7 @@ void test_assign_through_double_ptr() {
int c = 3;
*pp = &c;
}
- (void)**pp;
+ use(**pp);
}
int** test_ternary_double_ptr(bool cond) {
@@ -760,7 +759,7 @@ void no_error_if_dangle_then_rescue() {
p = &temp; // p is temporarily dangling.
}
p = &safe; // p is "rescued" before use.
- (void)*p; // This is safe.
+ use(*p); // This is safe.
}
void no_error_if_dangle_then_rescue_gsl() {
@@ -783,7 +782,7 @@ void no_error_if_dangle_then_rescue_via_ref() {
ref = &temp; // p temporarily points to temp via ref.
}
ref = &safe; // p is "rescued" via ref before use.
- (void)*ref; // This is safe.
+ use(*ref); // This is safe.
}
void no_error_loan_from_current_iteration(bool cond) {
@@ -795,7 +794,7 @@ void no_error_loan_from_current_iteration(bool cond) {
if (cond) {
p = a;
}
- (void)p;
+ use(p);
}
}
@@ -938,7 +937,7 @@ void lifetimebound_with_pointers() {
ptr = GetPointer(obj); // expected-warning {{local variable 'obj' does not live long enough}} \
// expected-note {{result of call to 'GetPointer' aliases the storage of local variable 'obj' because parameter 'obj' is marked as lifetimebound}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)*ptr; // expected-note {{later used here}}
+ use(*ptr); // expected-note {{later used here}}
}
void chained_assignment_lifetimebound_call() {
@@ -948,7 +947,7 @@ void chained_assignment_lifetimebound_call() {
p = Identity(obj = &s); // expected-warning {{does not live long enough}} \
// expected-note {{result of call to 'Identity' aliases the storage of local variable 's' because parameter 'v' is marked as lifetimebound}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void lifetimebound_no_error_safe_usage() {
@@ -982,7 +981,7 @@ void lifetimebound_return_reference() {
// expected-note {{result of call to 'GetObject' aliases the storage of local variable 'obj' because parameter 'v' is marked as lifetimebound}}
ptr = &ref;
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)*ptr; // expected-note {{later used here}}
+ use(*ptr); // expected-note {{later used here}}
}
struct LifetimeBoundCtor {
@@ -999,7 +998,7 @@ void lifetimebound_ctor() {
MyObj obj;
v = obj; // expected-warning {{local variable 'obj' does not live long enough}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
void lifetimebound_ctor_functional_cast() {
@@ -1008,7 +1007,7 @@ void lifetimebound_ctor_functional_cast() {
MyObj obj;
v = LifetimeBoundCtor(obj); // expected-warning {{local variable 'obj' does not live long enough}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
void lifetimebound_ctor_c_style_cast() {
@@ -1017,7 +1016,7 @@ void lifetimebound_ctor_c_style_cast() {
MyObj obj;
v = (LifetimeBoundCtor)(obj); // expected-warning {{local variable 'obj' does not live long enough}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
void lifetimebound_ctor_static_cast() {
@@ -1026,7 +1025,7 @@ void lifetimebound_ctor_static_cast() {
MyObj obj;
v = static_cast<LifetimeBoundCtor>(obj); // expected-warning {{local variable 'obj' does not live long enough}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
void lifetimebound_make_unique() {
@@ -1036,7 +1035,7 @@ void lifetimebound_make_unique() {
ptr = std::make_unique<LifetimeBoundCtor>(obj); // tu-warning {{local variable 'obj' does not live long enough}} \
// tu-note {{result of call to 'make_unique<LifetimeBoundCtor, MyObj &>' aliases the storage of local variable 'obj' because parameter 'args' is inferred as lifetimebound}}
} // tu-note {{local variable 'obj' is destroyed here}}
- (void)ptr; // tu-note {{later used here}}
+ use(ptr); // tu-note {{later used here}}
}
void non_lifetimebound_make_unique() {
@@ -1046,14 +1045,14 @@ void non_lifetimebound_make_unique() {
// No error as the ctor is not lifetimebound.
ptr = std::make_unique<LifetimeBoundCtor>(obj, 0);
}
- (void)ptr;
+ use(ptr);
}
void lifetimebound_make_unique_temp() {
std::unique_ptr<LifetimeBoundCtor> ptr = std::make_unique<LifetimeBoundCtor>(MyObj()); // tu-warning {{temporary object does not live long enough}} \
// tu-note {{temporary object is destroyed here}} \
// tu-note {{result of call to 'make_unique<LifetimeBoundCtor, MyObj>' aliases the storage of temporary object because parameter 'args' is inferred as lifetimebound}}
- (void)ptr; // tu-note {{later used here}}
+ use(ptr); // tu-note {{later used here}}
}
// FIXME: make_unique annotation cannot be inferred for pointer param in constructor
@@ -1064,7 +1063,7 @@ void lifetimebound_make_unique_raw_ptr() {
int* p = &x;
ptr = std::make_unique<LifetimeBoundCtor>(p);
}
- (void)ptr;
+ use(ptr);
}
// FIXME: make_unique annotation cannot be inferred for view-type param in constructor
@@ -1075,7 +1074,7 @@ void lifetimebound_make_unique_string_view_local() {
std::string_view sv(s);
ptr = std::make_unique<LifetimeBoundCtor>(sv);
}
- (void)ptr;
+ use(ptr);
}
struct MultiLifetimeBoundCtor {
@@ -1092,7 +1091,7 @@ void lifetimebound_make_unique_multi_params() {
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long); // tu-warning {{local variable 'obj_short' does not live long enough}} \
// tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &>' aliases the storage of local variable 'obj_short' because parameter 'args' is inferred as lifetimebound}}
} // tu-note {{local variable 'obj_short' is destroyed here}}
- (void)ptr; // tu-note {{later used here}}
+ use(ptr); // tu-note {{later used here}}
}
void lifetimebound_make_unique_multi_params2() {
@@ -1103,7 +1102,7 @@ void lifetimebound_make_unique_multi_params2() {
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1); // tu-warning {{local variable 'obj_short' does not live long enough}} \
// tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, int>' aliases the storage of local variable 'obj_short' because parameter 'args' is inferred as lifetimebound}}
} // tu-note {{local variable 'obj_short' is destroyed here}}
- (void)ptr; // tu-note {{later used here}}
+ use(ptr); // tu-note {{later used here}}
}
void lifetimebound_make_unique_multi_params2_no_error_case() {
@@ -1113,7 +1112,7 @@ void lifetimebound_make_unique_multi_params2_no_error_case() {
MyObj obj_short;
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1);
}
- (void)ptr;
+ use(ptr);
}
void lifetimebound_make_unique_multi_params3_1() {
@@ -1124,7 +1123,7 @@ void lifetimebound_make_unique_multi_params3_1() {
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_short, obj_long, 1.0); // tu-warning {{local variable 'obj_short' does not live long enough}} \
// tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, double>' aliases the storage of local variable 'obj_short' because parameter 'args' is inferred as lifetimebound}}
} // tu-note {{local variable 'obj_short' is destroyed here}}
- (void)ptr; // tu-note {{later used here}}
+ use(ptr); // tu-note {{later used here}}
}
void lifetimebound_make_unique_multi_params3_2() {
@@ -1135,7 +1134,7 @@ void lifetimebound_make_unique_multi_params3_2() {
ptr = std::make_unique<MultiLifetimeBoundCtor>(obj_long, obj_short, 1.0); // tu-warning {{local variable 'obj_short' does not live long enough}} \
// tu-note {{result of call to 'make_unique<MultiLifetimeBoundCtor, MyObj &, MyObj &, double>' aliases the storage of local variable 'obj_short' because parameter 'args' is inferred as lifetimebound}}
} // tu-note {{local variable 'obj_short' is destroyed here}}
- (void)ptr; // tu-note {{later used here}}
+ use(ptr); // tu-note {{later used here}}
}
View lifetimebound_return_of_local() {
@@ -1218,7 +1217,7 @@ void conditional_operator_one_unsafe_branch(bool cond) {
// ensures safety regardless of cond's value.
if (cond)
p = &safe;
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void conditional_operator_two_unsafe_branches(bool cond) {
@@ -1228,7 +1227,7 @@ void conditional_operator_two_unsafe_branches(bool cond) {
p = cond ? &a // expected-warning {{local variable 'a' does not live long enough}}
: &b; // expected-warning {{local variable 'b' does not live long enough}}
} // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note 2 {{later used here}}
+ use(*p); // expected-note 2 {{later used here}}
}
void conditional_operator_nested(bool cond) {
@@ -1240,7 +1239,7 @@ void conditional_operator_nested(bool cond) {
: cond ? &c // expected-warning {{local variable 'c' does not live long enough}}.
: &d; // expected-warning {{local variable 'd' does not live long enough}}.
} // expected-note {{local variable 'a' is destroyed here}} expected-note {{local variable 'd' is destroyed here}} expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'c' is destroyed here}}
- (void)*p; // expected-note 4 {{later used here}}
+ use(*p); // expected-note 4 {{later used here}}
}
void conditional_operator_lifetimebound(bool cond) {
@@ -1252,7 +1251,7 @@ void conditional_operator_lifetimebound(bool cond) {
// expected-note {{result of call to 'Identity' aliases the storage of local variable 'b' because parameter 'v' is marked as lifetimebound}}
: &b); // expected-warning {{local variable 'b' does not live long enough}}
} // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note 2 {{later used here}}
+ use(*p); // expected-note 2 {{later used here}}
}
void conditional_operator_lifetimebound_nested(bool cond) {
@@ -1265,7 +1264,7 @@ void conditional_operator_lifetimebound_nested(bool cond) {
: Identity(&b)); // expected-warning {{local variable 'b' does not live long enough}} \
// expected-note {{result of call to 'Identity' aliases the storage of local variable 'b' because parameter 'v' is marked as lifetimebound}}
} // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note 2 {{later used here}}
+ use(*p); // expected-note 2 {{later used here}}
}
void conditional_operator_lifetimebound_nested_deep(bool cond) {
@@ -1283,7 +1282,7 @@ void conditional_operator_lifetimebound_nested_deep(bool cond) {
// expected-note {{result of call to 'Identity' aliases the storage of local variable 'd' because parameter 'v' is marked as lifetimebound}}
: &d)); // expected-warning {{local variable 'd' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}} expected-note {{local variable 'd' is destroyed here}} expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'c' is destroyed here}}
- (void)*p; // expected-note 4 {{later used here}}
+ use(*p); // expected-note 4 {{later used here}}
}
// Comma operator.
@@ -1294,7 +1293,7 @@ void comma_use_after_scope() {
MyObj temp;
p = (side(), &temp); // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void comma_nested() {
@@ -1303,7 +1302,7 @@ void comma_nested() {
MyObj temp;
p = (side(), (side(), &temp)); // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void comma_masked_by_conditional(bool cond) {
@@ -1314,13 +1313,13 @@ void comma_masked_by_conditional(bool cond) {
MyObj temp;
p = cond ? keep : (side(), &temp); // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void comma_safe() {
MyObj safe;
MyObj* p = (side(), &safe);
- (void)*p; // no-warning
+ use(*p); // no-warning
}
// GNU binary conditional operator `a ?: b`.
@@ -1330,7 +1329,7 @@ void binary_conditional_false_unsafe(MyObj* in) {
MyObj temp;
p = in ?: &temp; // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void binary_conditional_common_unsafe(MyObj* fallback) {
@@ -1340,13 +1339,13 @@ void binary_conditional_common_unsafe(MyObj* fallback) {
MyObj* t = &temp; // expected-warning {{local variable 'temp' does not live long enough}}
p = t ?: fallback;
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void binary_conditional_safe(MyObj* in) {
MyObj fallback;
MyObj* p = in ?: &fallback;
- (void)*p; // no-warning
+ use(*p); // no-warning
}
void binary_conditional_nested(MyObj* a, MyObj* b) {
@@ -1355,7 +1354,7 @@ void binary_conditional_nested(MyObj* a, MyObj* b) {
MyObj temp;
p = a ?: b ?: &temp; // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void binary_conditional_masked_by_conditional(bool cond, MyObj* in) {
@@ -1366,14 +1365,14 @@ void binary_conditional_masked_by_conditional(bool cond, MyObj* in) {
MyObj temp;
p = cond ? keep : (in ?: &temp); // expected-warning {{local variable 'temp' does not live long enough}}
} // expected-note {{local variable 'temp' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void binary_conditional_use_after_free(int* in) {
int* h = new int; // expected-warning {{allocated object does not live long enough}}
int* p = in ?: h;
delete h; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
int** binary_conditional_double_ptr(int** in) {
@@ -1408,7 +1407,6 @@ FalseView binary_conditional_folded_false(FalseView fb) {
// Unary plus on a pointer is the identity, so the result carries the operand's
// loans.
namespace unary_plus {
-void use(int *p);
void borrow_of_local() {
int *p;
@@ -1461,7 +1459,7 @@ void simpleparen() {
MyObj* b = &a; // expected-warning {{local variable 'a' does not live long enough}}
p = (((b)));
} // expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void parentheses(bool cond) {
@@ -1470,14 +1468,14 @@ void parentheses(bool cond) {
MyObj a;
p = &((((a)))); // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
{
MyObj a;
p = ((GetPointer((a)))); // expected-warning {{local variable 'a' does not live long enough}} \
// expected-note {{result of call to 'GetPointer' aliases the storage of local variable 'a' because parameter 'obj' is marked as lifetimebound}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
{
MyObj a, b, c, d;
@@ -1486,14 +1484,14 @@ void parentheses(bool cond) {
: (cond ? c // expected-warning {{local variable 'c' does not live long enough}}.
: d)); // expected-warning {{local variable 'd' does not live long enough}}.
} // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'c' is destroyed here}} expected-note {{local variable 'a' is destroyed here}} expected-note {{local variable 'd' is destroyed here}}
- (void)*p; // expected-note 4 {{later used here}}
+ use(*p); // expected-note 4 {{later used here}}
{
MyObj a, b, c, d;
p = ((cond ? (((cond ? &a : &b))) // expected-warning {{local variable 'b' does not live long enough}} expected-warning {{local variable 'a' does not live long enough}}.
: &(((cond ? c : d))))); // expected-warning {{local variable 'd' does not live long enough}} expected-warning {{local variable 'c' does not live long enough}}.
} // expected-note {{local variable 'd' is destroyed here}} expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'c' is destroyed here}} expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note 4 {{later used here}}
+ use(*p); // expected-note 4 {{later used here}}
}
@@ -1537,7 +1535,7 @@ void bit_cast_use_after_scope() {
int local = 0;
p = __builtin_bit_cast(int *, &local); // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
int **bit_cast_multilevel() {
@@ -1584,7 +1582,7 @@ void foo() {
StatusOr<View> view_or = getViewOr();
view = view_or.value();
}
- (void)view;
+ use(view);
}
void bar() {
@@ -1593,7 +1591,7 @@ void bar() {
StatusOr<MyObj*> pointer_or = getPointerOr();
pointer = pointer_or.value();
}
- (void)*pointer;
+ use(*pointer);
}
void foobar() {
@@ -1604,7 +1602,7 @@ void foobar() {
// expected-note {{result of call to 'value' aliases the storage of local variable 'string_or' because the implicit object parameter is marked as lifetimebound}}
value();
} // expected-note {{local variable 'string_or' is destroyed here}}
- (void)view; // expected-note {{later used here}}
+ use(view); // expected-note {{later used here}}
}
} // namespace GH162834
@@ -1679,7 +1677,7 @@ void test_user_defined_deref_uaf() {
p = &(*smart_ptr); // expected-warning {{local variable 'smart_ptr' does not live long enough}} \
// expected-note {{result of call to 'operator*' aliases the storage of local variable 'smart_ptr' because the implicit object parameter is marked as lifetimebound}}
} // expected-note {{local variable 'smart_ptr' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
MyObj& test_user_defined_deref_uar() {
@@ -1708,7 +1706,7 @@ void test_user_defined_deref_arrow() {
p = smart_ptr.operator->(); // expected-warning {{local variable 'smart_ptr' does not live long enough}} \
// expected-note {{result of call to 'operator->' aliases the storage of local variable 'smart_ptr' because the implicit object parameter is marked as lifetimebound}}
} // expected-note {{local variable 'smart_ptr' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void test_user_defined_deref_chained() {
@@ -1719,7 +1717,7 @@ void test_user_defined_deref_chained() {
p = &(**double_ptr); // expected-warning {{local variable 'double_ptr' does not live long enough}} \
// expected-note 2 {{result of call to 'operator*' aliases the storage of local variable 'double_ptr' because the implicit object parameter is marked as lifetimebound}}
} // expected-note {{local variable 'double_ptr' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
} // namespace UserDefinedDereference
@@ -1871,7 +1869,7 @@ void strict_warn_on_move() {
v = a; // expected-warning {{local variable 'a' may not live long enough. This could be a false positive as the storage may have been moved later}}
b = std::move(a); // expected-note {{potentially moved here}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
void flow_sensitive(bool c) {
@@ -1884,7 +1882,7 @@ void flow_sensitive(bool c) {
}
v = a; // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
void take(MyObj&&);
@@ -1896,7 +1894,7 @@ void detect_conditional(bool cond) {
// expected-warning {{local variable 'b' may not live long enough. This could be a false positive as the storage may have been moved later}}
take(std::move(cond ? a : b)); // expected-note 2 {{potentially moved here}}
} // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}}
- (void)v; // expected-note 2 {{later used here}}
+ use(v); // expected-note 2 {{later used here}}
}
void wrong_use_of_move_is_permissive() {
@@ -1906,7 +1904,7 @@ void wrong_use_of_move_is_permissive() {
v = std::move(a); // expected-warning {{local variable 'a' does not live long enough}} \
// expected-note {{result of call to 'move<MyObj &>' aliases the storage of local variable 'a' because parameter 't' is inferred as lifetimebound}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
const int* p;
{
MyObj a;
@@ -1914,7 +1912,7 @@ void wrong_use_of_move_is_permissive() {
// expected-note {{result of call to 'move<MyObj &>' aliases the storage of local variable 'a' because parameter 't' is inferred as lifetimebound}} \
// expected-note {{result of call to 'getData' aliases the storage of local variable 'a' because the implicit object parameter is marked as lifetimebound}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)p; // expected-note {{later used here}}
+ use(p); // expected-note {{later used here}}
}
void take(int*);
@@ -1927,7 +1925,7 @@ void test_release_no_uaf() {
// expected-note {{result of call to 'get' aliases the storage of local variable 'p' because the implicit object parameter is inferred as lifetimebound}}
take(p.release()); // expected-note {{potentially moved here}}
} // expected-note {{local variable 'p' is destroyed here}}
- (void)*r; // expected-note {{later used here}}
+ use(*r); // expected-note {{later used here}}
}
} // namespace strict_warn_on_move
@@ -1951,9 +1949,9 @@ void bar() {
View y = S().x(); // expected-warning {{temporary object does not live long enough}} \
expected-note {{temporary object is destroyed here}} \
expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked as lifetimebound}}
- (void)y; // expected-note {{used here}}
+ use(y); // expected-note {{used here}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)x; // expected-note {{used here}}
+ use(x); // expected-note {{used here}}
}
}
@@ -2040,12 +2038,12 @@ const S& identity(const S& in [[clang::lifetimebound]]);
void test_temporary() {
const std::string& x = S().x(); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \
// expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked as lifetimebound}}
- (void)x; // expected-note {{later used here}}
+ use(x); // expected-note {{later used here}}
const std::string& y = identity(S().x()); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \
// expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked as lifetimebound}} \
// expected-note {{result of call to 'identity' aliases the storage of temporary object because parameter 'in' is marked as lifetimebound}}
- (void)y; // expected-note {{later used here}}
+ use(y); // expected-note {{later used here}}
std::string_view z;
{
@@ -2054,15 +2052,15 @@ void test_temporary() {
// expected-note {{result of call to 'x' aliases the storage of local variable 's' because the implicit object parameter is marked as lifetimebound}}
z = zz; // expected-note {{result of call to 'operator basic_string_view' aliases the storage of local variable 's'}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)z; // expected-note {{later used here}}
+ use(z); // expected-note {{later used here}}
}
void test_lifetime_extension_ok() {
const S& x = S();
- (void)x;
+ use(x);
const S& y = identity(S()); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \
// expected-note {{result of call to 'identity' aliases the storage of temporary object because parameter 'in' is marked as lifetimebound}}
- (void)y; // expected-note {{later used here}}
+ use(y); // expected-note {{later used here}}
}
const std::string& test_return() {
@@ -2087,7 +2085,7 @@ void uaf() {
view = p->s; // expected-note {{local variable 'p' aliases the storage of local variable 'str'}} \
// expected-note {{result of call to 'operator basic_string_view' aliases the storage of local variable 'str' because the implicit object parameter is inferred as lifetimebound}}
} // expected-note {{local variable 'str' is destroyed here}}
- (void)view; // expected-note {{later used here}}
+ use(view); // expected-note {{later used here}}
}
void not_uaf() {
@@ -2097,7 +2095,7 @@ void not_uaf() {
S* p = &str;
view = p->sv;
}
- (void)view;
+ use(view);
}
union U {
@@ -2114,7 +2112,7 @@ void uaf_union() {
view = up->s; // expected-note {{local variable 'up' aliases the storage of local variable 'u'}} \
// expected-note {{result of call to 'operator basic_string_view' aliases the storage of local variable 'u' because the implicit object parameter is inferred as lifetimebound}}
} // expected-note {{local variable 'u' is destroyed here}}
- (void)view; // expected-note {{later used here}}
+ use(view); // expected-note {{later used here}}
}
struct AnonymousUnion {
@@ -2131,7 +2129,7 @@ void uaf_anonymous_union() {
AnonymousUnion* up = &au; // expected-warning {{local variable 'au' does not live long enough}}
ip = &up->x; // expected-note {{local variable 'up' aliases the storage of local variable 'au'}}
} // expected-note {{local variable 'au' is destroyed here}}
- (void)ip; // expected-note {{later used here}}
+ use(ip); // expected-note {{later used here}}
}
struct RefMember {
@@ -2167,7 +2165,7 @@ void via_dot_star() {
int S::*pm = &S::x;
p = &(s.*pm); // expected-warning {{local variable 's' does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void via_arrow_star() {
@@ -2178,7 +2176,7 @@ void via_arrow_star() {
S *sp = &s; // expected-warning {{local variable 's' does not live long enough}}
p = &(sp->*pm); // expected-note {{local variable 'sp' aliases the storage of local variable 's'}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
// Negative: a long-lived object borrowed through `.*` stays silent.
@@ -2186,7 +2184,7 @@ void via_dot_star_ok() {
static S s{5};
int S::*pm = &S::x;
const int *p = &(s.*pm);
- (void)*p; // no-warning
+ use(*p); // no-warning
}
// A pointer/view member makes `obj.*pm` an origin one level deeper than the
@@ -2200,7 +2198,7 @@ void via_dot_star_view_member() {
std::string_view V::*pm = &V::view;
p = &(v.*pm); // expected-warning {{local variable 'v' does not live long enough}}
} // expected-note {{local variable 'v' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
// A pointer-to-member-function result is only callable (not storable), so it
@@ -2253,9 +2251,9 @@ void test() {
// tu-note {{result of call to 'memberB' aliases the storage of temporary object because parameter 'x' is inferred as lifetimebound}}
const MyObj* pTMC = mtf.memberC(MyObj()); // expected-warning {{temporary object does not live long enough}} // expected-note {{temporary object is destroyed here}} \
// expected-note {{result of call to 'memberC' aliases the storage of temporary object because parameter 'x' is marked as lifetimebound}}
- (void)pTMA; // expected-note {{later used here}}
- (void)pTMB; // tu-note {{later used here}}
- (void)pTMC; // expected-note {{later used here}}
+ use(pTMA); // expected-note {{later used here}}
+ use(pTMB); // tu-note {{later used here}}
+ use(pTMC); // expected-note {{later used here}}
}
} // namespace attr_on_template_params
@@ -2291,7 +2289,7 @@ void test_optional_arrow() {
// expected-note {{result of call to 'operator->' aliases the storage of local variable 'opt' because the implicit object parameter is inferred as lifetimebound}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 'opt' because the implicit object parameter is inferred as lifetimebound}}
} // expected-note {{local variable 'opt' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void test_optional_arrow_lifetimebound() {
@@ -2313,7 +2311,7 @@ void test_unique_ptr_arrow() {
// expected-note {{result of call to 'operator->' aliases the storage of local variable 'up' because the implicit object parameter is inferred as lifetimebound}} \
// expected-note {{result of call to 'data' aliases the storage of local variable 'up' because the implicit object parameter is inferred as lifetimebound}}
} // expected-note {{local variable 'up' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void test_optional_view_arrow() {
@@ -2322,7 +2320,7 @@ void test_optional_view_arrow() {
std::optional<std::string_view> opt;
p = opt->data();
}
- (void)*p;
+ use(*p);
}
} // namespace OwnerArrowOperator
@@ -2520,7 +2518,7 @@ void conditional_assignment_in_loop() {
if (i > 5) {
view = &obj;
}
- (void)*view;
+ use(*view);
}
}
@@ -2528,7 +2526,7 @@ void unconditional_assignment_in_loop() {
for (int i = 0; i < 10; ++i) {
MyObj obj;
MyObj* view = &obj;
- (void)*view;
+ use(*view);
}
}
@@ -2543,7 +2541,7 @@ void multi_level_pointer_in_loop() {
p = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
pp = &p;
}
- (void)**pp; // expected-note {{later used here}}
+ use(**pp); // expected-note {{later used here}}
} // expected-note {{local variable 'obj' is destroyed here}}
}
@@ -2554,7 +2552,7 @@ void outer_pointer_outlives_inner_pointee() {
MyObj obj;
view = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)*view; // expected-note {{later used here}}
+ use(*view); // expected-note {{later used here}}
}
} // namespace LoopLocalPointers
@@ -2567,7 +2565,7 @@ void element_use_after_scope() {
int a[10]{};
p = &a[2]; // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
int* element_use_after_return() {
@@ -2579,7 +2577,7 @@ int* element_use_after_return() {
void element_use_same_scope() {
int a[10]{};
int* p = &a[0];
- (void)*p;
+ use(*p);
}
void element_reassigned_safe() {
@@ -2590,7 +2588,7 @@ void element_reassigned_safe() {
p = &a[0];
}
p = &safe[0]; // Rescued.
- (void)*p;
+ use(*p);
}
void multidimensional_use_after_scope() {
@@ -2599,7 +2597,7 @@ void multidimensional_use_after_scope() {
int a[3][4]{};
p = &a[1][2]; // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void member_array_element_use_after_scope() {
@@ -2612,7 +2610,7 @@ void member_array_element_use_after_scope() {
S s;
p = &s.arr[0]; // expected-warning {{local variable 's' does not live long enough}}
} // expected-note {{local variable 's' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void array_of_pointers_use_after_scope() {
@@ -2621,7 +2619,7 @@ void array_of_pointers_use_after_scope() {
int* a[10]{};
p = a; // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void reversed_subscript_use_after_scope() {
@@ -2630,7 +2628,7 @@ void reversed_subscript_use_after_scope() {
int a[10]{};
p = &(0[a]); // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
int* return_decayed_array() {
@@ -2658,9 +2656,9 @@ void pointer_arithmetic_use_after_scope() {
p2 = a - 5; // expected-warning {{local variable 'a' does not live long enough}}
p3 = 5 + a; // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note 3 {{local variable 'a' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
- (void)*p2; // expected-note {{later used here}}
- (void)*p3; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
+ use(*p2); // expected-note {{later used here}}
+ use(*p3); // expected-note {{later used here}}
}
// FIXME: Copying a pointer value out of an array element is not tracked.
@@ -2671,7 +2669,7 @@ void copy_pointer_from_array_use_after_scope() {
int* arr[10] = {&x};
q = arr[0];
}
- (void)*q; // Should warn.
+ use(*q); // Should warn.
}
// FIXME: A pointer inside an array becoming dangling is not detected.
@@ -2681,7 +2679,7 @@ void pointer_in_array_use_after_scope() {
int x = 0;
arr[0] = &x;
}
- (void)*arr[0]; // Should warn.
+ use(*arr[0]); // Should warn.
}
} // namespace array
@@ -2706,14 +2704,13 @@ void indexing_with_static_operator() {
2, // expected-warning {{temporary object does not live long enough}}
3); // expected-warning {{temporary object does not live long enough}} expected-note 2 {{temporary object is destroyed here}}
- (void)x; // expected-note 2 {{later used here}}
+ use(x); // expected-note 2 {{later used here}}
}
} // namespace static_call_operator
namespace track_origins_for_lifetimebound_record_type {
-template <class T> void use(T);
struct S {
S();
@@ -2998,7 +2995,7 @@ void owner_return_unique_ptr_s() {
auto ptr = getUniqueS(std::string("temp")); // expected-warning {{temporary object does not live long enough}} \
// expected-note {{temporary object is destroyed here}} \
// expected-note {{result of call to 'getUniqueS' aliases the storage of temporary object because parameter 's' is marked as lifetimebound}}
- (void)ptr; // expected-note {{later used here}}
+ use(ptr); // expected-note {{later used here}}
}
std::string_view return_dangling_view_through_owner() {
@@ -3016,7 +3013,7 @@ void owner_outlives_lifetimebound_source() {
ups = getUniqueS(local); // expected-warning {{local variable 'local' does not live long enough}} \
// expected-note {{result of call to 'getUniqueS' aliases the storage of local variable 'local' because parameter 's' is marked as lifetimebound}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)ups; // expected-note {{later used here}}
+ use(ups); // expected-note {{later used here}}
}
} // namespace track_origins_for_lifetimebound_record_type
@@ -3031,7 +3028,6 @@ template <typename T> struct [[gsl::Pointer]] Pointer {
const T &operator*() const [[clang::lifetimebound]];
};
-template <typename T> void use(T);
void local_pointer() {
Pointer<int> p;
@@ -3072,13 +3068,13 @@ namespace conditional_operator_control_flow {
#ifdef __cpp_exceptions
void throw_branches(bool cond, int *value) {
- (void)(cond ? throw 1 : value);
+ use((cond ? throw 1 : value));
(void)(cond ? throw 1 : throw 2);
}
void nested_throw_branches(bool cond, bool cond2, int *value) {
- (void)(cond ? (cond2 ? throw 1 : value) : throw 2);
- (void)(cond ? throw 1 : (cond2 ? value : throw 2));
+ use((cond ? (cond2 ? throw 1 : value) : throw 2));
+ use((cond ? throw 1 : (cond2 ? value : throw 2)));
}
// A `throw` arm of a binary conditional `a ?: b` carries no origins; flowing it
@@ -3155,45 +3151,45 @@ void new_view_from_dead_scope() {
void new_int_basic() {
int *p = new int; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void new_int_parens() {
int *p = new int(); // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void new_int_braces() {
int *p = new int{}; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void new_int_aligned() {
int *p = new (std::align_val_t(sizeof(int))) int{}; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void new_int_nothrow() {
int *p = new (std::nothrow) int{}; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void new_int_aligned_nothrow() {
int *p = new (std::align_val_t(sizeof(int)), std::nothrow) int{}; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void conditional_delete(bool cond) {
int *p1 = new int; // expected-warning {{allocated object does not live long enough}}
int *p2 = new int; // expected-warning {{allocated object does not live long enough}}
delete (cond ? p1 : p2); // expected-note 2 {{allocated object is freed here}}
- (void)*p1; // expected-note {{later used here}}
- (void)*p2; // expected-note {{later used here}}
+ use(*p1); // expected-note {{later used here}}
+ use(*p2); // expected-note {{later used here}}
}
int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
@@ -3202,8 +3198,8 @@ void delete_returned_from_call() {
int* x = new int(1); // expected-warning {{allocated object does not live long enough}}
int* y = new int(2); // expected-warning {{allocated object does not live long enough}}
delete foo(x, y); // expected-note 2 {{allocated object is freed here}}
- (void)x; // expected-note {{later used here}}
- (void)y; // expected-note {{later used here}}
+ use(x); // expected-note {{later used here}}
+ use(y); // expected-note {{later used here}}
}
void new_pointer_from_pointer() {
@@ -3213,7 +3209,7 @@ void new_pointer_from_pointer() {
MyObj *q = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
p = new MyObj *(q); // expected-note {{local variable 'q' aliases the storage of local variable 'obj'}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)**p; // expected-note {{later used here}}
+ use(**p); // expected-note {{later used here}}
}
void new_pointer_from_dead_object() {
@@ -3222,7 +3218,7 @@ void new_pointer_from_dead_object() {
MyObj obj;
p = new MyObj *(&obj); // expected-warning {{local variable 'obj' does not live long enough}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)**p; // expected-note {{later used here}}
+ use(**p); // expected-note {{later used here}}
}
struct MultiView {
@@ -3236,25 +3232,25 @@ void new_multiview_from_mixed_scope() {
MyObj obj2;
p = new MultiView(obj1, obj2); // expected-warning {{local variable 'obj2' does not live long enough}}
} // expected-note {{local variable 'obj2' is destroyed here}}
- (void)p; // expected-note {{later used here}}
+ use(p); // expected-note {{later used here}}
}
void new_array_basic() {
int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
delete[] p; // expected-note {{allocated object is freed here}}
- (void)p[0]; // expected-note {{later used here}}
+ use(p[0]); // expected-note {{later used here}}
}
void new_array_parens() {
int *p = new int[2](); // expected-warning {{allocated object does not live long enough}}
delete[] p; // expected-note {{allocated object is freed here}}
- (void)p[0]; // expected-note {{later used here}}
+ use(p[0]); // expected-note {{later used here}}
}
void new_array_braces() {
int *p = new int[2]{}; // expected-warning {{allocated object does not live long enough}}
delete[] p; // expected-note {{allocated object is freed here}}
- (void)p[0]; // expected-note {{later used here}}
+ use(p[0]); // expected-note {{later used here}}
}
// FIXME: https://github.com/llvm/llvm-project/issues/187471
@@ -3264,8 +3260,8 @@ void new_pointer_array_from_dead_objects() {
MyObj a, b;
arr = new MyObj *[2]{&a, &b};
}
- (void)arr[0]->id;
- (void)arr[1]->id;
+ use(arr[0]->id);
+ use(arr[1]->id);
}
struct PointerArrayFieldHolder {
@@ -3279,7 +3275,7 @@ void pointer_array_field_sensitivity() {
MyObj a, b;
h.Ptrs = new MyObj *[2]{&a, &b};
}
- (void)h.Ptrs[0]->id;
+ use(h.Ptrs[0]->id);
}
//===----------------------------------------------------------------------===//
@@ -3289,26 +3285,26 @@ void pointer_array_field_sensitivity() {
void delete_direct_use_after_free() {
MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)p->id; // expected-note {{later used here}}
+ use(p->id); // expected-note {{later used here}}
}
void delete_alias_use_after_free() {
MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
MyObj *q = p; // expected-note {{local variable 'p' aliases the storage of allocated object}}
delete p; // expected-note {{allocated object is freed here}}
- (void)q->id; // expected-note {{later used here}}
+ use(q->id); // expected-note {{later used here}}
}
void delete_pointer_propagation_use_after_free() {
MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
MyObj **pp = &p;
delete p; // expected-note {{allocated object is freed here}}
- (void)(*pp)->id; // expected-note {{later used here}}
+ use((*pp)->id); // expected-note {{later used here}}
}
void delete_param_pointer(int* x) { // expected-warning {{parameter 'x' does not live long enough}}
delete x; // expected-note {{parameter 'x' is freed here}}
- (void)x; // expected-note {{later used here}}
+ use(x); // expected-note {{later used here}}
}
// FIXME: false-negative
@@ -3316,14 +3312,14 @@ struct S {
int *x;
void foo() {
delete x;
- (void)x;
+ use(x);
}
};
void use_inner_origin_after_delete(MyObj* obj) { // expected-warning {{parameter 'obj' does not live long enough}}
int* p = &obj->id;
delete obj; // expected-note {{parameter 'obj' is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void delete_nullptr_no_warning() {
@@ -3344,7 +3340,7 @@ struct ClassSpecificDelete {
void class_specific_operator_delete_use_after_free() {
ClassSpecificDelete *p = new ClassSpecificDelete; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)p->X; // expected-note {{later used here}}
+ use(p->X); // expected-note {{later used here}}
}
struct ClassSpecificNew {
@@ -3356,7 +3352,7 @@ struct ClassSpecificNew {
void class_specific_operator_new_use_after_free() {
ClassSpecificNew *p = new ClassSpecificNew; // expected-warning {{allocated object does not live long enough}}
delete p; // expected-note {{allocated object is freed here}}
- (void)p->X; // expected-note {{later used here}}
+ use(p->X); // expected-note {{later used here}}
}
struct PointerFieldHolder {
@@ -3367,21 +3363,21 @@ struct PointerFieldHolder {
void delete_through_pointer_field() {
PointerFieldHolder h{new MyObj};
delete h.Ptr;
- (void)h.Ptr->id;
+ use(h.Ptr->id);
}
void delete_stack_object() {
MyObj obj;
MyObj* p = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
delete &obj; // expected-note {{local variable 'obj' is freed here}}
- (void)p->id; // expected-note {{later used here}}
+ use(p->id); // expected-note {{later used here}}
}
void delete_stack_object_int() {
int obj;
int* p = &obj; // expected-warning {{local variable 'obj' does not live long enough}}
delete &obj; // expected-note {{local variable 'obj' is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void allocate_void_ptr() {
@@ -3399,7 +3395,7 @@ void placement_new_int_basic() {
int storage;
p = new (&storage) int; // expected-warning {{local variable 'storage' does not live long enough}}
} // expected-note {{local variable 'storage' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
void placement_new_view_from_dead_scope() {
@@ -3419,7 +3415,7 @@ void placement_new_pointer_from_dead_object() {
MyObj obj;
p = new (&slot) MyObj *(&obj); // expected-warning {{local variable 'obj' does not live long enough}}
} // expected-note {{local variable 'obj' is destroyed here}}
- (void)**p; // expected-note {{later used here}}
+ use(**p); // expected-note {{later used here}}
}
void placement_new_array_basic() {
@@ -3428,7 +3424,7 @@ void placement_new_array_basic() {
int storage[2];
p = new (&storage) int[2]; // expected-warning {{local variable 'storage' does not live long enough}}
} // expected-note {{local variable 'storage' is destroyed here}}
- (void)p[0]; // expected-note {{later used here}}
+ use(p[0]); // expected-note {{later used here}}
}
void placement_new_array_braces() {
@@ -3437,14 +3433,14 @@ void placement_new_array_braces() {
int storage[2];
p = new (&storage) int[2]{}; // expected-warning {{local variable 'storage' does not live long enough}}
} // expected-note {{local variable 'storage' is destroyed here}}
- (void)p[0]; // expected-note {{later used here}}
+ use(p[0]); // expected-note {{later used here}}
}
void placement_new_heap_then_delete_use_after_free() {
int *storage = new int(7); // expected-warning {{allocated object does not live long enough}}
int *p = new (storage) int(42); // expected-note {{local variable 'storage' aliases the storage of allocated object}}
delete storage; // expected-note {{allocated object is freed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
struct PlacementArg {};
@@ -3455,7 +3451,7 @@ struct VariadicPlacementNew {
void variadic_placement_new() {
PlacementArg arg;
- (void)new (arg) VariadicPlacementNew;
+ use(new (arg) VariadicPlacementNew);
}
struct Arena {};
@@ -3475,13 +3471,13 @@ struct SingleArgCustomPlacementNew {
void custom_placement_new_not_heap(Arena &A) {
CustomPlacementNew *p = new (A, 0) CustomPlacementNew;
delete p;
- (void)p->X;
+ use(p->X);
}
void single_arg_custom_placement_new_not_heap(Arena &A) {
SingleArgCustomPlacementNew *p = new (A) SingleArgCustomPlacementNew;
delete p;
- (void)p->X;
+ use(p->X);
}
int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
@@ -3494,7 +3490,7 @@ void placement_new_delete_result_of_lifetimebound_call() {
// expected-note {{local variable 'y' aliases the storage of allocated object}} \
// expected-note 2 {{result of call to 'foo' aliases the storage of allocated object}}
delete foo(x, y); // expected-note 2 {{allocated object is freed here}}
- (void)**p; // expected-note 2 {{later used here}}
+ use(**p); // expected-note 2 {{later used here}}
}
@@ -3510,7 +3506,7 @@ void placement_new_pointer_field_use_after_scope() {
MyObj obj;
p = new (&h) PointerFieldHolder{&obj};
}
- (void)p->Ptr->id;
+ use(p->Ptr->id);
}
} // namespace placement_new
@@ -3628,7 +3624,7 @@ void placement_new_direct_array_use_after_placement() {
std::string* str1 = new (storage) std::string{"Old"};
auto p1 = str1->c_str();
new (storage) std::string{"New"};
- (void)*p1;
+ use(*p1);
}
} // namespace placement_new_argument
@@ -3811,7 +3807,7 @@ void uaf_via_lifetimebound() {
f = capture_lifetimebound_param(local); // expected-warning {{local variable 'local' does not live long enough}} \
// expected-note {{result of call to 'capture_lifetimebound_param' aliases the storage of local variable 'local' because parameter 'x' is marked as lifetimebound}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)f; // expected-note {{later used here}}
+ use(f); // expected-note {{later used here}}
}
} // namespace callable_wrappers
@@ -3830,7 +3826,7 @@ struct [[gsl::Pointer]] function_ref {
void assign_non_capturing_to_function_ref(function_ref &r) {
r = []() {}; // expected-warning {{temporary object does not live long enough}} \
// expected-note {{temporary object is destroyed here}}
- (void)r; // expected-note {{later used here}}
+ use(r); // expected-note {{later used here}}
}
} // namespace GH126600
@@ -3875,7 +3871,7 @@ void deref_use_after_scope() {
p = &*opt; // expected-warning {{local variable 'opt' does not live long enough}} \
// expected-note {{result of call to 'operator*' aliases the storage of local variable 'opt' because the implicit object parameter is inferred as lifetimebound}}
} // expected-note {{local variable 'opt' is destroyed here}}
- (void)p->id; // expected-note {{later used here}}
+ use(p->id); // expected-note {{later used here}}
}
} // namespace GH188832
@@ -3927,7 +3923,7 @@ void use_after_free_capture_by() {
MyObj a;
setCaptureBy(res, a); // expected-warning {{local variable 'a' does not live long enough}}
} // expected-note {{local variable 'a' is destroyed here}}
- (void)res; // expected-note {{later used here}}
+ use(res); // expected-note {{later used here}}
}
View use_after_return_capture_by() {
@@ -3945,7 +3941,7 @@ void transitive_capture() {
setCaptureBy(v1, local); // expected-warning {{local variable 'local' does not live long enough}}
setCaptureBy(v2, v1); // expected-note {{local variable 'v1' aliases the storage of local variable 'local'}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)v2; // expected-note {{later used here}}
+ use(v2); // expected-note {{later used here}}
}
void set1(View& res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
@@ -3956,7 +3952,7 @@ void test_reference_to_view() {
MyObj local;
set1(v, local); // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)v; // expected-note {{later used here}}
+ use(v); // expected-note {{later used here}}
}
// FIXME: Add special handling for multi-level pointers and lvalue expressions which are not DeclRefExpr.
@@ -3968,7 +3964,7 @@ void test_pointer_to_pointer() {
MyObj local;
set2(&ptr, local);
}
- (void)ptr;
+ use(ptr);
}
void test_pointer_to_pointer_2(MyObj **ptr) {
@@ -3976,7 +3972,7 @@ void test_pointer_to_pointer_2(MyObj **ptr) {
MyObj local;
set2(ptr, local);
}
- (void)ptr;
+ use(ptr);
}
void set3(MyObj*& res, const MyObj& in [[clang::lifetime_capture_by(res)]]);
@@ -3987,7 +3983,7 @@ void test_reference_to_pointer() {
MyObj local;
set3(ptr, local); // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)ptr; // expected-note {{later used here}}
+ use(ptr); // expected-note {{later used here}}
}
struct [[gsl::Pointer]] MyContainer {
@@ -4001,7 +3997,7 @@ void member_capture() {
MyObj local;
c.set(local); // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)c.stored; // expected-note {{later used here}}
+ use(c.stored); // expected-note {{later used here}}
}
struct SimpleContainer {
@@ -4015,7 +4011,7 @@ void member_capture_simple_container() {
MyObj local;
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}}
+ use(c.stored); // expected-note {{later used here}}
}
void captureTwo(View& into,
@@ -4029,7 +4025,7 @@ void multiple_captures() {
MyObj val2;
captureTwo(res, val1, val2); // expected-warning {{local variable 'val2' does not live long enough}}
} // expected-note {{local variable 'val2' is destroyed here}}
- (void)res; // expected-note {{later used here}}
+ use(res); // expected-note {{later used here}}
}
void multiple_local_captures() {
@@ -4039,7 +4035,7 @@ void multiple_local_captures() {
MyObj val2;
captureTwo(res, val1, val2); // expected-warning {{local variable 'val1' does not live long enough}} // expected-warning {{local variable 'val2' does not live long enough}}
} // expected-note {{local variable 'val2' is destroyed here}} expected-note {{local variable 'val1' is destroyed here}}
- (void)res; // expected-note 2 {{later used here}}
+ use(res); // expected-note 2 {{later used here}}
}
void captureIntoTwo(View& v1, View& v2,
@@ -4051,7 +4047,7 @@ void captured_by_multiple_params() {
MyObj local;
captureIntoTwo(v1, v2, local); // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)v1; // expected-note {{later used here}}
+ use(v1); // expected-note {{later used here}}
}
void captured_by_multiple_params_2() {
@@ -4060,7 +4056,7 @@ void captured_by_multiple_params_2() {
MyObj local;
captureIntoTwo(v1, v2, local); // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{local variable 'local' is destroyed here}}
- (void)v2; // expected-note {{later used here}}
+ use(v2); // expected-note {{later used here}}
}
void capturing_multiple_locals() {
@@ -4071,7 +4067,7 @@ void capturing_multiple_locals() {
MyObj local2;
setCaptureBy(v, local2); // expected-warning{{local variable 'local2' does not live long enough}}
} // expected-note {{local variable 'local1' is destroyed here}} expected-note {{local variable 'local2' is destroyed here}}
- (void)v; // expected-note 2 {{later used here}}
+ use(v); // expected-note 2 {{later used here}}
}
struct [[gsl::Pointer()]] PtrWithInt { int x; };
@@ -4084,7 +4080,6 @@ PtrWithInt f() {
// a borrow `e` carries is tracked: a borrow of a body-local dangles, and a
// borrow forwarded from an outer object propagates to the value's users.
namespace statement_expression {
-void use(int *p);
// A borrow of a statement-expression-local escaping via the value.
void borrow_of_local() {
@@ -4102,7 +4097,7 @@ int *return_borrow_of_local() {
// A view bound to a temporary produced by the statement expression dangles.
void borrow_temporary() {
std::string_view view = ({ std::string x = "long enough heap string!!!!!!"; x; }); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}}
- (void)view; // expected-note {{later used here}}
+ use(view); // expected-note {{later used here}}
}
// Forwarding an outer borrow that dangles.
@@ -4295,7 +4290,7 @@ void doubleFree() {
// This is a double-free due to multiple ownership which is currently not supported.
up = takeOwnership(&a);
}
- (void)up.get();
+ use(up.get());
}
void ok() {
@@ -4304,7 +4299,7 @@ void ok() {
int* a = new int(42);
up = takeOwnership(a); // Ok.
}
- (void)up.get();
+ use(up.get());
}
void take(std::unique_ptr<int> o);
@@ -4318,6 +4313,6 @@ void foo() {
// expected-note {{result of call to 'get' aliases the storage of local variable 'o' because the implicit object parameter is inferred as lifetimebound}}
up = std::move(o); // expected-note {{potentially moved here}}
} // expected-note {{local variable 'o' is destroyed here}}
- (void)*p; // expected-note {{later used here}}
+ use(*p); // expected-note {{later used here}}
}
} // namespace TakeOwnershipTests
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 759e2d3ba01b7b..dcaa31d62b3c74 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -40,6 +40,9 @@ class LifetimeTestRunner {
View(const MyObj&);
View();
};
+
+ // `(void)v` is not a use in C++: no lvalue-to-rvalue conversion.
+ template <typename... Ts> void use(const Ts &...vs);
)";
FullCode += Code.str();
@@ -1332,7 +1335,7 @@ TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf) {
p = a;
}
POINT(p4);
- (void)p;
+ use(p);
POINT(p5);
}
}
@@ -1364,8 +1367,8 @@ TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf2) {
}
POINT(p5);
- (void)*p;
- (void)*q;
+ use(*p);
+ use(*q);
POINT(p6);
}
}
@@ -1397,7 +1400,7 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
POINT(p1);
}
POINT(p2);
- (void)*p;
+ use(*p);
}
)");
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
@@ -1413,7 +1416,7 @@ TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) {
ptr = &s;
}
POINT(p1);
- (void)*ptr;
+ use(*ptr);
}
)");
EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
@@ -1433,7 +1436,7 @@ TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAF) {
ptr = &s;
}
POINT(p1);
- (void)ptr;
+ use(ptr);
}
)");
EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
@@ -2005,7 +2008,7 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChain) {
}
POINT(after_nested_merge);
- (void)*s;
+ use(*s);
int reset;
s = &reset;
}
>From 0df205c977e5dbff9bfe331905ab79e659dfa6ab Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Wed, 23 Sep 2026 15:13:34 +0100
Subject: [PATCH 2/5] [LifetimeSafety] Treat values handed to opaque code as
uses
Arguments to indirect calls (through a function pointer or pointer to member),
`throw` operands, asm inputs and in-out outputs, `typeid` and `dynamic_cast`
operands, `delete` and placement `new` arguments, and lambda captures may all be
dereferenced by code the analysis cannot see. Record them as uses explicitly.
These are currently still caught because every DeclRefExpr counts as a use, so
diagnostics do not change. This prepares for removing that rule.
Assisted by: Opus 5.5
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 3 +
.../LifetimeSafety/FactsGenerator.cpp | 40 +++++-
.../LifetimeSafety/LifetimeAnnotations.cpp | 5 +-
clang/test/Sema/LifetimeSafety/safety.cpp | 125 ++++++++++++++++++
4 files changed, 162 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index ec5027965002e4..7aea673c2a18f4 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -57,6 +57,9 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE);
void VisitCXXNewExpr(const CXXNewExpr *NE);
void VisitCXXDeleteExpr(const CXXDeleteExpr *DE);
+ void VisitCXXThrowExpr(const CXXThrowExpr *TE);
+ void VisitGCCAsmStmt(const GCCAsmStmt *AS);
+ void VisitCXXTypeidExpr(const CXXTypeidExpr *TE);
void VisitStmtExpr(const StmtExpr *SE);
private:
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 98760dba188821..b5e9f73d900a05 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -288,10 +288,12 @@ void FactsGenerator::VisitCXXNullPtrLiteralExpr(
}
void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
+ const Expr *SubExpr = CE->getSubExpr();
+ if (CE->getCastKind() == CK_Dynamic)
+ handleUse(SubExpr);
OriginList *Dest = getOriginsList(*CE);
if (!Dest)
return;
- const Expr *SubExpr = CE->getSubExpr();
OriginList *Src = getOriginsList(*SubExpr);
switch (CE->getCastKind()) {
@@ -702,6 +704,8 @@ void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) {
for (const Expr *Init : LE->capture_inits()) {
if (!Init)
continue;
+ // The lambda body may dereference a capture to any depth.
+ handleUse(Init);
OriginList *InitList = getOriginsList(*Init);
if (!InitList)
continue;
@@ -758,6 +762,8 @@ bool FactsGenerator::handlePlacementNew(const CXXNewExpr *NE,
// FIXME: General placement arguments need separate handling to overwrite
// the right origins.
+ handleUse(PlacementArg);
+
// The pointer returned by placement new comes from the placement
// argument.
if (PlacementList)
@@ -796,7 +802,28 @@ void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
flow(NewList, InitList, true);
}
+// TODO: An escape fact may fit `throw` and asm better than a use.
+void FactsGenerator::VisitCXXThrowExpr(const CXXThrowExpr *TE) {
+ if (const Expr *Sub = TE->getSubExpr())
+ handleUse(Sub);
+}
+
+void FactsGenerator::VisitGCCAsmStmt(const GCCAsmStmt *AS) {
+ for (const Expr *Input : AS->inputs())
+ handleUse(Input);
+ for (unsigned I = 0, N = AS->getNumOutputs(); I != N; ++I)
+ if (AS->isOutputPlusConstraint(I)) // Also read.
+ handleUse(AS->getOutputExpr(I));
+}
+
+void FactsGenerator::VisitCXXTypeidExpr(const CXXTypeidExpr *TE) {
+ if (TE->isPotentiallyEvaluated())
+ handleUse(TE->getExprOperand());
+}
+
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
+ // The destructor may dereference to any depth.
+ handleUse(DE->getArgument());
OriginList *List = getOriginsList(*DE->getArgument());
CurrentBlockFacts.push_back(
FactMgr.createFact<InvalidateOriginFact>(List->getOuterOriginID(), DE));
@@ -1083,18 +1110,17 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
void FactsGenerator::handleFunctionCall(const Expr *Call,
bool IsGslConstruction) {
FunctionCallInfo CallInfo(Call);
+ llvm::ArrayRef<const Expr *> Args = CallInfo.Args;
+ // The callee may dereference any argument to any depth.
+ for (const Expr *Arg : Args)
+ handleUse(Arg);
if (!CallInfo.FD)
return;
- const FunctionDecl *FD = CallInfo.FD;
- llvm::ArrayRef<const Expr *> Args = CallInfo.Args;
OriginList *CallList = getOriginsList(*Call);
// Ignore functions returning values with no origin.
- FD = getDeclWithMergedLifetimeBoundAttrs(FD);
+ const FunctionDecl *FD = getDeclWithMergedLifetimeBoundAttrs(CallInfo.FD);
if (!FD)
return;
- // All arguments to a function are a use of the corresponding expressions.
- for (const Expr *Arg : Args)
- handleUse(Arg);
handleInvalidatingCall(Call, FD, Args);
handleDestructiveCall(Call, FD, Args);
handleMovedArgsInCall(FD, Args);
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 98b2a5a8677522..93cff4764e2533 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -116,11 +116,8 @@ FunctionCallInfo::FunctionCallInfo(const Expr *Call) {
if (!AC)
return;
- FD = dyn_cast_or_null<FunctionDecl>(AC->getDecl());
- if (!FD)
- return;
-
Args = AC->arguments();
+ FD = dyn_cast_or_null<FunctionDecl>(AC->getDecl());
}
std::optional<LifetimeBoundParamInfo>
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index 952e9cdf052020..abc725a094bb9f 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -4316,3 +4316,128 @@ void foo() {
use(*p); // expected-note {{later used here}}
}
} // namespace TakeOwnershipTests
+
+//===----------------------------------------------------------------------===//
+// Uses by code the analysis cannot see into
+//
+// Opaque code may dereference whatever it is handed. These are uses in their
+// own right, not only because every DeclRefExpr is currently one.
+//===----------------------------------------------------------------------===//
+
+namespace std { class type_info; }
+
+namespace what_is_a_use {
+struct Node {
+ int id;
+ Node *next;
+};
+
+// Opaque code may dereference what it is handed, so every argument is a use --
+// including when there is no FunctionDecl to inspect.
+namespace opaque_callees {
+void (*g_fp)(Node *);
+struct Callable { void m(Node *); };
+
+void through_function_pointer(void (*fp)(Node *)) {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ fp(p); // expected-note {{later used here}}
+}
+
+void through_global_function_pointer() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ g_fp(p); // expected-note {{later used here}}
+}
+
+void through_pointer_to_member(Callable &c, void (Callable::*pmf)(Node *)) {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ (c.*pmf)(p); // expected-note {{later used here}}
+}
+
+// A view has no lvalue-to-rvalue conversion of its own, so the argument rule is
+// the only thing covering it.
+void view_through_function_pointer(void (*fp)(View)) {
+ View v;
+ {
+ MyObj local;
+ v = local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ fp(v); // expected-note {{later used here}}
+}
+
+#ifdef __cpp_exceptions
+void through_throw() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ throw p; // expected-note {{later used here}}
+}
+#endif
+
+void through_inline_asm() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ asm volatile("" :: "r"(p)); // expected-note {{later used here}}
+}
+
+void through_placement_new() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ new (p) Node; // expected-note {{later used here}}
+}
+
+} // namespace opaque_callees
+
+// Reads with no lvalue-to-rvalue conversion in the AST.
+namespace class_reads {
+struct Base { virtual ~Base(); };
+struct Derived : Base {};
+
+void dynamic_cast_reads_the_object() {
+ Base *b;
+ {
+ Derived local;
+ b = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ Derived *d = dynamic_cast<Derived *>(b); // expected-note {{later used here}}
+ (void)d;
+}
+
+void typeid_reads_the_object() {
+ Base *b;
+ {
+ Derived local;
+ b = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ (void)typeid(*b); // expected-note {{later used here}}
+}
+
+void asm_inout_operand_is_read() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ asm volatile("" : "+r"(p)); // expected-note {{later used here}}
+}
+} // namespace class_reads
+} // namespace what_is_a_use
>From 080e65a2571c7f8179962480708498cd1810547c Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Wed, 23 Sep 2026 15:37:46 +0100
Subject: [PATCH 3/5] [LifetimeSafety] Treat an access through an lvalue as a
use
Previously every DeclRefExpr was a use, so naming a pointer, taking its address
or copying it counted as reading what it points to. Writes were then exempted
after the fact through a UseFact -> Expr map and a special case for writes
through references.
Now a use is an access: an lvalue-to-rvalue conversion, a write through an
assignment, increment or asm output, or a copy of a class object from a glvalue.
It is recorded on the accessed lvalue's outer origin, the loans saying which
storage it may name. The visitors for `*p`, `p->f` and `p[i]` already flow the
base pointer there, so dereferences need no special handling. Reads of a value
go through readValue(), which records the access; writes peel the outer origin
directly. Values handed to opaque code still use every level.
Consequences:
- Copying a pointer, taking an address, `(void)p` and `p++` are not uses.
- Writing through a dangling pointer (`*p = 1`, `p->f = 1`) is now diagnosed.
- An access of an origin that names a declaration outright is skipped: it
only holds a loan to that declaration, which is valid wherever it is named.
- UseFact::IsWritten and markUseAsWrite are removed; kills on assignment
already come from the destination's OriginFlowFact or KillOriginFact.
Fewer use facts are generated, e.g. on the lifetime-safety lit tests:
safety.cpp 1886 -> 882
invalidations.cpp 616 -> 451
nocfg.cpp 544 -> 415
capture-by.cpp 395 -> 324
Assisted by: Opus 5.5
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 6 -
.../Analyses/LifetimeSafety/FactsGenerator.h | 21 +-
.../Analyses/LifetimeSafety/LifetimeSafety.h | 2 +
.../Analyses/LifetimeSafety/Origins.h | 4 +
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 21 +-
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 2 +-
.../LifetimeSafety/FactsGenerator.cpp | 147 +++++------
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 18 +-
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 12 +-
.../LifetimeSafety/Inputs/lifetime-analysis.h | 1 +
.../Sema/LifetimeSafety/dangling-field.cpp | 21 +-
.../Sema/LifetimeSafety/invalidations.cpp | 4 +-
.../Sema/LifetimeSafety/lifetime-facts.cpp | 75 +++++-
clang/test/Sema/LifetimeSafety/safety.cpp | 233 ++++++++++++++++--
.../unittests/Analysis/LifetimeSafetyTest.cpp | 93 ++++++-
15 files changed, 500 insertions(+), 160 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 794e0cd30b9e5f..16fe31a577fa1b 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -246,9 +246,6 @@ class GlobalEscapeFact : public OriginEscapesFact {
class UseFact : public Fact {
const Expr *UseExpr;
const OriginList *OList;
- // True if this use is a write operation (e.g., left-hand side of assignment).
- // Write operations are exempted from use-after-free checks.
- bool IsWritten = false;
public:
static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
@@ -257,10 +254,7 @@ class UseFact : public Fact {
: Fact(Kind::Use), UseExpr(UseExpr), OList(OList) {}
const OriginList *getUsedOrigins() const { return OList; }
- void setUsedOrigins(const OriginList *NewList) { OList = NewList; }
const Expr *getUseExpr() const { return UseExpr; }
- void markAsWritten() { IsWritten = true; }
- bool isWritten() const { return IsWritten; }
void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM,
const LoanPropagationAnalysis *LPA = nullptr) const override;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 7aea673c2a18f4..e9f2a4789241c0 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -141,12 +141,17 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
/// If so, creates a `TestPointFact` and returns true.
bool handleTestPoint(const CXXFunctionalCastExpr *FCE);
- // Treats an expression as a use of the referenced object. It will be
- // checked for use-after-free unless it is later marked as being written to
- // (e.g. on the left-hand side of an assignment in the case of a DeclRefExpr).
- void handleUse(const Expr *E);
+ bool namesDeclStorage(const OriginList *List) const;
+
+ OriginList *readValue(const Expr *E);
- void markUseAsWrite(const DeclRefExpr *DRE);
+ /// Records an access (read or write) of the storage \p E designates, or that
+ /// a prvalue pointer \p E points to.
+ void handleAccess(const Expr *E);
+
+ /// Records that \p E's value is handed to opaque code, which may dereference
+ /// it to any depth.
+ void handleUse(const Expr *E);
bool escapesViaReturn(OriginID OID) const;
@@ -158,12 +163,6 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
// appended at the end of CurrentBlockFacts to ensure they appear after
// ExpireFact entries.
llvm::SmallVector<Fact *> EscapesInCurrentBlock;
- // To distinguish between reads and writes for use-after-free checks, this map
- // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
- // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use
- // corresponding to the left-hand side is updated to be a "write", thereby
- // exempting it from the check.
- llvm::DenseMap<const Expr *, UseFact *> UseFacts;
const CFGBlock *CurrentBlock;
bool IsCMode = false;
};
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 68e84961010ddf..18e5e8473e4144 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -74,6 +74,8 @@ class LifetimeSafetySemaHelper {
SourceLocation FreeLoc,
llvm::ArrayRef<const Expr *> ExprChain) {}
+ // TODO: Pass the expiry location and aliasing chain like
+ // reportUseAfterScope.
virtual void reportUseAfterReturn(const Expr *IssueExpr,
const Expr *ReturnExpr,
const Expr *MovedExpr) {}
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index 6ab2f59283ad3c..b4a7ec2832d712 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -53,6 +53,10 @@ struct Origin {
/// Null for synthetic lvalue origins (e.g., outer origin of DeclRefExpr).
const Type *Ty;
+ /// True if this origin only holds a loan to a declaration named in scope, so
+ /// it can never hold an expired loan.
+ bool NamesDeclStorage = false;
+
Origin(OriginID ID, const clang::ValueDecl *D, const Type *QT)
: ID(ID), Ptr(D), Ty(QT) {}
Origin(OriginID ID, const clang::Expr *E, const Type *QT)
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index c4cc872dcd568d..a590491df23e74 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -24,6 +24,7 @@
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TimeProfiler.h"
@@ -557,14 +558,22 @@ class LifetimeChecker {
/// Given a chain of origins that shows how a loan propagates, this function
/// extracts the corresponding expressions for each origin. Origins that refer
/// to declarations (rather than expressions) are skipped.
+ ///
+ /// Until the chain reaches a declaration it is inside the use expression,
+ /// where casts just load the used variable.
llvm::SmallVector<const Expr *>
getExprChain(llvm::ArrayRef<OriginID> OriginFlowChain) {
- llvm::SmallVector<const Expr *> rs;
- for (const OriginID CurrOID : OriginFlowChain)
- if (const Expr *CurrExpr =
- FactMgr.getOriginMgr().getOrigin(CurrOID).getExpr())
- rs.push_back(CurrExpr);
- return rs;
+ llvm::SmallVector<const Expr *> Chain;
+ bool InUse = true;
+ for (const OriginID CurrOID : OriginFlowChain) {
+ const Expr *CurrExpr =
+ FactMgr.getOriginMgr().getOrigin(CurrOID).getExpr();
+ if (!CurrExpr)
+ InUse = false;
+ else if (!InUse || !isa<ImplicitCastExpr>(CurrExpr))
+ Chain.push_back(CurrExpr);
+ }
+ return Chain;
}
};
} // namespace
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index a56774327731c2..2d3c161ae8f113 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -167,7 +167,7 @@ void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
if (I < NumUsedOrigins - 1)
OS << ", ";
}
- OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
+ OS << ")\n";
}
void InvalidateOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index b5e9f73d900a05..091935df1a509e 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -153,17 +153,17 @@ void FactsGenerator::run() {
FactMgr.computePersistentOrigins(Cfg);
}
-/// Simulates LValueToRValue conversion by peeling the outer lvalue origin
-/// if the expression is a GLValue. For pointer/view GLValues, this strips
-/// the origin representing the storage location to get the origins of the
-/// pointed-to value.
+/// Returns the origins of the value \p E evaluates to, recording the read of a
+/// glvalue. Writes peel the outer origin directly instead.
///
/// Example: For `View& v`, returns the origin of what v points to, not v's
/// storage.
-static OriginList *getRValueOrigins(const Expr *E, OriginList *List) {
- if (!List)
- return nullptr;
- return E->isGLValue() ? List->peelOuterOrigin() : List;
+OriginList *FactsGenerator::readValue(const Expr *E) {
+ OriginList *List = getOriginsList(*E);
+ if (!E->isGLValue())
+ return List;
+ handleAccess(E);
+ return List ? List->peelOuterOrigin() : nullptr;
}
void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) {
@@ -184,7 +184,6 @@ void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
// GLValues (like EnumConstants).
if (DRE->getFoundDecl()->isFunctionOrFunctionTemplate() || !DRE->isGLValue())
return;
- handleUse(DRE);
// For all declarations with storage (non-references), we issue a loan
// representing the borrow of the variable's storage itself.
//
@@ -218,7 +217,7 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 &&
hasOrigins(CCE->getType())) {
const Expr *Arg = CCE->getArg(0);
- if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
+ if (OriginList *ArgList = readValue(Arg)) {
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
return;
}
@@ -228,7 +227,7 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
RD && isStdCallableWrapperType(RD) && CCE->getNumArgs() == 1) {
const Expr *Arg = CCE->getArg(0);
- if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
+ if (OriginList *ArgList = readValue(Arg)) {
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
return;
}
@@ -289,8 +288,13 @@ void FactsGenerator::VisitCXXNullPtrLiteralExpr(
void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
const Expr *SubExpr = CE->getSubExpr();
- if (CE->getCastKind() == CK_Dynamic)
- handleUse(SubExpr);
+ const CastKind Kind = CE->getCastKind();
+ const bool Loads =
+ Kind == CK_LValueToRValue || Kind == CK_LValueToRValueBitCast;
+ OriginList *Loaded = Loads ? readValue(SubExpr) : nullptr;
+ if (Kind == CK_Dynamic)
+ handleAccess(SubExpr);
+
OriginList *Dest = getOriginsList(*CE);
if (!Dest)
return;
@@ -304,10 +308,8 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
assert(Src && "LValue being cast to RValue has no origin list");
// The result of an LValue-to-RValue cast on a pointer lvalue (like `q` in
// `int *p, *q; p = q;`) should propagate the inner origin (what the pointer
- // points to), not the outer origin (the pointer's storage location). Strip
- // the outer lvalue origin.
- flow(getOriginsList(*CE), getRValueOrigins(SubExpr, Src),
- /*Kill=*/true);
+ // points to), not the outer origin (the pointer's storage location).
+ flow(Dest, Loaded, /*Kill=*/true);
return;
case CK_NullToPointer:
getOriginsList(*CE);
@@ -353,7 +355,7 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
// lvalue level first. A bit-cast that materializes a pointer from a
// non-pointer representation has no matching source origin and is
// untracked.
- OriginList *RVSrc = getRValueOrigins(SubExpr, Src);
+ OriginList *RVSrc = Loads ? Loaded : Src;
if (RVSrc && Dest->getLength() == RVSrc->getLength())
flow(Dest, RVSrc, /*Kill=*/true);
return;
@@ -395,18 +397,17 @@ void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) {
if (!UO->getType()->isPointerType())
return;
const Expr *SubExpr = UO->getSubExpr();
- flow(getOriginsList(*UO),
- getRValueOrigins(SubExpr, getOriginsList(*SubExpr)), /*Kill=*/true);
+ flow(getOriginsList(*UO), readValue(SubExpr), /*Kill=*/true);
return;
}
case UO_PreInc:
case UO_PostInc:
case UO_PreDec:
case UO_PostDec: {
+ handleAccess(UO->getSubExpr());
// Inc/dec keeps a pointer in the same allocation, so the result carries the
// operand's loans. Peel the operand's storage origin when the *result* is a
- // prvalue (post-inc/dec, or any form in C) -- the inverse of
- // getRValueOrigins, which peels when its own argument is a glvalue.
+ // prvalue (post-inc/dec, or any form in C).
if (!UO->getType()->isPointerType())
return;
OriginList *SubList = getOriginsList(*UO->getSubExpr());
@@ -432,6 +433,8 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr,
const Expr *LHSExpr,
const Expr *RHSExpr) {
LHSExpr = LHSExpr->IgnoreParenImpCasts();
+ handleAccess(LHSExpr);
+ OriginList *RHSList = readValue(RHSExpr);
OriginList *LHSList = nullptr;
if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
@@ -447,37 +450,7 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr,
}
if (!LHSList)
return;
- OriginList *RHSList = getOriginsList(*RHSExpr);
- // For operator= with reference parameters (e.g.,
- // `View& operator=(const View&)`), the RHS argument stays an lvalue,
- // unlike built-in assignment where LValueToRValue cast strips the outer
- // lvalue origin. Strip it manually to get the actual value origins being
- // assigned.
- RHSList = getRValueOrigins(RHSExpr, RHSList);
- if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
- QualType QT = DRE_LHS->getDecl()->getType();
- if (QT->isReferenceType()) {
- if (hasOrigins(QT->getPointeeType())) {
- // Writing through a reference uses the binding but overwrites the
- // pointee. Model this as a Read of the outer origin (keeping the
- // binding live) and a Write of the inner origins (killing the pointee's
- // liveness).
- if (UseFact *UF = UseFacts.lookup(DRE_LHS)) {
- const OriginList *FullList = UF->getUsedOrigins();
- assert(FullList);
- UF->setUsedOrigins(FactMgr.getOriginMgr().createSingleOriginList(
- FullList->getOuterOriginID()));
- if (const OriginList *InnerList = FullList->peelOuterOrigin()) {
- UseFact *WriteUF = FactMgr.createFact<UseFact>(DRE_LHS, InnerList);
- WriteUF->markAsWritten();
- CurrentBlockFacts.push_back(WriteUF);
- }
- }
- }
- } else
- markUseAsWrite(DRE_LHS);
- }
if (!RHSList) {
// RHS has no tracked origins (e.g., assigning a callable without origins
// to std::function). Clear loans of the destination.
@@ -494,7 +467,7 @@ void FactsGenerator::handleAssignment(const Expr *TargetExpr,
// In C, assignment expressions are not GLValues, so the assignment result has
// the assigned value origins, not the LHS storage origin.
if (IsCMode)
- LHSList = getRValueOrigins(LHSExpr, LHSList);
+ LHSList = LHSList->peelOuterOrigin();
flow(getOriginsList(*TargetExpr), LHSList, /*Kill=*/true);
}
@@ -521,14 +494,12 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
// counterpart in the object's origin -- so the lists may differ in length
// and we flow just the top level, leaving the member's value untouched.
OriginList *Dst = getOriginsList(*BO);
- OriginList *ObjSrc =
- BO->getOpcode() == BO_PtrMemD
- ? getOriginsList(*BO->getLHS())
- : getRValueOrigins(BO->getLHS(), getOriginsList(*BO->getLHS()));
+ OriginList *ObjSrc = BO->getOpcode() == BO_PtrMemD
+ ? getOriginsList(*BO->getLHS())
+ : readValue(BO->getLHS());
if (Dst && ObjSrc)
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
Dst->getOuterOriginID(), ObjSrc->getOuterOriginID(), /*Kill=*/true));
- handleUse(BO->getLHS());
return;
}
if (BO->getOpcode() == BO_Comma) {
@@ -536,6 +507,7 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
return;
}
if (BO->isCompoundAssignmentOp()) {
+ handleAccess(BO->getLHS());
// A pointer compound additive assignment (`p += n`) carries the LHS's loans
// like inc/dec above; in C the result is a prvalue, so peel its outer
// (storage) origin.
@@ -548,10 +520,9 @@ void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
}
if (BO->getType()->isPointerType() && BO->isAdditiveOp())
handlePointerArithmetic(BO);
- handleUse(BO->getRHS());
if (BO->isAssignmentOp())
handleAssignment(BO, BO->getLHS(), BO->getRHS());
- // TODO: Handle assignments involving dereference like `*p = q`.
+ // TODO: Propagate origins for assignments through a dereference (`*p = q`).
}
static const CFGBlock *findPredBlockForExpr(const CFGBlock *MergeBlock,
@@ -668,7 +639,7 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
MTEList->getLength() == (SubExprList->getLength() + 1)) &&
"MTE top level origin should contain a loan to the MTE itself");
- OriginList *RValMTEList = getRValueOrigins(MTE, MTEList);
+ OriginList *RValMTEList = MTEList->peelOuterOrigin();
flow(RValMTEList, SubExprList, /*Kill=*/true);
OriginID OuterMTEID = MTEList->getOuterOriginID();
if (MTE->getStorageDuration() == SD_FullExpression) {
@@ -762,7 +733,7 @@ bool FactsGenerator::handlePlacementNew(const CXXNewExpr *NE,
// FIXME: General placement arguments need separate handling to overwrite
// the right origins.
- handleUse(PlacementArg);
+ handleAccess(PlacementArg);
// The pointer returned by placement new comes from the placement
// argument.
@@ -814,11 +785,13 @@ void FactsGenerator::VisitGCCAsmStmt(const GCCAsmStmt *AS) {
for (unsigned I = 0, N = AS->getNumOutputs(); I != N; ++I)
if (AS->isOutputPlusConstraint(I)) // Also read.
handleUse(AS->getOutputExpr(I));
+ else
+ handleAccess(AS->getOutputExpr(I));
}
void FactsGenerator::VisitCXXTypeidExpr(const CXXTypeidExpr *TE) {
if (TE->isPotentiallyEvaluated())
- handleUse(TE->getExprOperand());
+ handleAccess(TE->getExprOperand());
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
@@ -840,7 +813,7 @@ void FactsGenerator::VisitStmtExpr(const StmtExpr *SE) {
if (!Last)
return;
if (OriginList *Dst = getOriginsList(*SE))
- if (OriginList *Src = getRValueOrigins(Last, getOriginsList(*Last)))
+ if (OriginList *Src = readValue(Last))
flow(Dst, Src, /*Kill=*/true);
}
@@ -904,13 +877,12 @@ void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
const Expr *Arg = CCE->getArg(0);
if (isGslPointerType(Arg->getType())) {
- OriginList *ArgList = getOriginsList(*Arg);
- assert(ArgList && "GSL pointer argument should have an origin list");
// GSL pointer is constructed from another gsl pointer.
// Example:
// View(View v);
// View(const View &v);
- ArgList = getRValueOrigins(Arg, ArgList);
+ OriginList *ArgList = readValue(Arg);
+ assert(ArgList && "GSL pointer argument should have an origin list");
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
} else if (Arg->getType()->isPointerType()) {
// GSL pointer is constructed from a raw pointer. Flow only the outermost
@@ -1092,8 +1064,7 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
: CallArgs[CapturingArgIdx];
assert(CapturedByArg && "Capturer expression must be valid");
- OriginList *CapturingOriginList = getOriginsList(*CapturedByArg);
- OriginList *Dest = getRValueOrigins(CapturedByArg, CapturingOriginList);
+ OriginList *Dest = readValue(CapturedByArg);
if (!Dest)
continue;
// KillDest=false because we cannot know if previous captures are being
@@ -1158,7 +1129,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
// std::string_view(const std::string_view& from)
if (isGslPointerType(Args[I]->getType())) {
assert(!Args[I]->isGLValue() || ArgList->getLength() >= 2);
- ArgList = getRValueOrigins(Args[I], ArgList);
+ ArgList = readValue(Args[I]);
}
if (isGslOwnerType(Args[I]->getType())) {
// The constructed gsl::Pointer borrows from the Owner's storage, not
@@ -1220,29 +1191,27 @@ bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) {
return false;
}
-void FactsGenerator::handleUse(const Expr *E) {
+bool FactsGenerator::namesDeclStorage(const OriginList *List) const {
+ return FactMgr.getOriginMgr()
+ .getOrigin(List->getOuterOriginID())
+ .NamesDeclStorage;
+}
+
+void FactsGenerator::handleAccess(const Expr *E) {
OriginList *List = getOriginsList(*E);
- if (!List)
+ if (!List || namesDeclStorage(List))
return;
- // For DeclRefExpr: Remove the outer layer of origin which borrows from the
- // decl directly (e.g., when this is not a reference). This is a use of the
- // underlying decl.
- if (auto *DRE = dyn_cast<DeclRefExpr>(E);
- DRE && !DRE->getDecl()->getType()->isReferenceType())
- List = getRValueOrigins(DRE, List);
- // Skip if there is no inner origin (e.g., when it is not a pointer type).
- if (!List)
- return;
- if (!UseFacts.contains(E)) {
- UseFact *UF = FactMgr.createFact<UseFact>(E, List);
- CurrentBlockFacts.push_back(UF);
- UseFacts[E] = UF;
- }
+ CurrentBlockFacts.push_back(FactMgr.createFact<UseFact>(
+ E,
+ FactMgr.getOriginMgr().createSingleOriginList(List->getOuterOriginID())));
}
-void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
- if (UseFacts.contains(DRE))
- UseFacts[DRE]->markAsWritten();
+void FactsGenerator::handleUse(const Expr *E) {
+ OriginList *List = getOriginsList(*E);
+ if (List && namesDeclStorage(List))
+ List = List->peelOuterOrigin();
+ if (List)
+ CurrentBlockFacts.push_back(FactMgr.createFact<UseFact>(E, List));
}
// Creates an IssueFact for a new placeholder loan for each pointer or reference
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index f834f24921aaa8..7f827a28657823 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -146,22 +146,14 @@ class AnalysisImpl
return Lattice(Joined, Factory.getEmptyMap());
}
- /// A read operation makes the origin live with definite confidence, as it
- /// dominates this program point. A write operation kills the liveness of
- /// the origin since it overwrites the value.
+ /// A use makes the origin live with definite confidence, as it dominates this
+ /// program point.
Lattice transfer(Lattice In, const UseFact &UF) {
Lattice Out = In;
for (const OriginList *Cur = UF.getUsedOrigins(); Cur;
- Cur = Cur->peelOuterOrigin()) {
- OriginID OID = Cur->getOuterOriginID();
- // Write kills liveness.
- if (UF.isWritten())
- Out = removeLive(Out, OID);
- else
- // Read makes origin live with definite confidence (dominates this
- // point).
- Out = addLive(Out, OID, LivenessInfo(&UF, LivenessKind::Must));
- }
+ Cur = Cur->peelOuterOrigin())
+ Out = addLive(Out, Cur->getOuterOriginID(),
+ LivenessInfo(&UF, LivenessKind::Must));
return Out;
}
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 0c0c280d73cb6d..93afe2183900b2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -298,6 +298,8 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
// `p` points to.
if (doesDeclHaveStorage(ReferencedDecl)) {
Head = createNode(E, QualType{});
+ // `this->f` reaches its field through `this` instead of naming it.
+ AllOrigins.back().NamesDeclStorage = isa<DeclRefExpr>(E);
// This ensures origin sharing: multiple expressions to the same
// declaration share the same underlying origins.
Head->setInnerOriginList(getOrCreateList(ReferencedDecl));
@@ -316,7 +318,15 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
// addressable.
if (E->isGLValue() && !Type->isReferenceType())
Type = AST.getLValueReferenceType(Type);
- return ExprToList[E] = buildListForType(Type, E);
+ OriginList *List = buildListForType(Type, E);
+ // A qualification conversion of a glvalue names what its operand names. It is
+ // not transparent: for class types it is the node alias notes report.
+ if (const auto *CE = dyn_cast<CastExpr>(E);
+ CE && CE->getCastKind() == CK_NoOp && E->isGLValue())
+ if (const OriginList *Sub = getOrCreateList(CE->getSubExpr()))
+ AllOrigins[List->getOuterOriginID().Value].NamesDeclStorage =
+ getOrigin(Sub->getOuterOriginID()).NamesDeclStorage;
+ return ExprToList[E] = List;
}
void OriginManager::dump(OriginID OID, llvm::raw_ostream &OS) const {
diff --git a/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h b/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
index a24e1a7abcf86c..5bed87fe34389b 100644
--- a/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
@@ -366,3 +366,4 @@ void *operator new(std::size_t, std::align_val_t,
// Marks a use for -Wlifetime-safety. `(void)v` is not one: a discarded-value
// expression performs no lvalue-to-rvalue conversion in C++.
template <typename... Ts> void use(const Ts &...vs);
+
diff --git a/clang/test/Sema/LifetimeSafety/dangling-field.cpp b/clang/test/Sema/LifetimeSafety/dangling-field.cpp
index 8b68d962d807c0..68ed47687b29ae 100644
--- a/clang/test/Sema/LifetimeSafety/dangling-field.cpp
+++ b/clang/test/Sema/LifetimeSafety/dangling-field.cpp
@@ -157,17 +157,17 @@ struct MemberSetters {
p = local.data(); // expected-warning {{stack memory associated with local variable 'local' escapes to the field 'p' which will dangle}}
}
- void use_after_scope() {
+ void escape_to_field() {
{
std::string local;
view = local; // expected-warning {{stack memory associated with local variable 'local' escapes to the field 'view' which will dangle}}
p = local.data(); // expected-warning {{stack memory associated with local variable 'local' escapes to the field 'p' which will dangle}}
}
- (void)view;
+ (void)view; // Discarding the value is not a use.
(void)p;
}
- void use_after_scope_saved_after_reassignment() {
+ void escape_to_field_saved_after_reassignment() {
{
std::string local;
view = local;
@@ -179,6 +179,21 @@ struct MemberSetters {
view = kGlobal;
p = kGlobal.data();
}
+
+ // Reading the dangling field is reported as a use of it.
+ void use_after_scope() {
+ {
+ std::string local;
+ view = local; // expected-warning {{local variable 'local' does not live long enough}}
+ p = local.data(); // expected-warning {{local variable 'local' does not live long enough}} \
+ // expected-note {{result of call to 'data' aliases the storage of local variable 'local' because the implicit object parameter is inferred as lifetimebound}}
+ } // expected-note 2 {{local variable 'local' is destroyed here}}
+ use(view); // expected-note {{later used here}}
+ use(p); // expected-note {{later used here}}
+
+ view = kGlobal;
+ p = kGlobal.data();
+ }
};
// FIXME: Detect escape to field of field.
diff --git a/clang/test/Sema/LifetimeSafety/invalidations.cpp b/clang/test/Sema/LifetimeSafety/invalidations.cpp
index bfe161f7970c25..a7c62852c69892 100644
--- a/clang/test/Sema/LifetimeSafety/invalidations.cpp
+++ b/clang/test/Sema/LifetimeSafety/invalidations.cpp
@@ -447,9 +447,9 @@ void SelfInvalidatingMap() {
mp[2] = mp[1]; // expected-warning {{local variable 'mp' is later invalidated}} \
// expected-warning {{local variable 'mp' is later invalidated}} \
// expected-note {{local variable 'mp' is invalidated here}} \
+ // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'mp' because the implicit object parameter is inferred as lifetimebound}} \
// expected-note {{later used here}} \
// expected-note {{local variable 'mp' is invalidated here}} \
- // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'mp'}} \
// expected-note {{later used here}}
}
@@ -798,9 +798,9 @@ void FlatMapSubscriptMultipleCallsInvalidate(std::flat_map<int, int> mp, int a,
PrintMax(mp[a], mp[b]); // expected-warning {{parameter 'mp' is later invalidated}} \
// expected-warning {{parameter 'mp' is later invalidated}} \
// expected-note {{parameter 'mp' is invalidated here}} \
+ // expected-note 2 {{result of call to 'operator[]' aliases the storage of parameter 'mp' because the implicit object parameter is inferred as lifetimebound}} \
// expected-note {{later used here}} \
// expected-note {{parameter 'mp' is invalidated here}} \
- // expected-note 2 {{result of call to 'operator[]' aliases the storage of parameter 'mp'}} \
// expected-note {{later used here}}
}
diff --git a/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp b/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp
index 028833902aa94e..f3e76374b34c88 100644
--- a/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp
+++ b/clang/test/Sema/LifetimeSafety/lifetime-facts.cpp
@@ -19,7 +19,6 @@ MyObj* return_local_addr() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : MyObj *)
// CHECK-NEXT: Src: [[O_ADDR_X]] (Expr: UnaryOperator, Type : MyObj *)
-// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Read)
return p;
// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
// CHECK: OriginFlow:
@@ -81,7 +80,6 @@ void overwrite_origin() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_ADDR_S2:[0-9]+]] (Expr: UnaryOperator, Type : MyObj *) has loans to { s2 }
// CHECK-NEXT: Src: [[O_DRE_S2]] (Expr: DeclRefExpr, Decl: s2)
-// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Write)
// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *) has loans to { s2 }
@@ -103,7 +101,6 @@ void reassign_to_null() {
// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : MyObj *)
// CHECK-NEXT: Src: [[O_ADDR_S1]] (Expr: UnaryOperator, Type : MyObj *)
p = nullptr;
-// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Write)
// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *) has no loans
@@ -126,7 +123,6 @@ void pointer_indirection() {
// CHECK-NEXT: Dest: [[O_P:[0-9]+]] (Decl: p, Type : int *) has loans to { a }
// CHECK-NEXT: Src: [[O_ADDR_A]] (Expr: UnaryOperator, Type : int *)
int **pp = &p;
-// CHECK: Use ([[O_P]] (Decl: p, Type : int *), Read)
// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int **) has loans to { p }
@@ -143,7 +139,6 @@ void pointer_indirection() {
// FIXME: Propagate origins across dereference unary operator*
int *q = *pp;
-// CHECK: Use ([[O_PP_OUTER]] (Decl: pp, Type : int **), [[O_PP_INNER]] (Decl: pp, Type : int *), Read)
// CHECK: Issue ({{[0-9]+}} (Path: pp), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: pp))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int **) has loans to { p }
@@ -152,11 +147,14 @@ void pointer_indirection() {
// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) has loans to { a }
// CHECK-NEXT: Src: [[O_PP_INNER]] (Decl: pp, Type : int *)
// CHECK: OriginFlow:
-// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *&) has loans to { p }
+// CHECK-NEXT: Dest: [[O_DEREF_PP:[0-9]+]] (Expr: UnaryOperator, Type : int *&) has loans to { p }
// CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int **)
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : int *) has loans to { a }
// CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *)
+// The second load reads `*pp`, so it accesses what `pp` pointed to, and not
+// what `*pp` pointed to.
+// CHECK: Use ([[O_DEREF_PP]] (Expr: UnaryOperator, Type : int *&))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : int *) has loans to { a }
// CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : int *)
@@ -186,16 +184,16 @@ void test_use_lifetimebound_call() {
// CHECK-NEXT: Dest: [[O_Q:[0-9]+]] (Decl: q, Type : MyObj *) has loans to { y }
// CHECK-NEXT: Src: [[O_ADDR_Y]] (Expr: UnaryOperator, Type : MyObj *)
MyObj* r = LifetimeBoundCall(p, q);
-// CHECK: Use ([[O_P]] (Decl: p, Type : MyObj *), Read)
// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_P_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) has loans to { x }
// CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : MyObj *)
-// CHECK: Use ([[O_Q]] (Decl: q, Type : MyObj *), Read)
// CHECK: Issue ({{[0-9]+}} (Path: q), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: q))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_Q_RVAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *) has loans to { y }
// CHECK-NEXT: Src: [[O_Q]] (Decl: q, Type : MyObj *)
+// CHECK: Use ([[O_P_RVAL]] (Expr: ImplicitCastExpr, Type : MyObj *))
+// CHECK: Use ([[O_Q_RVAL]] (Expr: ImplicitCastExpr, Type : MyObj *))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_CALL_EXPR:[0-9]+]] (Expr: CallExpr, Type : MyObj *) has loans to { x }
// CHECK-NEXT: Src: [[O_P_RVAL]] (Expr: ImplicitCastExpr, Type : MyObj *)
@@ -230,10 +228,69 @@ void test_reference_variable() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *) has loans to { x }
// CHECK-NEXT: Src: [[O_Z]] (Decl: z, Type : const MyObj &)
-// CHECK: Use ({{[0-9]+}} (Decl: p, Type : const MyObj *), Write)
// CHECK: Issue ({{[0-9]+}} (Path: p), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr, Decl: p))
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: p, Type : const MyObj *) has loans to { x }
// CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *)
// CHECK: Expire (x)
}
+
+//===----------------------------------------------------------------------===//
+// Address-of versus access
+//
+// Field and element origins are not yet tracked across statements, so these
+// distinctions are only visible in the facts.
+//===----------------------------------------------------------------------===//
+
+struct Node { int id; Node *next; };
+
+// CHECK-LABEL: Function: address_of_array_element
+void address_of_array_element(int i) {
+ Node *arr[4];
+ Node **elem = &arr[i];
+// The element origin names the array, but taking its address never reads it.
+// CHECK: Dest: {{[0-9]+}} (Expr: ArraySubscriptExpr, Type : Node *&) has loans to { arr }
+// CHECK-NOT: Use ({{[0-9]+}} (Expr: ArraySubscriptExpr
+}
+
+// CHECK-LABEL: Function: read_array_element
+void read_array_element(int i) {
+ Node *arr[4];
+ Node *sink = arr[i];
+// CHECK: Dest: [[O_ELEM:[0-9]+]] (Expr: ArraySubscriptExpr, Type : Node *&) has loans to { arr }
+// CHECK: Use ([[O_ELEM]] (Expr: ArraySubscriptExpr, Type : Node *&))
+}
+
+// CHECK-LABEL: Function: write_array_element
+void write_array_element(int i, Node *p) {
+ Node *arr[4];
+ arr[i] = p;
+// CHECK: Dest: [[O_ELEM:[0-9]+]] (Expr: ArraySubscriptExpr, Type : Node *&) has loans to { arr }
+// CHECK: Use ([[O_ELEM]] (Expr: ArraySubscriptExpr, Type : Node *&))
+}
+
+// CHECK-LABEL: Function: address_of_member
+void address_of_member(Node *p) {
+ Node **pnext = &p->next;
+// CHECK: Dest: {{[0-9]+}} (Expr: MemberExpr, Type : Node *&) has loans to { $p }
+// CHECK-NOT: Use ({{[0-9]+}} (Expr: MemberExpr
+}
+
+// CHECK-LABEL: Function: read_member
+void read_member(Node *p) {
+ Node *sink = p->next;
+// CHECK: Dest: [[O_NEXT:[0-9]+]] (Expr: MemberExpr, Type : Node *&) has loans to { $p }
+// CHECK: Use ([[O_NEXT]] (Expr: MemberExpr, Type : Node *&))
+}
+
+// Reading a variable by name can only reach that variable, and the name proves
+// it is in scope, so no access is recorded. Reading through a pointer is.
+// CHECK-LABEL: Function: naming_a_variable_is_not_an_access
+void naming_a_variable_is_not_an_access(int *p) {
+ int a = 0;
+ int b = a;
+ *p = b;
+// CHECK: Dest: [[O_DEREF:[0-9]+]] (Expr: UnaryOperator, Type : int &) has loans to { $p }
+// CHECK: Use ([[O_DEREF]] (Expr: UnaryOperator, Type : int &))
+// CHECK-NOT: Use ({{[0-9]+}} (Expr: DeclRefExpr
+}
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index abc725a094bb9f..deef09c1e0bbb8 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -725,13 +725,15 @@ int** test_ternary_double_ptr(bool cond) {
// These are cases where the diagnostic kind is determined by location
//===----------------------------------------------------------------------===//
+// Returning the dangling pointer is not a use of it, so this is reported as a
+// return of stack memory rather than a use-after-scope.
MyObj* uaf_before_uar() {
MyObj* p;
{
MyObj local_obj;
- p = &local_obj; // expected-warning {{local variable 'local_obj' does not live long enough}}
- } // expected-note {{local variable 'local_obj' is destroyed here}}
- return p; // expected-note {{later used here}}
+ p = &local_obj; // expected-warning {{stack memory associated with local variable 'local_obj' is returned}}
+ }
+ return p; // expected-note {{returned here}}
}
View uar_before_uaf(const MyObj& safe, bool c) {
@@ -2064,9 +2066,8 @@ void test_lifetime_extension_ok() {
}
const std::string& test_return() {
- const std::string& x = S().x(); // expected-warning {{temporary object does not live long enough}} expected-note {{temporary object is destroyed here}} \
- // expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked as lifetimebound}}
- return x; // expected-note {{later used here}}
+ const std::string& x = S().x(); // expected-warning {{stack memory associated with temporary object is returned}}
+ return x; // expected-note {{returned here}}
}
} // namespace reference_type_decl_ref_expr
@@ -2833,11 +2834,8 @@ S getS2(const std::string &a [[clang::lifetimebound]], const std::string &b [[cl
S multiple_lifetimebound_params() {
std::string str{"abc"};
S s = getS2(str, std::string("temp")); // expected-warning {{stack memory associated with local variable 'str' is returned}} \
- // expected-warning {{temporary object does not live long enough}} \
- // expected-note {{result of call to 'getS2' aliases the storage of temporary object because parameter 'b' is marked as lifetimebound}} \
- // expected-note {{temporary object is destroyed here}}
- return s; // expected-note {{returned here}} \
- // expected-note {{later used here}}
+ // expected-warning {{stack memory associated with temporary object is returned}}
+ return s; // expected-note 2 {{returned here}}
}
// TODO: Diagnose [[clang::lifetimebound]] on functions whose return value
@@ -3714,6 +3712,14 @@ struct Y : X {
}
(void)x;
}
+ void baz() {
+ {
+ int a;
+ x = &a; // expected-warning {{local variable 'a' does not live long enough}}
+ } // expected-note {{local variable 'a' is destroyed here}}
+ use(x); // expected-note {{later used here}}
+ x = nullptr;
+ }
};
} // namespace base_class_fields
@@ -3997,7 +4003,7 @@ void member_capture() {
MyObj local;
c.set(local); // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{local variable 'local' is destroyed here}}
- use(c.stored); // expected-note {{later used here}}
+ use(c); // expected-note {{later used here}}
}
struct SimpleContainer {
@@ -4010,8 +4016,8 @@ void member_capture_simple_container() {
{
MyObj local;
c.set(local); // expected-warning {{local variable 'local' does not live long enough}}
- } // expected-note {{destroyed here}}
- use(c.stored); // expected-note {{later used here}}
+ } // expected-note {{destroyed here}}
+ use(c); // expected-note {{later used here}}
}
void captureTwo(View& into,
@@ -4318,10 +4324,12 @@ void foo() {
} // namespace TakeOwnershipTests
//===----------------------------------------------------------------------===//
-// Uses by code the analysis cannot see into
+// What counts as a use
//
-// Opaque code may dereference whatever it is handed. These are uses in their
-// own right, not only because every DeclRefExpr is currently one.
+// A use is an access through an lvalue: reading it (an lvalue-to-rvalue
+// conversion) or writing through it. The loans of the accessed lvalue say which
+// objects it may name, so a dereference needs no special handling. Taking an
+// address, naming a variable, or copying a pointer out of one is not an access.
//===----------------------------------------------------------------------===//
namespace std { class type_info; }
@@ -4332,6 +4340,155 @@ struct Node {
Node *next;
};
+void copying_a_pointer_is_not_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ Node *q = p; // Reads p, not *p.
+ // expected-note at -1 {{local variable 'p' aliases the storage of local variable 'local'}}
+ use(q); // expected-note {{later used here}}
+}
+
+void taking_an_address_is_not_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local;
+ }
+ Node **pp = &p; // no-warning: borrows p's storage, never reads it.
+ Node *reborrow = &*p; // no-warning: reborrows, no access.
+ Node **pnext = &p->next; // no-warning: address of a field.
+ (void)pp; (void)reborrow; (void)pnext;
+}
+
+void reading_through_a_pointer_is_a_use() {
+ Node *p;
+ int sink;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ sink = p->id; // expected-note {{later used here}}
+ (void)sink;
+}
+
+// Loading a scalar is not a use of the scalar; it is a use of the pointer that
+// was dereferenced to reach it.
+void reading_a_pointer_field_is_a_use() {
+ Node *p;
+ Node *sink;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ sink = p->next; // expected-note {{later used here}}
+ (void)sink;
+}
+
+void writing_through_a_pointer_is_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ p->id = 1; // expected-note {{later used here}}
+ p->next = nullptr;
+}
+
+void incrementing_through_a_pointer_is_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ p->id++; // expected-note {{later used here}}
+}
+
+// Incrementing the pointer itself only touches p's own storage.
+void incrementing_the_pointer_is_not_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local;
+ }
+ p++; // no-warning
+ p += 1; // no-warning
+}
+
+void discarding_the_value_is_not_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local;
+ }
+ (void)p; // no-warning
+}
+
+void element_access(int i) {
+ Node *arr[4];
+ Node *p, *sink;
+ {
+ Node local;
+ p = &local;
+ }
+ Node **elem = &arr[i]; // no-warning: address of an element.
+ arr[i] = p; // Reads p, writes the element; neither reads *p.
+ sink = arr[i]; // Reads the element, which names part of arr.
+ (void)elem; (void)sink;
+}
+
+// A dereference only accesses the level actually loaded.
+void one_level_per_load() {
+ Node **pp;
+ {
+ Node *inner;
+ Node outer;
+ inner = &outer;
+ pp = &inner; // expected-warning {{local variable 'inner' does not live long enough}}
+ } // expected-note {{local variable 'inner' is destroyed here}}
+ Node *q = *pp; // expected-note {{later used here}}
+ (void)q; // Reads pp, so it names 'inner'; 'outer' is never read.
+}
+
+void reading_through_a_reference_is_a_use() {
+ Node *p;
+ int sink;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ Node &r = *p; // Binding a reference is a reborrow, not an access.
+ // expected-note at -1 {{local variable 'p' aliases the storage of local variable 'local'}}
+ sink = r.id; // expected-note {{later used here}}
+ (void)sink;
+}
+
+void writing_through_a_reference_is_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ Node &r = *p; // expected-note {{local variable 'p' aliases the storage of local variable 'local'}}
+ r.id = 1; // expected-note {{later used here}}
+}
+
+void through_a_conditional(bool cond) {
+ Node *p1, *p2, *reborrow;
+ int sink;
+ {
+ Node a, b;
+ p1 = &a; // expected-warning {{local variable 'a' does not live long enough}}
+ p2 = &b; // expected-warning {{local variable 'b' does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ reborrow = &*(cond ? p1 : p2); // no-warning: reborrow only.
+ sink = (cond ? p1 : p2)->id; // expected-note 2 {{later used here}}
+ (cond ? p1 : p2)->id = 1;
+ (void)reborrow; (void)sink;
+}
+
// Opaque code may dereference what it is handed, so every argument is a use --
// including when there is no FunctionDecl to inspect.
namespace opaque_callees {
@@ -4405,13 +4562,45 @@ void through_placement_new() {
new (p) Node; // expected-note {{later used here}}
}
+// Reading the dangling value and then overwriting it is still a use; the read
+// happens first.
+Node *ident(Node *);
+void read_then_overwrite() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ p = ident(p); // expected-note {{later used here}}
+}
} // namespace opaque_callees
// Reads with no lvalue-to-rvalue conversion in the AST.
namespace class_reads {
+struct Holder { View v; };
struct Base { virtual ~Base(); };
struct Derived : Base {};
+void copy_view_from_field() {
+ Holder *h;
+ {
+ Holder local;
+ h = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ View v = h->v; // expected-note {{later used here}}
+ (void)v;
+}
+
+void assign_view_from_deref() {
+ View *pv;
+ View v;
+ {
+ View local;
+ pv = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ v = *pv; // expected-note {{later used here}}
+}
+
void dynamic_cast_reads_the_object() {
Base *b;
{
@@ -4431,6 +4620,16 @@ void typeid_reads_the_object() {
(void)typeid(*b); // expected-note {{later used here}}
}
+void bit_cast_reads_its_operand() {
+ long *p;
+ {
+ long local = 0;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ int *q = __builtin_bit_cast(int *, *p); // expected-note {{later used here}}
+ (void)q;
+}
+
void asm_inout_operand_is_read() {
Node *p;
{
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index dcaa31d62b3c74..6d118d9235f574 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -1311,8 +1311,11 @@ TEST_F(LifetimeAnalysisTest, LivenessInLoop) {
EXPECT_THAT(Origins({"p", "q"}), MaybeLiveAt("p3"));
- EXPECT_THAT(Origins({"q"}), MustBeLiveAt("p2"));
- EXPECT_THAT(NoOrigins(), MaybeLiveAt("p2"));
+ // `p = q` is a copy, not a use of `q`, so `q` is live at "p2" only because
+ // its loans flow into `p`. It inherits `p`'s confidence, and `p` is merely
+ // maybe-live at "p3": the backedge overwrites it before reading it.
+ EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p2"));
+ EXPECT_THAT(NoOrigins(), MustBeLiveAt("p2"));
EXPECT_THAT(Origins({"p", "q"}), MaybeLiveAt("p1"));
}
@@ -1407,6 +1410,92 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1"));
}
+// A use is an access through an lvalue: an lvalue-to-rvalue conversion or a
+// write through it. Taking an address or copying a pointer out of a variable
+// is neither.
+TEST_F(LifetimeAnalysisTest, AddressOfIsNotAUse) {
+ SetupTest(R"(
+ void target() {
+ MyObj s;
+ MyObj* p = &s;
+ POINT(p1);
+ MyObj** pp = &p;
+ (void)pp;
+ }
+ )");
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LoadIsAUse) {
+ SetupTest(R"(
+ void target() {
+ MyObj s;
+ MyObj* p = &s;
+ POINT(p1);
+ MyObj* q = p;
+ use(q);
+ }
+ )");
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
+}
+
+// Reading `pp` and then `*pp` accesses exactly those two levels. Nothing reads
+// `q`, so no deeper level stays live.
+TEST_F(LifetimeAnalysisTest, DereferenceAccessesOneLevel) {
+ SetupTest(R"(
+ void target(MyObj** pp) {
+ POINT(p1);
+ MyObj* q = *pp;
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origins({"pp"}), MustBeLiveAt("p1"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, WriteThroughDereferenceIsAUse) {
+ SetupTest(R"(
+ void target(MyObj** pp) {
+ POINT(p1);
+ *pp = nullptr;
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origins({"pp"}), MustBeLiveAt("p1"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+}
+
+// Assigning to a variable is an access to that variable's own storage, which is
+// never a loan that can expire, so it leaves nothing live.
+TEST_F(LifetimeAnalysisTest, WriteToVariableIsNotAUseOfItsValue) {
+ SetupTest(R"(
+ void target(MyObj* q) {
+ MyObj* p;
+ POINT(p1);
+ p = q;
+ POINT(p2);
+ use(*p);
+ }
+ )");
+ EXPECT_THAT(Origins({"q"}), MustBeLiveAt("p1"));
+ EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
+}
+
+// A reference decl has no storage of its own, so its outer origin is the
+// binding; reading or writing through it accesses whatever it was bound to.
+TEST_F(LifetimeAnalysisTest, AccessThroughReference) {
+ SetupTest(R"(
+ void target(MyObj* p) {
+ MyObj& r = *p;
+ POINT(p1);
+ r.i = 1;
+ POINT(p2);
+ }
+ )");
+ EXPECT_THAT(Origins({"r"}), MustBeLiveAt("p1"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+}
+
TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) {
SetupTest(R"(
void target() {
>From f9ea958bf9252af1d7c0dfcd6d972ccd48d1b550 Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Thu, 24 Sep 2026 12:06:00 +0100
Subject: [PATCH 4/5] [LifetimeSafety] Address review comments
Assisted by: Opus 5.5
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 7 ++++
.../Analyses/LifetimeSafety/LoanPropagation.h | 2 +
.../Analyses/LifetimeSafety/Origins.h | 29 ++++++++-----
clang/lib/Analysis/LifetimeSafety/Checker.cpp | 21 +++-------
.../LifetimeSafety/FactsGenerator.cpp | 41 +++++++------------
.../LifetimeSafety/LoanPropagation.cpp | 22 +++++++++-
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 28 +++++++------
7 files changed, 83 insertions(+), 67 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index e9f2a4789241c0..b20da9b48ed57d 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -141,8 +141,15 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
/// If so, creates a `TestPointFact` and returns true.
bool handleTestPoint(const CXXFunctionalCastExpr *FCE);
+ /// Whether \p List's outer origin names a declaration's storage outright, so
+ /// it can never hold an expired loan (see Origin::NamesDeclStorage).
bool namesDeclStorage(const OriginList *List) const;
+ /// Returns the origins of the value \p E evaluates to, recording the read of
+ /// a glvalue. Callers that write to \p E peel the outer origin themselves.
+ ///
+ /// Example: For `View& v`, returns the origin of what v points to, not v's
+ /// storage.
OriginList *readValue(const Expr *E);
/// Records an access (read or write) of the storage \p E designates, or that
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
index e13442facd82d5..d46712ce2b1a56 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
@@ -51,6 +51,8 @@ class LoanPropagationAnalysis {
const LoanID TargetLoan,
const CFG *Cfg) const;
+ /// Like the above, starting from the used origin holding \p TargetLoan. The
+ /// casts that load the used variable itself are left out.
llvm::SmallVector<OriginID> buildOriginFlowChain(const UseFact *UF,
const LoanID TargetLoan,
const CFG *Cfg) const;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
index b4a7ec2832d712..4dc05cdd0c56ae 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
@@ -54,13 +54,19 @@ struct Origin {
const Type *Ty;
/// True if this origin only holds a loan to a declaration named in scope, so
- /// it can never hold an expired loan.
- bool NamesDeclStorage = false;
-
- Origin(OriginID ID, const clang::ValueDecl *D, const Type *QT)
- : ID(ID), Ptr(D), Ty(QT) {}
- Origin(OriginID ID, const clang::Expr *E, const Type *QT)
- : ID(ID), Ptr(E), Ty(QT) {}
+ /// it can never hold an expired loan. For example, in
+ /// int s = 42;
+ /// int t = s;
+ /// the outer origin of the expression `s` holds a loan to `s`, which is alive
+ /// wherever `s` can be named.
+ bool NamesDeclStorage;
+
+ Origin(OriginID ID, const clang::ValueDecl *D, const Type *QT,
+ bool NamesDeclStorage)
+ : ID(ID), Ptr(D), Ty(QT), NamesDeclStorage(NamesDeclStorage) {}
+ Origin(OriginID ID, const clang::Expr *E, const Type *QT,
+ bool NamesDeclStorage)
+ : ID(ID), Ptr(E), Ty(QT), NamesDeclStorage(NamesDeclStorage) {}
const clang::ValueDecl *getDecl() const {
return Ptr.dyn_cast<const clang::ValueDecl *>();
@@ -195,11 +201,14 @@ class OriginManager {
private:
OriginID getNextOriginID() { return NextOriginID++; }
- OriginList *createNode(const ValueDecl *D, QualType QT);
- OriginList *createNode(const Expr *E, QualType QT);
+ OriginList *createNode(const ValueDecl *D, QualType QT,
+ bool NamesDeclStorage = false);
+ OriginList *createNode(const Expr *E, QualType QT,
+ bool NamesDeclStorage = false);
template <typename T>
- OriginList *buildListForType(QualType QT, const T *Node);
+ OriginList *buildListForType(QualType QT, const T *Node,
+ bool NamesDeclStorage = false);
void initializeThisOrigins(const Decl *D);
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index a590491df23e74..c4cc872dcd568d 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -24,7 +24,6 @@
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TimeProfiler.h"
@@ -558,22 +557,14 @@ class LifetimeChecker {
/// Given a chain of origins that shows how a loan propagates, this function
/// extracts the corresponding expressions for each origin. Origins that refer
/// to declarations (rather than expressions) are skipped.
- ///
- /// Until the chain reaches a declaration it is inside the use expression,
- /// where casts just load the used variable.
llvm::SmallVector<const Expr *>
getExprChain(llvm::ArrayRef<OriginID> OriginFlowChain) {
- llvm::SmallVector<const Expr *> Chain;
- bool InUse = true;
- for (const OriginID CurrOID : OriginFlowChain) {
- const Expr *CurrExpr =
- FactMgr.getOriginMgr().getOrigin(CurrOID).getExpr();
- if (!CurrExpr)
- InUse = false;
- else if (!InUse || !isa<ImplicitCastExpr>(CurrExpr))
- Chain.push_back(CurrExpr);
- }
- return Chain;
+ llvm::SmallVector<const Expr *> rs;
+ for (const OriginID CurrOID : OriginFlowChain)
+ if (const Expr *CurrExpr =
+ FactMgr.getOriginMgr().getOrigin(CurrOID).getExpr())
+ rs.push_back(CurrExpr);
+ return rs;
}
};
} // namespace
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 091935df1a509e..292e3279233bb4 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -153,17 +153,14 @@ void FactsGenerator::run() {
FactMgr.computePersistentOrigins(Cfg);
}
-/// Returns the origins of the value \p E evaluates to, recording the read of a
-/// glvalue. Writes peel the outer origin directly instead.
-///
-/// Example: For `View& v`, returns the origin of what v points to, not v's
-/// storage.
OriginList *FactsGenerator::readValue(const Expr *E) {
OriginList *List = getOriginsList(*E);
+ if (!List)
+ return nullptr;
if (!E->isGLValue())
return List;
handleAccess(E);
- return List ? List->peelOuterOrigin() : nullptr;
+ return List->peelOuterOrigin();
}
void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) {
@@ -288,31 +285,21 @@ void FactsGenerator::VisitCXXNullPtrLiteralExpr(
void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
const Expr *SubExpr = CE->getSubExpr();
- const CastKind Kind = CE->getCastKind();
- const bool Loads =
- Kind == CK_LValueToRValue || Kind == CK_LValueToRValueBitCast;
- OriginList *Loaded = Loads ? readValue(SubExpr) : nullptr;
- if (Kind == CK_Dynamic)
- handleAccess(SubExpr);
-
+ // May be null: loading an `int` has no origins but still reads the operand.
OriginList *Dest = getOriginsList(*CE);
- if (!Dest)
- return;
- OriginList *Src = getOriginsList(*SubExpr);
+ OriginList *Src = Dest ? getOriginsList(*SubExpr) : nullptr;
switch (CE->getCastKind()) {
case CK_LValueToRValue:
- if (!SubExpr->isGLValue())
- return;
-
- assert(Src && "LValue being cast to RValue has no origin list");
// The result of an LValue-to-RValue cast on a pointer lvalue (like `q` in
// `int *p, *q; p = q;`) should propagate the inner origin (what the pointer
// points to), not the outer origin (the pointer's storage location).
- flow(Dest, Loaded, /*Kill=*/true);
+ flow(Dest, readValue(SubExpr), /*Kill=*/true);
+ return;
+ case CK_Dynamic:
+ handleAccess(SubExpr);
return;
case CK_NullToPointer:
- getOriginsList(*CE);
// TODO: Flow into them a null origin.
return;
case CK_NoOp:
@@ -330,7 +317,7 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
case CK_ArrayToPointerDecay:
// va_arg(ap, array_type) is UB and does not provide addressable array
// storage to model.
- if (isa<VAArgExpr>(SubExpr->IgnoreParens()))
+ if (!Dest || isa<VAArgExpr>(SubExpr->IgnoreParens()))
return;
assert(Src && "Array expression should have origins as it is GL value");
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
@@ -351,12 +338,12 @@ void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
case CK_AtomicToNonAtomic: {
// `__builtin_bit_cast`/`std::bit_cast` of a pointer, and
// wrapping/unwrapping `_Atomic(T*)`, preserve the pointer value, so
- // propagate the borrow. The operand may be a glvalue, so strip its outer
- // lvalue level first. A bit-cast that materializes a pointer from a
+ // propagate the borrow. readValue peels a glvalue operand's storage level
+ // and records the read. A bit-cast that materializes a pointer from a
// non-pointer representation has no matching source origin and is
// untracked.
- OriginList *RVSrc = Loads ? Loaded : Src;
- if (RVSrc && Dest->getLength() == RVSrc->getLength())
+ OriginList *RVSrc = readValue(SubExpr);
+ if (Dest && RVSrc && Dest->getLength() == RVSrc->getLength())
flow(Dest, RVSrc, /*Kill=*/true);
return;
}
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index a11f1cca25aca6..80ba7d08a3103b 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -10,6 +10,7 @@
#include <memory>
#include "Dataflow.h"
+#include "clang/AST/Expr.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
@@ -229,13 +230,30 @@ class AnalysisImpl
for (const OriginList *Cur = UF->getUsedOrigins(); Cur;
Cur = Cur->peelOuterOrigin())
if (getLoans(Cur->getOuterOriginID(), UF).contains(TargetLoan))
- return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan,
- Cfg);
+ return dropLoadsInUse(
+ buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan, Cfg));
return {};
}
private:
+ /// An expression's origin only receives loans from its subexpressions, so
+ /// until the chain reaches a declaration it is inside the use expression.
+ /// Casts there just load the used variable, so drop them.
+ llvm::SmallVector<OriginID>
+ dropLoadsInUse(llvm::SmallVector<OriginID> Chain) const {
+ const OriginManager &OM = FactMgr.getOriginMgr();
+ auto FirstDecl = llvm::find_if(
+ Chain, [&](OriginID OID) { return !OM.getOrigin(OID).getExpr(); });
+ Chain.erase(std::remove_if(Chain.begin(), FirstDecl,
+ [&](OriginID OID) {
+ return isa<ImplicitCastExpr>(
+ OM.getOrigin(OID).getExpr());
+ }),
+ FirstDecl);
+ return Chain;
+ }
+
/// Returns true if the origin is persistent (referenced in multiple blocks).
bool isPersistent(OriginID OID) const {
return PersistentOrigins.test(OID.Value);
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index 93afe2183900b2..385b203d728455 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -210,15 +210,17 @@ void OriginManager::initializeThisOrigins(const Decl *D) {
ThisOrigins = buildListForType(MD->getThisType(), MD);
}
-OriginList *OriginManager::createNode(const ValueDecl *D, QualType QT) {
+OriginList *OriginManager::createNode(const ValueDecl *D, QualType QT,
+ bool NamesDeclStorage) {
OriginID NewID = getNextOriginID();
- AllOrigins.emplace_back(NewID, D, QT.getTypePtrOrNull());
+ AllOrigins.emplace_back(NewID, D, QT.getTypePtrOrNull(), NamesDeclStorage);
return new (ListAllocator.Allocate<OriginList>()) OriginList(NewID);
}
-OriginList *OriginManager::createNode(const Expr *E, QualType QT) {
+OriginList *OriginManager::createNode(const Expr *E, QualType QT,
+ bool NamesDeclStorage) {
OriginID NewID = getNextOriginID();
- AllOrigins.emplace_back(NewID, E, QT.getTypePtrOrNull());
+ AllOrigins.emplace_back(NewID, E, QT.getTypePtrOrNull(), NamesDeclStorage);
return new (ListAllocator.Allocate<OriginList>()) OriginList(NewID);
}
@@ -227,12 +229,13 @@ OriginList *OriginManager::createSingleOriginList(OriginID OID) {
}
template <typename T>
-OriginList *OriginManager::buildListForType(QualType QT, const T *Node) {
+OriginList *OriginManager::buildListForType(QualType QT, const T *Node,
+ bool NamesDeclStorage) {
assert(hasOrigins(QT) && "buildListForType called for non-pointer type");
// `_Atomic(T)` is transparent for lifetime purposes: build the node for T.
if (const auto *AT = QT->getAs<AtomicType>())
- return buildListForType(AT->getValueType(), Node);
- OriginList *Head = createNode(Node, QT);
+ return buildListForType(AT->getValueType(), Node, NamesDeclStorage);
+ OriginList *Head = createNode(Node, QT, NamesDeclStorage);
if (QT->isPointerOrReferenceType()) {
QualType PointeeTy = QT->getPointeeType();
@@ -297,9 +300,9 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
// This models taking the address: `&p` borrows the storage of `p`, not what
// `p` points to.
if (doesDeclHaveStorage(ReferencedDecl)) {
- Head = createNode(E, QualType{});
// `this->f` reaches its field through `this` instead of naming it.
- AllOrigins.back().NamesDeclStorage = isa<DeclRefExpr>(E);
+ Head = createNode(E, QualType{},
+ /*NamesDeclStorage=*/isa<DeclRefExpr>(E));
// This ensures origin sharing: multiple expressions to the same
// declaration share the same underlying origins.
Head->setInnerOriginList(getOrCreateList(ReferencedDecl));
@@ -318,15 +321,14 @@ OriginList *OriginManager::getOrCreateList(const Expr *E) {
// addressable.
if (E->isGLValue() && !Type->isReferenceType())
Type = AST.getLValueReferenceType(Type);
- OriginList *List = buildListForType(Type, E);
// A qualification conversion of a glvalue names what its operand names. It is
// not transparent: for class types it is the node alias notes report.
+ bool NamesDeclStorage = false;
if (const auto *CE = dyn_cast<CastExpr>(E);
CE && CE->getCastKind() == CK_NoOp && E->isGLValue())
if (const OriginList *Sub = getOrCreateList(CE->getSubExpr()))
- AllOrigins[List->getOuterOriginID().Value].NamesDeclStorage =
- getOrigin(Sub->getOuterOriginID()).NamesDeclStorage;
- return ExprToList[E] = List;
+ NamesDeclStorage = getOrigin(Sub->getOuterOriginID()).NamesDeclStorage;
+ return ExprToList[E] = buildListForType(Type, E, NamesDeclStorage);
}
void OriginManager::dump(OriginID OID, llvm::raw_ostream &OS) const {
>From fd0fc5bc6ac8f0112aceabe6453082aa67ef8493 Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Thu, 24 Sep 2026 14:18:49 +0100
Subject: [PATCH 5/5] Additional tests and minor style changes.
---
clang/test/Sema/LifetimeSafety/safety.cpp | 95 +++++++++++++++++++++--
1 file changed, 88 insertions(+), 7 deletions(-)
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index deef09c1e0bbb8..bf49e86577678d 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -3066,13 +3066,13 @@ namespace conditional_operator_control_flow {
#ifdef __cpp_exceptions
void throw_branches(bool cond, int *value) {
- use((cond ? throw 1 : value));
- (void)(cond ? throw 1 : throw 2);
+ use(cond ? throw 1 : value);
+ cond ? throw 1 : throw 2;
}
void nested_throw_branches(bool cond, bool cond2, int *value) {
- use((cond ? (cond2 ? throw 1 : value) : throw 2));
- use((cond ? throw 1 : (cond2 ? value : throw 2)));
+ use(cond ? (cond2 ? throw 1 : value) : throw 2);
+ use(cond ? throw 1 : (cond2 ? value : throw 2));
}
// A `throw` arm of a binary conditional `a ?: b` carries no origins; flowing it
@@ -4363,6 +4363,37 @@ void taking_an_address_is_not_a_use() {
(void)pp; (void)reborrow; (void)pnext;
}
+// The results still point into the dead object, so using them warns.
+void using_an_address_of_the_pointer_is_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ Node **pp = &p;
+ use(pp); // expected-note {{later used here}}
+}
+
+void using_a_reborrow_is_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ Node *reborrow = &*p; // expected-note {{local variable 'p' aliases the storage of local variable 'local'}}
+ use(reborrow); // expected-note {{later used here}}
+}
+
+void using_an_address_of_a_field_is_a_use() {
+ Node *p;
+ {
+ Node local;
+ p = &local; // expected-warning {{local variable 'local' does not live long enough}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ Node **pnext = &p->next; // expected-note {{local variable 'p' aliases the storage of local variable 'local'}}
+ use(pnext); // expected-note {{later used here}}
+}
+
void reading_through_a_pointer_is_a_use() {
Node *p;
int sink;
@@ -4449,7 +4480,45 @@ void one_level_per_load() {
pp = &inner; // expected-warning {{local variable 'inner' does not live long enough}}
} // expected-note {{local variable 'inner' is destroyed here}}
Node *q = *pp; // expected-note {{later used here}}
- (void)q; // Reads pp, so it names 'inner'; 'outer' is never read.
+ (void)*q; // Discarded, so 'outer' is never read.
+}
+
+// Each of these reads 'outer' as well.
+void reading_the_second_level_is_a_use() {
+ Node **pp;
+ {
+ Node *inner;
+ Node outer;
+ inner = &outer; // expected-warning {{local variable 'outer' does not live long enough}}
+ pp = &inner; // expected-warning {{local variable 'inner' does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ Node *q = *pp; // expected-note {{later used here}} \
+ // expected-note {{local variable 'pp' aliases the storage of local variable 'outer'}}
+ use(*q); // expected-note {{later used here}}
+}
+
+void using_the_loaded_pointer_is_a_use() {
+ Node **pp;
+ {
+ Node *inner;
+ Node outer;
+ inner = &outer; // expected-warning {{local variable 'outer' does not live long enough}}
+ pp = &inner; // expected-warning {{local variable 'inner' does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ Node *q = *pp; // expected-note {{later used here}} \
+ // expected-note {{local variable 'pp' aliases the storage of local variable 'outer'}}
+ use(q); // expected-note {{later used here}}
+}
+
+void using_the_double_pointer_is_a_use() {
+ Node **pp;
+ {
+ Node *inner;
+ Node outer;
+ inner = &outer; // expected-warning {{local variable 'outer' does not live long enough}}
+ pp = &inner; // expected-warning {{local variable 'inner' does not live long enough}}
+ } // expected-note 2 {{destroyed here}}
+ use(pp); // expected-note 2 {{later used here}}
}
void reading_through_a_reference_is_a_use() {
@@ -4562,6 +4631,18 @@ void through_placement_new() {
new (p) Node; // expected-note {{later used here}}
}
+// Unlike `p++` on a raw pointer, a class iterator's operator++ is a call.
+void class_iterator_increment_is_a_use() {
+ std::vector<int> v;
+ auto it = v.begin();
+ {
+ std::vector<int> local;
+ it = local.begin(); // expected-warning {{local variable 'local' does not live long enough}} \
+ // expected-note {{result of call to 'begin' aliases the storage of local variable 'local' because the implicit object parameter is inferred as lifetimebound}}
+ } // expected-note {{local variable 'local' is destroyed here}}
+ it++; // expected-note {{later used here}}
+}
+
// Reading the dangling value and then overwriting it is still a use; the read
// happens first.
Node *ident(Node *);
@@ -4621,9 +4702,9 @@ void typeid_reads_the_object() {
}
void bit_cast_reads_its_operand() {
- long *p;
+ __INTPTR_TYPE__ *p;
{
- long local = 0;
+ __INTPTR_TYPE__ local = 0;
p = &local; // expected-warning {{local variable 'local' does not live long enough}}
} // expected-note {{local variable 'local' is destroyed here}}
int *q = __builtin_bit_cast(int *, *p); // expected-note {{later used here}}
More information about the cfe-commits
mailing list