[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