================
@@ -4318,6 +4319,324 @@ 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
+
+//===----------------------------------------------------------------------===//
+// What counts as a use
+//
+// 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; }
+
+namespace what_is_a_use {
+struct Node {
+  int id;
+  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@-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@-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 {
+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}}
+}
+
+// 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 {
----------------
Xazax-hun wrote:

Yup, I think in the future we should maybe consider some user-defined 
operations as accesses instead of uses. But to make it sound, we also should 
check the bodies of those functions and warn in case they are actually end up 
dereferencing something they should not. I think this can be follow-up work. 

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

Reply via email to