Author: Gábor Horváth
Date: 2026-07-03T08:58:44+01:00
New Revision: 448e7fec76bdedb50c2e8e17f68440b505da369c

URL: 
https://github.com/llvm/llvm-project/commit/448e7fec76bdedb50c2e8e17f68440b505da369c
DIFF: 
https://github.com/llvm/llvm-project/commit/448e7fec76bdedb50c2e8e17f68440b505da369c.diff

LOG: [LifetimeSafety] Track unary plus on a pointer (#207243)

Unary plus on a pointer is the identity (+p == p), so the result carries
the operand's loans -- but UO_Plus fell through VisitUnaryOperator's
default and left the result origin empty, dropping the borrow (e.g. p =
+&local was a silently-missed use-after-scope). Handle it by flowing the
operand's rvalue origins.

Assisted-by: Claude Opus 4.8

Co-authored-by: Gabor Horvath <[email protected]>

Added: 
    

Modified: 
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/test/Sema/LifetimeSafety/safety.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index b909b8b568aee..095bee8135c20 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -399,6 +399,17 @@ void FactsGenerator::VisitUnaryOperator(const 
UnaryOperator *UO) {
     killAndFlowOrigin(*UO, *SubExpr);
     return;
   }
+  case UO_Plus: {
+    // Unary plus on a pointer is the identity (`+p == p`), so the prvalue
+    // result carries the operand's loans. Flow the operand's rvalue origins
+    // (peeling storage only when the operand is itself a glvalue).
+    if (!UO->getType()->isPointerType())
+      return;
+    const Expr *SubExpr = UO->getSubExpr();
+    flow(getOriginsList(*UO),
+         getRValueOrigins(SubExpr, getOriginsList(*SubExpr)), /*Kill=*/true);
+    return;
+  }
   case UO_PreInc:
   case UO_PostInc:
   case UO_PreDec:

diff  --git a/clang/test/Sema/LifetimeSafety/safety.cpp 
b/clang/test/Sema/LifetimeSafety/safety.cpp
index 57369a95d0e82..7362ca632b2e5 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -1370,6 +1370,53 @@ FalseView binary_conditional_folded_false(FalseView fb) {
   return FalseView(local) ?: fb;  // no-warning (result is always 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;
+  {
+    int local = 0;
+    p = +&local; // expected-warning {{local variable 'local' does not live 
long enough}}
+  }              // expected-note {{local variable 'local' is destroyed here}}
+  use(p);        // expected-note {{later used here}}
+}
+
+int *return_borrow_of_local() {
+  int local = 0;
+  return +&local; // expected-warning {{stack memory associated with local 
variable 'local' is returned}}
+                  // expected-note@-1 {{returned here}}
+}
+
+// A pointer glvalue operand forwards its loan too.
+void forward_pointer_value() {
+  int *p;
+  {
+    int local = 0;
+    int *q = &local; // expected-warning {{local variable 'local' does not 
live long enough}}
+    p = +q;          // expected-note {{local variable 'q' aliases the storage 
of local variable 'local'}}
+  }                  // expected-note {{local variable 'local' is destroyed 
here}}
+  use(p);            // expected-note {{later used here}}
+}
+
+// Negative: a long-lived borrow stays silent.
+void ok() {
+  static int s;
+  int *p = +&s;
+  use(p); // no-warning
+}
+
+// Multi-level: the deeper origin flows through unary plus too.
+int **return_multilevel() {
+  int a = 1;
+  int *b = &a;   // expected-warning {{stack memory associated with local 
variable 'a' is returned}}
+  int **c = +&b; // expected-warning {{stack memory associated with local 
variable 'b' is returned}}
+  return c;      // expected-note 2 {{returned here}}
+}
+} // namespace unary_plus
+
 // FIXME: Diagnostic output does not handle ParenExpr correctly, causing alias
 // information to be missed (local variable 'p' aliases the storage of local 
variable 'b').
 void simpleparen() {


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

Reply via email to