https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/178670
>From b6e7a711b257585d50cee4736be35f9f4c8a0825 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Thu, 29 Jan 2026 08:26:16 +0000 Subject: [PATCH] Revisit handling moved origins --- .../Analyses/LifetimeSafety/Checker.h | 3 +- .../Analysis/Analyses/LifetimeSafety/Facts.h | 23 ++++ .../Analyses/LifetimeSafety/FactsGenerator.h | 19 +-- .../Analyses/LifetimeSafety/LifetimeSafety.h | 8 +- .../Analysis/Analyses/LifetimeSafety/Loans.h | 7 + .../Analyses/LifetimeSafety/MovedLoans.h | 45 +++++++ clang/include/clang/Basic/DiagnosticGroups.td | 10 +- .../clang/Basic/DiagnosticSemaKinds.td | 17 ++- .../Analysis/LifetimeSafety/CMakeLists.txt | 1 + clang/lib/Analysis/LifetimeSafety/Checker.cpp | 36 ++++-- clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 + clang/lib/Analysis/LifetimeSafety/Facts.cpp | 7 + .../LifetimeSafety/FactsGenerator.cpp | 56 ++++---- .../LifetimeSafety/LifetimeSafety.cpp | 28 ++-- .../LifetimeSafety/LoanPropagation.cpp | 1 + .../Analysis/LifetimeSafety/MovedLoans.cpp | 121 ++++++++++++++++++ clang/lib/Sema/AnalysisBasedWarnings.cpp | 35 +++-- clang/test/Sema/Inputs/lifetime-analysis.h | 4 + .../Sema/warn-lifetime-analysis-nocfg.cpp | 21 +-- .../warn-lifetime-safety-dangling-field.cpp | 29 +++-- clang/test/Sema/warn-lifetime-safety.cpp | 54 +++++--- 21 files changed, 422 insertions(+), 106 deletions(-) create mode 100644 clang/include/clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h create mode 100644 clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h index 5c631c92c0167..1ad3f9da61f60 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Checker.h @@ -26,8 +26,9 @@ namespace clang::lifetimes::internal { /// examining loan expiration points and checking if any live origins hold /// the expired loan. void runLifetimeChecker(const LoanPropagationAnalysis &LoanPropagation, + const MovedLoansAnalysis &MovedLoans, const LiveOriginsAnalysis &LiveOrigins, - const FactManager &FactMgr, AnalysisDeclContext &ADC, + FactManager &FactMgr, AnalysisDeclContext &ADC, LifetimeSafetySemaHelper *SemaHelper); } // namespace clang::lifetimes::internal diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index d948965af34d5..f2c72d6955930 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -44,6 +44,8 @@ class Fact { OriginFlow, /// An origin is used (eg. appears as l-value expression like DeclRefExpr). Use, + /// An origin that is moved (e.g., passed to an rvalue reference parameter). + MovedOrigin, /// A marker for a specific point in the code, for testing. TestPoint, /// An origin that escapes the function scope (e.g., via return). @@ -219,6 +221,27 @@ class UseFact : public Fact { const OriginManager &OM) const override; }; +/// Top-level origin of the expression which was found to be moved, e.g, when +/// being used as an argument to an r-value reference parameter. +class MovedOriginFact : public Fact { + const OriginID MovedOrigin; + const Expr *MoveExpr; + +public: + static bool classof(const Fact *F) { + return F->getKind() == Kind::MovedOrigin; + } + + MovedOriginFact(const Expr *MoveExpr, OriginID MovedOrigin) + : Fact(Kind::MovedOrigin), MovedOrigin(MovedOrigin), MoveExpr(MoveExpr) {} + + OriginID getMovedOrigin() const { return MovedOrigin; } + const Expr *getMoveExpr() const { return MoveExpr; } + + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override; +}; + /// A dummy-fact used to mark a specific point in the code for testing. /// It is generated by recognizing a `void("__lifetime_test_point_...")` cast. class TestPointFact : public Fact { diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 8b45337bee218..b05cd2fdca83f 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -67,6 +67,16 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { void handleGSLPointerConstruction(const CXXConstructExpr *CCE); + /// Detects arguments passed to rvalue reference parameters and creates + /// MovedOriginFact for them. This is a flow-insensitive approximation: once + /// an origin is moved anywhere in the function, all loans from that origin + /// are marked as potentially 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 as they are anyway surfaced as strict + /// warning. + void handleMovedArgsInCall(const FunctionDecl *FD, + ArrayRef<const Expr *> Args); + /// Checks if a call-like expression creates a borrow by passing a value to a /// reference parameter, creating an IssueFact if it does. /// \param IsGslConstruction True if this is a GSL construction where all @@ -110,15 +120,6 @@ 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/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 9f22db20e79b1..6e87951c8961d 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -24,8 +24,10 @@ #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h" #include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h" #include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" +#include "clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h" #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h" #include "clang/Analysis/AnalysisDeclContext.h" +#include <memory> namespace clang::lifetimes { @@ -58,16 +60,18 @@ class LifetimeSafetySemaHelper { virtual ~LifetimeSafetySemaHelper() = default; virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, - SourceLocation FreeLoc, + const Expr *MovedExpr, SourceLocation FreeLoc, Confidence Confidence) {} virtual void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr, + const Expr *MovedExpr, SourceLocation ExpiryLoc, Confidence Confidence) {} virtual void reportDanglingField(const Expr *IssueExpr, const FieldDecl *Field, + const Expr *MovedExpr, SourceLocation ExpiryLoc) {} // Suggests lifetime bound annotations for function paramters. @@ -107,6 +111,7 @@ void collectLifetimeStats(AnalysisDeclContext &AC, OriginManager &OM, struct LifetimeFactory { OriginLoanMap::Factory OriginMapFactory{/*canonicalize=*/false}; LoanSet::Factory LoanSetFactory{/*canonicalize=*/false}; + MovedLoansMap::Factory MovedLoansMapFactory{/*canonicalize=*/false}; LivenessMap::Factory LivenessMapFactory{/*canonicalize=*/false}; }; @@ -133,6 +138,7 @@ class LifetimeSafetyAnalysis { std::unique_ptr<FactManager> FactMgr; std::unique_ptr<LiveOriginsAnalysis> LiveOrigins; std::unique_ptr<LoanPropagationAnalysis> LoanPropagation; + std::unique_ptr<MovedLoansAnalysis> MovedLoans; }; } // namespace internal } // namespace clang::lifetimes diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h index a366e3e811cbf..29bf4b374667f 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h @@ -52,6 +52,12 @@ struct AccessPath { const clang::MaterializeTemporaryExpr *getAsMaterializeTemporaryExpr() const { return P.dyn_cast<const clang::MaterializeTemporaryExpr *>(); } + + bool operator==(const AccessPath &RHS) const { + return getAsValueDecl() == RHS.getAsValueDecl() && + getAsMaterializeTemporaryExpr() == + RHS.getAsMaterializeTemporaryExpr(); + } }; /// An abstract base class for a single "Loan" which represents lending a @@ -164,6 +170,7 @@ class LoanManager { assert(ID.Value < AllLoans.size()); return AllLoans[ID.Value]; } + llvm::ArrayRef<const Loan *> getLoans() const { return AllLoans; } private: diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h new file mode 100644 index 0000000000000..3d8f5b9e177e2 --- /dev/null +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h @@ -0,0 +1,45 @@ +//===- MovedLoans.h - Moved Loans Analysis -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the MovedLoansAnalysis, a forward dataflow analysis that +// tracks which loans have been moved out of their original storage location +// at each program point. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_MOVED_LOANS_H +#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_MOVED_LOANS_H + +#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" + +namespace clang::lifetimes::internal { + +// Map from a loan to an expression responsible for moving the borrowed storage. +using MovedLoansMap = llvm::ImmutableMap<LoanID, const Expr *>; + +class MovedLoansAnalysis { +public: + MovedLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, + const LoanPropagationAnalysis &LoanPropagation, + const LoanManager &LoanMgr, + LoanSet::Factory &LoanSetFactory, + MovedLoansMap::Factory &MovedLoansMapFactory); + ~MovedLoansAnalysis(); + + MovedLoansMap getMovedLoans(ProgramPoint P) const; + +private: + class Impl; + std::unique_ptr<Impl> PImpl; +}; + +} // namespace clang::lifetimes::internal + +#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_MOVED_LOANS_H diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index d36ee57fe7651..2f128fda5e31f 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -539,9 +539,17 @@ def LifetimeSafetyDanglingField : DiagGroup<"lifetime-safety-dangling-field"> { }]; } +def LifetimeSafetyDanglingFieldStrict : DiagGroup<"lifetime-safety-dangling-field-strict"> { + code Documentation = [{ + Warning to detect dangling field references. + This may contain false-positives, e.g. when the borrowed storage is potentially moved and is not destroyed at function exit. + }]; +} + def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive", [LifetimeSafetyDanglingField]>; -def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict">; +def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict", + [LifetimeSafetyDanglingFieldStrict]>; def LifetimeSafety : DiagGroup<"lifetime-safety", [LifetimeSafetyPermissive, LifetimeSafetyStrict]> { diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 807440c107897..c6484295504a4 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10816,13 +10816,19 @@ def warn_lifetime_safety_loan_expires_permissive : Warning< def warn_lifetime_safety_loan_expires_strict : Warning< "object whose reference is captured may not live long enough">, InGroup<LifetimeSafetyStrict>, DefaultIgnore; +def warn_lifetime_safety_loan_expires_moved_strict : Warning< + "object whose reference is captured may not live long enough. " + "This could be false positive as the storage may have been moved later">, + InGroup<LifetimeSafetyStrict>, DefaultIgnore; def warn_lifetime_safety_return_stack_addr_permissive : Warning<"address of stack memory is returned later">, InGroup<LifetimeSafetyPermissive>, DefaultIgnore; -def warn_lifetime_safety_return_stack_addr_strict - : Warning<"address of stack memory may be returned later">, +def warn_lifetime_safety_return_stack_addr_moved_strict + : Warning<"address of stack memory may be returned later. " + "This could be false positive as the storage may have been moved. " + "Consider moving first and then aliasing later to resolve the issue">, InGroup<LifetimeSafetyStrict>, DefaultIgnore; @@ -10830,10 +10836,17 @@ def warn_lifetime_safety_dangling_field : Warning<"address of stack memory escapes to a field">, InGroup<LifetimeSafetyDanglingField>, DefaultIgnore; +def warn_lifetime_safety_dangling_field_moved + : Warning<"address of stack memory escapes to a field. " + "This could be a false positive as the storage may have been moved. " + "Consider moving first and then aliasing later to resolve the issue">, + InGroup<LifetimeSafetyDanglingFieldStrict>, + DefaultIgnore; def note_lifetime_safety_used_here : Note<"later used here">; def note_lifetime_safety_destroyed_here : Note<"destroyed here">; def note_lifetime_safety_returned_here : Note<"returned here">; +def note_lifetime_safety_moved_here : Note<"potentially moved here">; def note_lifetime_safety_dangling_field_here: Note<"this field dangles">; def note_lifetime_safety_escapes_to_field_here: Note<"escapes to this field">; diff --git a/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt b/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt index e5876e747610a..247377c7256d9 100644 --- a/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt +++ b/clang/lib/Analysis/LifetimeSafety/CMakeLists.txt @@ -8,6 +8,7 @@ add_clang_library(clangAnalysisLifetimeSafety LifetimeStats.cpp Loans.cpp LoanPropagation.cpp + MovedLoans.cpp Origins.cpp LINK_LIBS diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index c954a9b14bcdf..59ae665e652a6 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -47,6 +47,7 @@ namespace { struct PendingWarning { SourceLocation ExpiryLoc; // Where the loan expired. llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact; + const Expr *MovedExpr; Confidence ConfidenceLevel; }; @@ -60,18 +61,21 @@ class LifetimeChecker { llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap; llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap; const LoanPropagationAnalysis &LoanPropagation; + const MovedLoansAnalysis &MovedLoans; const LiveOriginsAnalysis &LiveOrigins; - const FactManager &FactMgr; + FactManager &FactMgr; LifetimeSafetySemaHelper *SemaHelper; ASTContext &AST; public: LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation, - const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM, + const MovedLoansAnalysis &MovedLoans, + const LiveOriginsAnalysis &LiveOrigins, FactManager &FM, AnalysisDeclContext &ADC, LifetimeSafetySemaHelper *SemaHelper) - : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM), - SemaHelper(SemaHelper), AST(ADC.getASTContext()) { + : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans), + LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper), + AST(ADC.getASTContext()) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs<ExpireFact>()) @@ -140,6 +144,10 @@ class LifetimeChecker { /// propagation (e.g., a loan may only be held on some execution paths). void checkExpiry(const ExpireFact *EF) { LoanID ExpiredLoan = EF->getLoanID(); + const Expr *MovedExpr = nullptr; + if (auto *ME = MovedLoans.getMovedLoans(EF).lookup(ExpiredLoan)) + MovedExpr = *ME; + LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF); Confidence CurConfidence = Confidence::None; // The UseFact or OriginEscapesFact most indicative of a lifetime error, @@ -166,6 +174,7 @@ class LifetimeChecker { return; FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(), /*BestCausingFact=*/BestCausingFact, + /*MovedExpr=*/MovedExpr, /*ConfidenceLevel=*/CurConfidence}; } @@ -179,19 +188,21 @@ class LifetimeChecker { llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact = Warning.CausingFact; Confidence Confidence = Warning.ConfidenceLevel; + const Expr *MovedExpr = Warning.MovedExpr; SourceLocation ExpiryLoc = Warning.ExpiryLoc; if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) - SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc, - Confidence); + SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr, + ExpiryLoc, Confidence); else if (const auto *OEF = CausingFact.dyn_cast<const OriginEscapesFact *>()) { if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF)) - SemaHelper->reportUseAfterReturn( - IssueExpr, RetEscape->getReturnExpr(), ExpiryLoc, Confidence); + SemaHelper->reportUseAfterReturn(IssueExpr, + RetEscape->getReturnExpr(), + MovedExpr, ExpiryLoc, Confidence); else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF)) SemaHelper->reportDanglingField( - IssueExpr, FieldEscape->getFieldDecl(), ExpiryLoc); + IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc); else llvm_unreachable("Unhandled OriginEscapesFact type"); } else @@ -293,11 +304,12 @@ class LifetimeChecker { } // namespace void runLifetimeChecker(const LoanPropagationAnalysis &LP, - const LiveOriginsAnalysis &LO, - const FactManager &FactMgr, AnalysisDeclContext &ADC, + const MovedLoansAnalysis &MovedLoans, + const LiveOriginsAnalysis &LO, FactManager &FactMgr, + AnalysisDeclContext &ADC, LifetimeSafetySemaHelper *SemaHelper) { llvm::TimeTraceScope TimeProfile("LifetimeChecker"); - LifetimeChecker Checker(LP, LO, FactMgr, ADC, SemaHelper); + LifetimeChecker Checker(LP, MovedLoans, LO, FactMgr, ADC, SemaHelper); } } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h index 05c20d6385368..4de9bbb54af9f 100644 --- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h +++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h @@ -170,6 +170,8 @@ class DataflowAnalysis { return D->transfer(In, *F->getAs<ExpireFact>()); case Fact::Kind::OriginFlow: return D->transfer(In, *F->getAs<OriginFlowFact>()); + case Fact::Kind::MovedOrigin: + return D->transfer(In, *F->getAs<MovedOriginFact>()); case Fact::Kind::OriginEscapes: return D->transfer(In, *F->getAs<OriginEscapesFact>()); case Fact::Kind::Use: @@ -184,6 +186,7 @@ class DataflowAnalysis { Lattice transfer(Lattice In, const IssueFact &) { return In; } Lattice transfer(Lattice In, const ExpireFact &) { return In; } Lattice transfer(Lattice In, const OriginFlowFact &) { return In; } + Lattice transfer(Lattice In, const MovedOriginFact &) { return In; } Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; } Lattice transfer(Lattice In, const UseFact &) { return In; } Lattice transfer(Lattice In, const TestPointFact &) { return In; } diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 1fc72aa0a4259..bc4000e9ce3ef 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -45,6 +45,13 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &, OS << "\n"; } +void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const { + OS << "MovedOrigins ("; + OM.dump(getMovedOrigin(), OS); + OS << ")\n"; +} + void ReturnEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const { OS << "OriginEscapes ("; diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index fb859eeb856af..bdb48b0c81172 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -9,6 +9,8 @@ #include <cassert> #include <string> +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/OperationKinds.h" #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" #include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h" @@ -185,6 +187,9 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) { handleGSLPointerConstruction(CCE); return; } + handleFunctionCall(CCE, CCE->getConstructor(), + {CCE->getArgs(), CCE->getNumArgs()}, + /*IsGslConstruction=*/false); } void FactsGenerator::handleCXXCtorInitializer(const CXXCtorInitializer *CII) { @@ -233,23 +238,9 @@ void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) { } } -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( @@ -453,11 +444,6 @@ 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(); @@ -533,10 +519,29 @@ void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { } } -/// Checks if a call-like expression creates a borrow by passing a value to a -/// reference parameter, creating an IssueFact if it does. -/// \param IsGslConstruction True if this is a GSL construction where all -/// argument origins should flow to the returned origin. +void FactsGenerator::handleMovedArgsInCall(const FunctionDecl *FD, + ArrayRef<const Expr *> Args) { + unsigned IsInstance = 0; + if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); + Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) + IsInstance = 1; + + // Skip 'this' arg as it cannot be moved. + for (unsigned I = IsInstance; + I < Args.size() && I < FD->getNumParams() + IsInstance; ++I) { + const ParmVarDecl *PVD = FD->getParamDecl(I - IsInstance); + if (!PVD->getType()->isRValueReferenceType()) + continue; + const Expr *Arg = Args[I]; + OriginList *MovedOrigins = getOriginsList(*Arg); + assert(MovedOrigins->getLength() >= 1 && + "unexpected length for r-value reference param"); + // Arg is being moved to this parameter. Mark the origin as moved. + CurrentBlockFacts.push_back(FactMgr.createFact<MovedOriginFact>( + Arg, MovedOrigins->getOuterOriginID())); + } +} + void FactsGenerator::handleFunctionCall(const Expr *Call, const FunctionDecl *FD, ArrayRef<const Expr *> Args, @@ -544,7 +549,10 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, OriginList *CallList = getOriginsList(*Call); // Ignore functions returning values with no origin. FD = getDeclWithMergedLifetimeBoundAttrs(FD); - if (!FD || !CallList) + if (!FD) + return; + handleMovedArgsInCall(FD, Args); + if (!CallList) return; auto IsArgLifetimeBound = [FD](unsigned I) -> bool { const ParmVarDecl *PVD = nullptr; diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp index bf9a65254e8fa..d3b06d6040f7f 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp @@ -56,21 +56,12 @@ void LifetimeSafetyAnalysis::run() { llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis"); const CFG &Cfg = *AC.getCFG(); - DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(), - /*ShowColors=*/true)); FactMgr = std::make_unique<FactManager>(AC, Cfg); FactsGenerator FactGen(*FactMgr, AC); FactGen.run(); - DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC)); - - // Debug print facts for a specific function using - // -debug-only=EnableFilterByFunctionName,YourFunctionNameFoo - DEBUG_WITH_TYPE("EnableFilterByFunctionName", - DebugOnlyFunction(AC, Cfg, *FactMgr)); - /// TODO(opt): Consider optimizing individual blocks before running the /// dataflow analysis. /// 1. Expression Origins: These are assigned once and read at most once, @@ -85,12 +76,27 @@ void LifetimeSafetyAnalysis::run() { LoanPropagation = std::make_unique<LoanPropagationAnalysis>( Cfg, AC, *FactMgr, Factory.OriginMapFactory, Factory.LoanSetFactory); + MovedLoans = std::make_unique<MovedLoansAnalysis>( + Cfg, AC, *FactMgr, *LoanPropagation, FactMgr->getLoanMgr(), + Factory.LoanSetFactory, Factory.MovedLoansMapFactory); + LiveOrigins = std::make_unique<LiveOriginsAnalysis>( Cfg, AC, *FactMgr, Factory.LivenessMapFactory); + + runLifetimeChecker(*LoanPropagation, *MovedLoans, *LiveOrigins, *FactMgr, AC, + SemaHelper); + + DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(), + /*ShowColors=*/true)); + + DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC)); + + // Debug print facts for a specific function using + // -debug-only=EnableFilterByFunctionName,YourFunctionNameFoo + DEBUG_WITH_TYPE("EnableFilterByFunctionName", + DebugOnlyFunction(AC, Cfg, *FactMgr)); DEBUG_WITH_TYPE("LiveOrigins", LiveOrigins->dump(llvm::dbgs(), FactMgr->getTestPoints())); - - runLifetimeChecker(*LoanPropagation, *LiveOrigins, *FactMgr, AC, SemaHelper); } void collectLifetimeStats(AnalysisDeclContext &AC, OriginManager &OM, diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 01a74d30408ea..0996d11f0cdeb 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -63,6 +63,7 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr, Cur = Cur->peelOuterOrigin()) CheckOrigin(Cur->getOuterOriginID()); break; + case Fact::Kind::MovedOrigin: case Fact::Kind::OriginEscapes: case Fact::Kind::Expire: case Fact::Kind::TestPoint: diff --git a/clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp b/clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp new file mode 100644 index 0000000000000..2e248b14d54f3 --- /dev/null +++ b/clang/lib/Analysis/LifetimeSafety/MovedLoans.cpp @@ -0,0 +1,121 @@ + +#include "clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h" +#include "Dataflow.h" +#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" +#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h" +#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h" +#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h" + +namespace clang::lifetimes::internal { +namespace { +struct Lattice { + LoanSet ReachableLoans = LoanSet(nullptr); + MovedLoansMap MovedLoans = MovedLoansMap(nullptr); + + explicit Lattice(LoanSet ReachableLoans, MovedLoansMap MovedLoans) + : ReachableLoans(ReachableLoans), MovedLoans(MovedLoans) {} + + Lattice() = default; + + bool operator==(const Lattice &Other) const { + return ReachableLoans == Other.ReachableLoans && + MovedLoans == Other.MovedLoans; + } + bool operator!=(const Lattice &Other) const { return !(*this == Other); } +}; + +class AnalysisImpl + : public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Forward> { +public: + AnalysisImpl(const CFG &C, AnalysisDeclContext &AC, FactManager &F, + const LoanPropagationAnalysis &LoanPropagation, + const LoanManager &LoanMgr, LoanSet::Factory &LoanSetFactory, + MovedLoansMap::Factory &MovedLoansMapFactory) + : DataflowAnalysis(C, AC, F), LoanPropagation(LoanPropagation), + LoanMgr(LoanMgr), LoanSetFactory(LoanSetFactory), + MovedLoansMapFactory(MovedLoansMapFactory) {} + + using Base::transfer; + + StringRef getAnalysisName() const { return "MovedLoans"; } + + Lattice getInitialState() { return Lattice{}; } + + // TODO: Doc. + Lattice join(Lattice A, Lattice B) { + LoanSet ReachableLoans = + utils::join(A.ReachableLoans, B.ReachableLoans, LoanSetFactory); + MovedLoansMap MovedLoans = utils::join( + A.MovedLoans, B.MovedLoans, MovedLoansMapFactory, + [](const Expr *const *MoveA, const Expr *const *MoveB) -> const Expr * { + assert(MoveA || MoveB); + if (!MoveA) + return *MoveB; + if (!MoveB) + return *MoveA; + return (*MoveA)->getExprLoc() < (*MoveB)->getExprLoc() ? *MoveA + : *MoveB; + }, + utils::JoinKind::Asymmetric); + return Lattice(ReachableLoans, MovedLoans); + } + + Lattice transfer(Lattice In, const IssueFact &F) { + LoanSet ReachableLoans = + LoanSetFactory.add(In.ReachableLoans, F.getLoanID()); + return Lattice(ReachableLoans, In.MovedLoans); + } + + Lattice transfer(Lattice In, const MovedOriginFact &F) { + LoanSet ReachableLoans = In.ReachableLoans; + MovedLoansMap MovedLoans = In.MovedLoans; + OriginID MovedOrigin = F.getMovedOrigin(); + for (LoanID LID : LoanPropagation.getLoans(MovedOrigin, &F)) { + const Loan *MovedLoan = LoanMgr.getLoan(LID); + auto *PL = dyn_cast<PathLoan>(MovedLoan); + if (!PL) + continue; + for (LoanID ReachableLID : In.ReachableLoans) { + const Loan *ReachableLoan = LoanMgr.getLoan(ReachableLID); + auto *RL = dyn_cast<PathLoan>(ReachableLoan); + if (!RL) + continue; + if (RL->getAccessPath() == PL->getAccessPath()) { + MovedLoans = MovedLoansMapFactory.add(MovedLoans, ReachableLID, + F.getMoveExpr()); + } + } + } + return Lattice(ReachableLoans, MovedLoans); + } + + MovedLoansMap getMovedLoans(ProgramPoint P) { return getState(P).MovedLoans; } + +private: + const LoanPropagationAnalysis &LoanPropagation; + const LoanManager &LoanMgr; + LoanSet::Factory &LoanSetFactory; + MovedLoansMap::Factory &MovedLoansMapFactory; +}; +} // namespace + +class MovedLoansAnalysis::Impl final : public AnalysisImpl { + using AnalysisImpl::AnalysisImpl; +}; + +MovedLoansAnalysis::MovedLoansAnalysis( + const CFG &C, AnalysisDeclContext &AC, FactManager &F, + const LoanPropagationAnalysis &LoanPropagation, const LoanManager &LoanMgr, + LoanSet::Factory &LoanSetFactory, + MovedLoansMap::Factory &MovedLoansMapFactory) + : PImpl(std::make_unique<Impl>(C, AC, F, LoanPropagation, LoanMgr, + LoanSetFactory, MovedLoansMapFactory)) { + PImpl->run(); +} + +MovedLoansAnalysis::~MovedLoansAnalysis() = default; + +MovedLoansMap MovedLoansAnalysis::getMovedLoans(ProgramPoint P) const { + return PImpl->getMovedLoans(P); +} +} // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 0c96b0afef1a7..858d5b7f67b58 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2880,32 +2880,46 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { LifetimeSafetySemaHelperImpl(Sema &S) : S(S) {} void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, - SourceLocation FreeLoc, Confidence C) override { + const Expr *MovedExpr, SourceLocation FreeLoc, + Confidence C) override { S.Diag(IssueExpr->getExprLoc(), - C == Confidence::Definite + MovedExpr ? diag::warn_lifetime_safety_loan_expires_moved_strict + : C == Confidence::Definite ? diag::warn_lifetime_safety_loan_expires_permissive : diag::warn_lifetime_safety_loan_expires_strict) << IssueExpr->getSourceRange(); + if (MovedExpr) + S.Diag(MovedExpr->getExprLoc(), diag::note_lifetime_safety_moved_here) + << MovedExpr->getSourceRange(); S.Diag(FreeLoc, diag::note_lifetime_safety_destroyed_here); S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here) << UseExpr->getSourceRange(); } void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr, - SourceLocation ExpiryLoc, Confidence C) override { + const Expr *MovedExpr, SourceLocation ExpiryLoc, + Confidence C) override { S.Diag(IssueExpr->getExprLoc(), - C == Confidence::Definite - ? diag::warn_lifetime_safety_return_stack_addr_permissive - : diag::warn_lifetime_safety_return_stack_addr_strict) + MovedExpr ? diag::warn_lifetime_safety_return_stack_addr_moved_strict + : diag::warn_lifetime_safety_return_stack_addr_permissive) << IssueExpr->getSourceRange(); + if (MovedExpr) + S.Diag(MovedExpr->getExprLoc(), diag::note_lifetime_safety_moved_here) + << MovedExpr->getSourceRange(); S.Diag(ReturnExpr->getExprLoc(), diag::note_lifetime_safety_returned_here) << ReturnExpr->getSourceRange(); } void reportDanglingField(const Expr *IssueExpr, const FieldDecl *DanglingField, + const Expr *MovedExpr, SourceLocation ExpiryLoc) override { - S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_dangling_field) + S.Diag(IssueExpr->getExprLoc(), + MovedExpr ? diag::warn_lifetime_safety_dangling_field_moved + : diag::warn_lifetime_safety_dangling_field) << IssueExpr->getSourceRange(); + if (MovedExpr) + S.Diag(MovedExpr->getExprLoc(), diag::note_lifetime_safety_moved_here) + << MovedExpr->getSourceRange(); S.Diag(DanglingField->getLocation(), diag::note_lifetime_safety_dangling_field_here) << DanglingField->getEndLoc(); @@ -3117,10 +3131,13 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( D->getBeginLoc()) || !Diags.isIgnored(diag::warn_lifetime_safety_loan_expires_strict, D->getBeginLoc()) || - !Diags.isIgnored(diag::warn_lifetime_safety_return_stack_addr_permissive, + !Diags.isIgnored(diag::warn_lifetime_safety_loan_expires_moved_strict, D->getBeginLoc()) || - !Diags.isIgnored(diag::warn_lifetime_safety_return_stack_addr_strict, + !Diags.isIgnored(diag::warn_lifetime_safety_return_stack_addr_permissive, D->getBeginLoc()) || + !Diags.isIgnored( + diag::warn_lifetime_safety_return_stack_addr_moved_strict, + D->getBeginLoc()) || !Diags.isIgnored(diag::warn_lifetime_safety_noescape_escapes, D->getBeginLoc()); bool EnableLifetimeSafetyAnalysis = diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h index 4f881d463ebcc..8a3139f3db3d3 100644 --- a/clang/test/Sema/Inputs/lifetime-analysis.h +++ b/clang/test/Sema/Inputs/lifetime-analysis.h @@ -85,6 +85,8 @@ template<class _Mystr> struct iter { template<typename T> struct basic_string { basic_string(); + basic_string(const basic_string<T> &); + basic_string(basic_string<T> &&); basic_string(const T *); ~basic_string(); const T *c_str() const; @@ -96,6 +98,8 @@ using string = basic_string<char>; template<typename T> struct unique_ptr { + unique_ptr(); + unique_ptr(unique_ptr<T>&&); ~unique_ptr(); T &operator*(); T *get() const; diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp index c82cf41b07361..d8631e5a7cf74 100644 --- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp @@ -349,9 +349,11 @@ const char *trackThroughMultiplePointer() { struct X { X(std::unique_ptr<int> up) : - pointee(*up), pointee2(up.get()), pointer(std::move(up)) {} - int &pointee; - int *pointee2; + pointee(*up), // cfg-field-warning {{may have been moved.}} + pointee2(up.get()), // cfg-field-warning {{may have been moved.}} + pointer(std::move(up)) {} // cfg-field-note 2 {{potentially moved here}} + int &pointee; // cfg-field-note {{this field dangles}} + int *pointee2; // cfg-field-note {{this field dangles}} std::unique_ptr<int> pointer; }; @@ -360,11 +362,11 @@ struct [[gsl::Owner]] XOwner { }; struct X2 { // A common usage that moves the passing owner to the class. - // verify no warning on this case. + // verify a strict warning on this case. X2(XOwner owner) : - pointee(owner.get()), - owner(std::move(owner)) {} - int* pointee; + pointee(owner.get()), // cfg-field-warning {{may have been moved.}} + owner(std::move(owner)) {} // cfg-field-note {{potentially moved here}} + int* pointee; // cfg-field-note {{this field dangles}} XOwner owner; }; @@ -1108,9 +1110,8 @@ struct Foo2 { }; struct Test { - Test(Foo2 foo) : bar(foo.bar.get()), // OK \ - // FIXME: cfg-field-warning {{address of stack memory escapes to a field}} - storage(std::move(foo.bar)) {}; + Test(Foo2 foo) : bar(foo.bar.get()), // cfg-field-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} + storage(std::move(foo.bar)) {}; // cfg-field-note {{potentially moved here}} Bar* bar; // cfg-field-note {{this field dangles}} std::unique_ptr<Bar> storage; diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp index dfc43e49906f2..fa45458371012 100644 --- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp +++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp @@ -24,15 +24,26 @@ struct CtorInitLifetimeBound { }; struct CtorInitButMoved { - std::string_view view; - CtorInitButMoved(std::string s) : view(s) { takeString(std::move(s)); } + std::string_view view; // expected-note {{this field dangles}} + CtorInitButMoved(std::string s) + : view(s) { // expected-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} + takeString(std::move(s)); // expected-note {{potentially moved here}} + } }; struct CtorInitButMovedOwned { + std::string_view view; // expected-note {{this field dangles}} + std::string owned; + CtorInitButMovedOwned(std::string s) + : view(s), // expected-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} + owned(std::move(s)) {} // expected-note {{potentially moved here}} +}; + +struct CtorInitButMovedOwnedOrderedCorrectly { std::string owned; std::string_view view; - CtorInitButMovedOwned(std::string s) : view(s), owned(std::move(s)) {} - CtorInitButMovedOwned(Dummy<1>, std::string s) : owned(std::move(s)), view(owned) {} + CtorInitButMovedOwnedOrderedCorrectly(std::string s) + :owned(std::move(s)), view(owned) {} }; struct CtorInitMultipleViews { @@ -65,8 +76,8 @@ struct CtorPointerField { }; struct MemberSetters { - std::string_view view; // expected-note 5 {{this field dangles}} - const char* p; // expected-note 5 {{this field dangles}} + std::string_view view; // expected-note 6 {{this field dangles}} + const char* p; // expected-note 6 {{this field dangles}} void setWithParam(std::string s) { view = s; // expected-warning {{address of stack memory escapes to a field}} @@ -98,9 +109,9 @@ struct MemberSetters { void setWithLocalButMoved() { std::string s; - view = s; - p = s.data(); - takeString(std::move(s)); + view = s; // expected-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} + p = s.data(); // expected-warning-re {{address of stack memory escapes to a field. {{.*}} may have been moved}} + takeString(std::move(s)); // expected-note 2 {{potentially moved here}} } void setWithGlobal() { diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 2976c809e389c..96de471b649a9 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -10,6 +10,8 @@ struct [[gsl::Owner]] MyObj { MyObj(); MyObj(int); MyObj(const MyObj&); + MyObj(MyObj&&); + MyObj& operator=(MyObj&&); ~MyObj() {} // Non-trivial destructor MyObj operator+(MyObj); @@ -1404,39 +1406,57 @@ void add(int c, MyObj* node) { } } // namespace CppCoverage -namespace do_not_warn_on_std_move { +namespace strict_warn_on_move { void silenced() { MyObj b; View v; { MyObj a; - v = a; - b = std::move(a); // No warning for 'a' being moved. - } - (void)v; + v = a; // expected-warning-re {{object whose reference {{.*}} may have been moved}} + b = std::move(a); // expected-note {{potentially moved here}} + } // expected-note {{destroyed here}} + (void)v; // expected-note {{later used here}} } -void silenced_flow_insensitive(bool c) { - MyObj a; - View v = a; - if (c) { - MyObj b = std::move(a); - } - (void)v; +void flow_sensitive(bool c) { + View v; + { + MyObj a; + if (c) { + MyObj b = std::move(a); + return; + } + v = a; // expected-warning {{object whose reference}} + } // expected-note {{destroyed here}} + (void)v; // expected-note {{later used here}} } -// FIXME: Silence when move arg is not a declref. void take(MyObj&&); -void not_silenced_via_conditional(bool cond) { +void detect_conditional(bool cond) { View v; { MyObj a, b; - v = cond ? a : b; // expected-warning 2 {{object whose reference }} - take(std::move(cond ? a : b)); + v = cond ? a : b; // expected-warning-re 2 {{object whose reference {{.*}} may have been moved}} + take(std::move(cond ? a : b)); // expected-note 2 {{potentially moved here}} } // expected-note 2 {{destroyed here}} (void)v; // expected-note 2 {{later used here}} } -} // namespace do_not_warn_on_std_move + +void wrong_use_of_move_is_permissive() { + View v; + { + MyObj a; + v = std::move(a); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)v; // expected-note {{later used here}} + const int* p; + { + MyObj a; + p = std::move(a).getData(); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)p; // expected-note {{later used here}} +} +} // namespace strict_warn_on_move // Implicit this annotations with redecls. namespace GH172013 { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
