Author: Utkarsh Saxena
Date: 2026-01-16T21:01:07Z
New Revision: 2bcd2f2371655bfa6dc52a6c639a5a1284a37673

URL: 
https://github.com/llvm/llvm-project/commit/2bcd2f2371655bfa6dc52a6c639a5a1284a37673
DIFF: 
https://github.com/llvm/llvm-project/commit/2bcd2f2371655bfa6dc52a6c639a5a1284a37673.diff

LOG: [LifetimeSafety] Track moved declarations to prevent false positives 
(#170007)

Prevent false positives in lifetime safety analysis when variables are
moved using `std::move`.

When a value is moved using `std::move`, ownership is transferred from
the original variable to another. The lifetime safety analysis was
previously generating false positives by warning about
use-after-lifetime when the original variable was destroyed after being
moved. This change prevents those false positives by tracking moved
declarations and exempting them from loan expiration checks.

- Added tracking for declarations that have been moved via `std::move`
in the `FactsGenerator` class
- Added a `MovedDecls` set to track moved declarations in a
flow-insensitive manner
- Implemented detection of `std::move` calls in `VisitCallExpr`
- Modified `handleLifetimeEnds` to skip loans for declarations that have
been moved
- Added a test case demonstrating that warnings are suppressed for moved
variables

```cpp
void silenced() {
  MyObj b;
  View v;
  {
    MyObj a;
    v = a;
    b = std::move(a); // No warning for 'a' being moved.
  }
  (void)v;
}
```

Fixes https://github.com/llvm/llvm-project/issues/167290
Fixes https://github.com/llvm/llvm-project/issues/175273

Added: 
    

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
    clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
    clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
    clang/test/Sema/warn-lifetime-safety.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 0e23dc8ea0fc5..ee2f1aae4a4c5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -106,6 +106,15 @@ class FactsGenerator : public 
ConstStmtVisitor<FactsGenerator> {
   // corresponding to the left-hand side is updated to be a "write", thereby
   // exempting it from the check.
   llvm::DenseMap<const DeclRefExpr *, UseFact *> UseFacts;
+
+  // This is a flow-insensitive approximation: once a declaration is moved
+  // anywhere in the function, it's treated as moved everywhere. This can lead
+  // to false negatives on control flow paths where the value is not actually
+  // moved, but these are considered lower priority than the false positives
+  // this tracking prevents.
+  // TODO: The ideal solution would be flow-sensitive ownership tracking that
+  // records where values are moved from and to, but this is more complex.
+  llvm::DenseSet<const ValueDecl *> MovedDecls;
 };
 
 } // namespace clang::lifetimes::internal

diff  --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index 6dfe4c8fa6b9c..ee1634a6f5ea2 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -30,6 +30,7 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, 
LoanID ID) {
 /// variable.
 /// TODO: Model access paths of other types, e.g., s.field, heap and globals.
 struct AccessPath {
+private:
   // An access path can be:
   // - ValueDecl * , to represent the storage location corresponding to the
   //   variable declared in ValueDecl.
@@ -39,6 +40,7 @@ struct AccessPath {
                            const clang::MaterializeTemporaryExpr *>
       P;
 
+public:
   AccessPath(const clang::ValueDecl *D) : P(D) {}
   AccessPath(const clang::MaterializeTemporaryExpr *MTE) : P(MTE) {}
 

diff  --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 24e5479923946..54e19b58bd7d5 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -203,9 +203,23 @@ void FactsGenerator::VisitCXXMemberCallExpr(const 
CXXMemberCallExpr *MCE) {
   }
 }
 
+static bool isStdMove(const FunctionDecl *FD) {
+  return FD && FD->isInStdNamespace() && FD->getIdentifier() &&
+         FD->getName() == "move";
+}
+
 void FactsGenerator::VisitCallExpr(const CallExpr *CE) {
   handleFunctionCall(CE, CE->getDirectCallee(),
                      {CE->getArgs(), CE->getNumArgs()});
+  // Track declarations that are moved via std::move.
+  // This is a flow-insensitive approximation: once a declaration is moved
+  // anywhere in the function, it's treated as moved everywhere. We do not
+  // generate expire facts for moved decls to avoid false alarms.
+  if (isStdMove(CE->getDirectCallee()))
+    if (CE->getNumArgs() == 1)
+      if (auto *DRE =
+              dyn_cast<DeclRefExpr>(CE->getArg(0)->IgnoreParenImpCasts()))
+        MovedDecls.insert(DRE->getDecl());
 }
 
 void FactsGenerator::VisitCXXNullPtrLiteralExpr(
@@ -395,6 +409,11 @@ void FactsGenerator::handleLifetimeEnds(const 
CFGLifetimeEnds &LifetimeEnds) {
   // Iterate through all loans to see if any expire.
   for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
     if (const auto *BL = dyn_cast<PathLoan>(Loan)) {
+      // Skip loans for declarations that have been moved. When a value is
+      // moved, the original owner no longer has ownership and its destruction
+      // should not cause the loan to expire, preventing false positives.
+      if (MovedDecls.contains(BL->getAccessPath().getAsValueDecl()))
+        continue;
       // Check if the loan is for a stack variable and if that variable
       // is the one being destructed.
       const AccessPath AP = BL->getAccessPath();

diff  --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 6535245853c7f..0b1962b7cb651 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1,9 +1,14 @@
 // RUN: %clang_cc1 -fsyntax-only -fexperimental-lifetime-safety 
-Wexperimental-lifetime-safety -Wno-dangling -verify %s
 
+#include "Inputs/lifetime-analysis.h"
+
 struct View;
 
 struct [[gsl::Owner]] MyObj {
   int id;
+  MyObj();
+  MyObj(int);
+  MyObj(const MyObj&);
   ~MyObj() {}  // Non-trivial destructor
   MyObj operator+(MyObj);
   
@@ -1392,3 +1397,37 @@ void add(int c, MyObj* node) {
   arr[4] = node;
 }
 } // namespace CppCoverage
+
+namespace do_not_warn_on_std_move {
+void silenced() {
+  MyObj b;
+  View v;
+  {
+    MyObj a;
+    v = a;
+    b = std::move(a); // No warning for 'a' being moved.
+  }
+  (void)v;
+}
+
+void silenced_flow_insensitive(bool c) {
+  MyObj a;
+  View v = a;
+  if (c) {
+    MyObj b = std::move(a);
+  }
+  (void)v;
+}
+
+// FIXME: Silence when move arg is not a declref.
+void take(MyObj&&);
+void not_silenced_via_conditional(bool cond) {
+  View v;
+  {
+    MyObj a, b;
+    v = cond ? a : b; // expected-warning 2 {{object whose reference }}
+    take(std::move(cond ? a : b));
+  }         // expected-note 2 {{destroyed here}}
+  (void)v;  // expected-note 2 {{later used here}}
+}
+} // namespace do_not_warn_on_std_move


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

Reply via email to