[clang] [LifetimeSafety] Revisit tracking moved objects (PR #178670)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 29 12:51:42 PST 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/178670
>From ce28564000d46fa1cfce38917161cfe8571e55ba Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 | 15 ++-
.../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 | 56 ++++++--
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, 449 insertions(+), 107 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..e68d6ffb94231 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,7 +170,14 @@ class LoanManager {
assert(ID.Value < AllLoans.size());
return AllLoans[ID.Value];
}
+
+ Loan *getLoan(LoanID ID) {
+ assert(ID.Value < AllLoans.size());
+ return AllLoans[ID.Value];
+ }
+
llvm::ArrayRef<const Loan *> getLoans() const { return AllLoans; }
+ llvm::ArrayRef<Loan *> getLoans() { return AllLoans; }
private:
LoanID getNextLoanID() { return NextLoanID++; }
@@ -172,7 +185,7 @@ class LoanManager {
LoanID NextLoanID{0};
/// TODO(opt): Profile and evaluate the usefullness of small buffer
/// optimisation.
- llvm::SmallVector<const Loan *> AllLoans;
+ llvm::SmallVector<Loan *> AllLoans;
llvm::BumpPtrAllocator LoanAllocator;
};
} // namespace clang::lifetimes::internal
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..18ae11f23bd03 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>())
@@ -127,6 +131,26 @@ class LifetimeChecker {
}
}
+ // void handleMovedOrigins(const MovedOriginFact *MOF) {
+ // OriginID OID = MOF->getMovedOrigin();
+ // // Mark all the access paths of all loans which share the same access path
+ // // with the loans held by the moved origin.
+ // LoanSet MovedLoans = LoanPropagation.getLoans(OID, MOF);
+ // for (LoanID MovedLoan : MovedLoans) {
+ // const Loan *MovedL = FactMgr.getLoanMgr().getLoan(MovedLoan);
+ // const auto *MovedPL = dyn_cast<PathLoan>(MovedL);
+ // if (!MovedPL)
+ // continue;
+ // for (Loan *L : FactMgr.getLoanMgr().getLoans()) {
+ // const auto *PL = dyn_cast<PathLoan>(L);
+ // if (!PL)
+ // continue;
+ // if (PL->getAccessPath() == MovedPL->getAccessPath())
+ // FactMgr.getLoanMgr().setAsMoved(PL->getID(), MOF->getMoveExpr());
+ // }
+ // }
+ // }
+
/// Checks for use-after-free & use-after-return errors when a loan expires.
///
/// This method examines all live origins at the expiry point and determines
@@ -140,6 +164,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 +194,7 @@ class LifetimeChecker {
return;
FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
/*BestCausingFact=*/BestCausingFact,
+ /*MovedExpr=*/MovedExpr,
/*ConfidenceLevel=*/CurConfidence};
}
@@ -179,19 +208,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 +324,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..fab13bea82da7 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 {
More information about the cfe-commits
mailing list