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
