[llvm-branch-commits] [clang] [LifetimeSafety] Track moved declarations to prevent false positives (PR #170007)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Dec 8 06:54:56 PST 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/170007
>From 444baf2e0a43002cdcc8f516cd94deab30c3be57 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 29 Nov 2025 16:43:06 +0000
Subject: [PATCH] std_move false positive
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 5 ++++
.../LifetimeSafety/FactsGenerator.cpp | 23 +++++++++++++++++++
clang/test/Sema/warn-lifetime-safety.cpp | 18 +++++++++++++++
3 files changed, 46 insertions(+)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index ffe9101606a97..ac97991342b86 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -102,6 +102,11 @@ 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;
+
+ // Tracks declarations that have been moved via std::move. This is used to
+ // prevent false positives when the original owner is destroyed after the
+ // value has been moved. This tracking is flow-insensitive.
+ llvm::DenseSet<const ValueDecl *> MovedDecls;
};
} // namespace clang::lifetimes::internal
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 3ff817de0d18a..bf617261cc23b 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -183,9 +183,27 @@ 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. 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.
+ 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(
@@ -364,6 +382,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().D))
+ continue;
// Check if the loan is for a stack variable and if that variable
// is the one being destructed.
if (BL->getAccessPath().D == LifetimeEndsVD)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index f22c73cfeb784..97a79cc4ce102 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);
@@ -1297,3 +1302,16 @@ 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;
+}
+} // namespace do_not_warn_on_std_move
More information about the llvm-branch-commits
mailing list