[clang] [LifetimeSafety] Track origins through std::function (PR #191123)
Zhijie Wang via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 13 22:46:59 PDT 2026
https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/191123
>From a1778114f5e7214562e5c7832f255f968c9fda99 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 01/12] [LifetimeSafety] Track origins through std::function
---
.../LifetimeSafety/LifetimeAnnotations.h | 4 ++
.../LifetimeSafety/FactsGenerator.cpp | 26 ++++++++-
.../LifetimeSafety/LifetimeAnnotations.cpp | 8 +++
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 15 +++--
clang/test/Sema/Inputs/lifetime-analysis.h | 10 ++++
clang/test/Sema/warn-lifetime-safety.cpp | 57 +++++++++++++++++++
6 files changed, 109 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index aa9ae4b2a5e6a..098c15f4a7fb4 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -80,6 +80,10 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD);
// https://en.cppreference.com/w/cpp/container#Iterator_invalidation
bool isContainerInvalidationMethod(const CXXMethodDecl &MD);
+/// Returns true for standard library callable wrappers (e.g., std::function)
+/// that can propagate the stored lambda's origins.
+bool isStdCallableWrapperType(const CXXRecordDecl *RD);
+
} // namespace clang::lifetimes
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 2a2ef88987286..8f86f35ca2f34 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -190,9 +190,8 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
return;
}
// For defaulted (implicit or `= default`) copy/move constructors, propagate
- // origins directly. User-defined copy/move constructors have opaque semantics
- // and fall through to `handleFunctionCall`, where [[clang::lifetimebound]] is
- // needed to propagate origins.
+ // origins directly. User-defined copy/move constructors are not handled here
+ // as they have opaque semantics.
if (CCE->getConstructor()->isCopyOrMoveConstructor() &&
CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 &&
hasOrigins(CCE->getType())) {
@@ -202,6 +201,16 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
return;
}
}
+ // Standard library callable wrappers (e.g., std::function) propagate the
+ // stored lambda's origins.
+ if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
+ RD && isStdCallableWrapperType(RD) && CCE->getNumArgs() == 1) {
+ const Expr *Arg = CCE->getArg(0);
+ if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
+ flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
+ return;
+ }
+ }
handleFunctionCall(CCE, CCE->getConstructor(),
{CCE->getArgs(), CCE->getNumArgs()},
/*IsGslConstruction=*/false);
@@ -400,6 +409,10 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
} else
markUseAsWrite(DRE_LHS);
}
+ // RHS may not have tracked origins (e.g., assigning a non-lambda functor
+ // to a std::function). Skip the flow in that case.
+ if (!RHSList)
+ return;
// Kill the old loans of the destination origin and flow the new loans
// from the source origin.
flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
@@ -493,6 +506,13 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
handleAssignment(OCE->getArg(0), OCE->getArg(1));
return;
}
+ // Standard library callable wrappers (e.g., std::function) can propagate
+ // the stored lambda's origins.
+ if (const auto *RD = LHSTy->getAsCXXRecordDecl();
+ RD && isStdCallableWrapperType(RD)) {
+ handleAssignment(OCE->getArg(0), OCE->getArg(1));
+ return;
+ }
// Other tracked types: only defaulted operator= propagates origins.
// User-defined operator= has opaque semantics, so don't handle them now.
if (const auto *MD =
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 4852f444a51b3..27d95821dd0b4 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -382,4 +382,12 @@ bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
return InvalidatingMethods->contains(MD.getName());
}
+
+bool isStdCallableWrapperType(const CXXRecordDecl *RD) {
+ if (!RD || !isInStlNamespace(RD))
+ return false;
+ StringRef Name = getName(*RD);
+ return Name == "function" || Name == "move_only_function";
+}
+
} // namespace clang::lifetimes
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index fdd2671dee2e0..cda537bf98f75 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -107,14 +107,13 @@ bool OriginManager::hasOrigins(QualType QT) const {
const auto *RD = QT->getAsCXXRecordDecl();
if (!RD)
return false;
- // TODO: Limit to lambdas for now. This will be extended to user-defined
- // structs with pointer-like fields.
- if (!RD->isLambda())
- return false;
- for (const auto *FD : RD->fields())
- if (hasOrigins(FD->getType()))
- return true;
- return false;
+ // Standard library callable wrappers (e.g., std::function) can propagate the
+ // stored lambda's origins.
+ if (isStdCallableWrapperType(RD))
+ return true;
+ // TODO: Extend origin tracking to user-defined structs that may carry
+ // origins.
+ return RD->isLambda();
}
/// Determines if an expression has origins that need to be tracked.
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index d1e847d20cc50..58ba1aa0c03c3 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -268,4 +268,14 @@ struct true_type {
template<class T> struct is_pointer : false_type {};
template<class T> struct is_pointer<T*> : true_type {};
template<class T> struct is_pointer<T* const> : true_type {};
+
+template<class> class function;
+template<class R, class... Args>
+class function<R(Args...)> {
+public:
+ template<class F> function(F) {}
+ template<class F> function& operator=(F) { return *this; }
+ ~function();
+};
+
}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 2aca44daeb0aa..8e1f1b395875e 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2552,3 +2552,60 @@ struct Y : X {
}
};
} // namespace base_class_fields
+
+namespace callable_wrappers {
+
+std::function<void()> direct_return() {
+ int x;
+ return [&x]() { (void)x; }; // expected-warning {{address of stack memory is returned later}} \
+ // expected-note {{returned here}}
+}
+
+std::function<void()> copy_function() {
+ int x;
+ std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{address of stack memory is returned later}}
+ std::function<void()> f2 = f;
+ return f2; // expected-note {{returned here}}
+}
+
+std::function<void()> reassign_safe_then_unsafe() {
+ static int safe = 1;
+ int local = 2;
+ std::function<void()> f = []() { (void)safe; };
+ f = [&local]() { (void)local; }; // expected-warning {{address of stack memory is returned later}}
+ return f; // expected-note {{returned here}}
+}
+
+std::function<void()> reassign_unsafe_then_safe() {
+ static int safe = 1;
+ int local = 2;
+ std::function<void()> f = [&local]() { (void)local; };
+ f = []() { (void)safe; };
+ return f;
+}
+
+std::function<void()> non_capturing_lambda() {
+ return []() {};
+}
+
+void free_function();
+
+std::function<void()> reassign_lambda_to_function_pointer() {
+ int local;
+ std::function<void()> f = [&local]() { (void)local; };
+ f = &free_function;
+ return f;
+}
+
+struct Functor { void operator()() const; };
+
+// FIXME: False positive.
+std::function<void()> reassign_lambda_to_functor() {
+ int local;
+ Functor c;
+ std::function<void()> f = [&local]() { (void)local; }; // expected-warning {{address of stack memory is returned later}}
+ f = c;
+ return f; // expected-note {{returned here}}
+}
+
+} // namespace callable_wrappers
>From c36bfcda901cede1701a80a3d056f277316ebb22 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sat, 11 Apr 2026 16:52:54 -0700
Subject: [PATCH 02/12] Add a kill-only mechanism (e.g., OriginFlowFact with no
source) to clear old loans when the RHS has no origins
---
.../clang/Analysis/Analyses/LifetimeSafety/Facts.h | 9 ++++++---
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 10 ++++++----
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 11 ++++++++---
clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 10 ++++++----
clang/lib/Analysis/LifetimeSafety/Origins.cpp | 11 ++++++++---
clang/test/Sema/warn-lifetime-safety.cpp | 5 ++---
6 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 6be8f6e455bc2..265f783183553 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -130,7 +130,9 @@ class ExpireFact : public Fact {
class OriginFlowFact : public Fact {
OriginID OIDDest;
- OriginID OIDSrc;
+ // The source origin to flow from. Absent when only clearing the destination's
+ // loans.
+ std::optional<OriginID> OIDSrc;
// True if the destination origin should be killed (i.e., its current loans
// cleared) before the source origin's loans are flowed into it.
bool KillDest;
@@ -140,12 +142,13 @@ class OriginFlowFact : public Fact {
return F->getKind() == Kind::OriginFlow;
}
- OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
+ OriginFlowFact(OriginID OIDDest, std::optional<OriginID> OIDSrc,
+ bool KillDest)
: Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc),
KillDest(KillDest) {}
OriginID getDestOriginID() const { return OIDDest; }
- OriginID getSrcOriginID() const { return OIDSrc; }
+ std::optional<OriginID> getSrcOriginID() const { return OIDSrc; }
bool getKillDest() const { return KillDest; }
void dump(llvm::raw_ostream &OS, const LoanManager &,
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 1bc0521a72359..7a04fd1dfe2f2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -43,10 +43,12 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << "\tDest: ";
OM.dump(getDestOriginID(), OS);
OS << "\n";
- OS << "\tSrc: ";
- OM.dump(getSrcOriginID(), OS);
- OS << (getKillDest() ? "" : ", Merge");
- OS << "\n";
+ if (getSrcOriginID()) {
+ OS << "\tSrc: ";
+ OM.dump(*getSrcOriginID(), OS);
+ OS << (getKillDest() ? "" : ", Merge");
+ OS << "\n";
+ }
}
void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 8f86f35ca2f34..b6c6d37d4cc20 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -409,10 +409,15 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
} else
markUseAsWrite(DRE_LHS);
}
- // RHS may not have tracked origins (e.g., assigning a non-lambda functor
- // to a std::function). Skip the flow in that case.
- if (!RHSList)
+ if (!RHSList) {
+ // RHS has no tracked origins (e.g., assigning a non-lambda functor to a
+ // std::function). Emit a kill-only flow to clear old loans.
+ if (OriginList *LHSInner = LHSList->peelOuterOrigin())
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ LHSInner->getOuterOriginID(), /*OIDSrc=*/std::nullopt,
+ /*KillDest=*/true));
return;
+ }
// Kill the old loans of the destination origin and flow the new loans
// from the source origin.
flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index e437fb7d41268..a53e8d0419282 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -55,7 +55,8 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
case Fact::Kind::OriginFlow: {
const auto *OF = F->getAs<OriginFlowFact>();
CheckOrigin(OF->getDestOriginID());
- CheckOrigin(OF->getSrcOriginID());
+ if (OF->getSrcOriginID())
+ CheckOrigin(*OF->getSrcOriginID());
break;
}
case Fact::Kind::Use:
@@ -168,14 +169,15 @@ class AnalysisImpl
/// A flow from source to destination. If `KillDest` is true, this replaces
/// the destination's loans with the source's. Otherwise, the source's loans
- /// are merged into the destination's.
+ /// are merged into the destination's. If the source is nullopt, this is a
+ /// kill-only flow that clears the destination without adding new loans.
Lattice transfer(Lattice In, const OriginFlowFact &F) {
OriginID DestOID = F.getDestOriginID();
- OriginID SrcOID = F.getSrcOriginID();
LoanSet DestLoans =
F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
- LoanSet SrcLoans = getLoans(In, SrcOID);
+ LoanSet SrcLoans = F.getSrcOriginID() ? getLoans(In, *F.getSrcOriginID())
+ : LoanSetFactory.getEmptySet();
LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
return setLoans(In, DestOID, MergedLoans);
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index cda537bf98f75..033cbdd75352c 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -111,9 +111,14 @@ bool OriginManager::hasOrigins(QualType QT) const {
// stored lambda's origins.
if (isStdCallableWrapperType(RD))
return true;
- // TODO: Extend origin tracking to user-defined structs that may carry
- // origins.
- return RD->isLambda();
+ // TODO: Limit to lambdas for now. This will be extended to user-defined
+ // structs with pointer-like fields.
+ if (!RD->isLambda())
+ return false;
+ for (const auto *FD : RD->fields())
+ if (hasOrigins(FD->getType()))
+ return true;
+ return false;
}
/// Determines if an expression has origins that need to be tracked.
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 8e1f1b395875e..b650cf6ed086c 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2599,13 +2599,12 @@ std::function<void()> reassign_lambda_to_function_pointer() {
struct Functor { void operator()() const; };
-// FIXME: False positive.
std::function<void()> reassign_lambda_to_functor() {
int local;
Functor c;
- std::function<void()> f = [&local]() { (void)local; }; // expected-warning {{address of stack memory is returned later}}
+ std::function<void()> f = [&local]() { (void)local; };
f = c;
- return f; // expected-note {{returned here}}
+ return f;
}
} // namespace callable_wrappers
>From d6f3ea7a9abb2e5e3fe3eaf1711c7f87a9f9c0aa Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 12 Apr 2026 14:49:47 -0700
Subject: [PATCH 03/12] Add a separate KillOriginFact
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 28 +++++++++++++++----
clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 ++
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 17 +++++++----
.../LifetimeSafety/FactsGenerator.cpp | 12 ++++----
.../Analysis/LifetimeSafety/LiveOrigins.cpp | 4 +++
.../LifetimeSafety/LoanPropagation.cpp | 17 +++++++----
6 files changed, 57 insertions(+), 24 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 265f783183553..d375e94ea9771 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,6 +55,9 @@ class Fact {
OriginEscapes,
/// An origin is invalidated (e.g. vector resized).
InvalidateOrigin,
+ /// All loans are cleared from an origin (e.g., assigning a callable without
+ /// tracked origins to std::function).
+ KillOrigin,
};
private:
@@ -130,9 +133,7 @@ class ExpireFact : public Fact {
class OriginFlowFact : public Fact {
OriginID OIDDest;
- // The source origin to flow from. Absent when only clearing the destination's
- // loans.
- std::optional<OriginID> OIDSrc;
+ OriginID OIDSrc;
// True if the destination origin should be killed (i.e., its current loans
// cleared) before the source origin's loans are flowed into it.
bool KillDest;
@@ -142,13 +143,12 @@ class OriginFlowFact : public Fact {
return F->getKind() == Kind::OriginFlow;
}
- OriginFlowFact(OriginID OIDDest, std::optional<OriginID> OIDSrc,
- bool KillDest)
+ OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
: Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc),
KillDest(KillDest) {}
OriginID getDestOriginID() const { return OIDDest; }
- std::optional<OriginID> getSrcOriginID() const { return OIDSrc; }
+ OriginID getSrcOriginID() const { return OIDSrc; }
bool getKillDest() const { return KillDest; }
void dump(llvm::raw_ostream &OS, const LoanManager &,
@@ -319,6 +319,22 @@ class TestPointFact : public Fact {
const OriginManager &) const override;
};
+class KillOriginFact : public Fact {
+ OriginID OID;
+
+public:
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::KillOrigin;
+ }
+
+ KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {}
+
+ OriginID getKilledOrigin() const { return OID; }
+
+ void dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const override;
+};
+
class FactManager {
public:
FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) : OriginMgr(AC) {
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 0f64ac8a36ef7..fc3049c8bec84 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -180,6 +180,8 @@ class DataflowAnalysis {
return D->transfer(In, *F->getAs<TestPointFact>());
case Fact::Kind::InvalidateOrigin:
return D->transfer(In, *F->getAs<InvalidateOriginFact>());
+ case Fact::Kind::KillOrigin:
+ return D->transfer(In, *F->getAs<KillOriginFact>());
}
llvm_unreachable("Unknown fact kind");
}
@@ -193,6 +195,7 @@ class DataflowAnalysis {
Lattice transfer(Lattice In, const UseFact &) { return In; }
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; }
+ Lattice transfer(Lattice In, const KillOriginFact &) { return In; }
};
} // namespace clang::lifetimes::internal
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 7a04fd1dfe2f2..3d7fbcdacc830 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -43,12 +43,10 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << "\tDest: ";
OM.dump(getDestOriginID(), OS);
OS << "\n";
- if (getSrcOriginID()) {
- OS << "\tSrc: ";
- OM.dump(*getSrcOriginID(), OS);
- OS << (getKillDest() ? "" : ", Merge");
- OS << "\n";
- }
+ OS << "\tSrc: ";
+ OM.dump(getSrcOriginID(), OS);
+ OS << (getKillDest() ? "" : ", Merge");
+ OS << "\n";
}
void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
@@ -105,6 +103,13 @@ void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
}
+void KillOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+ const OriginManager &OM) const {
+ OS << "KillOrigin (";
+ OM.dump(getKilledOrigin(), OS);
+ OS << ")\n";
+}
+
llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const {
llvm::StringMap<ProgramPoint> AnnotationToPointMap;
for (const auto &BlockFacts : BlockToFacts) {
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index b6c6d37d4cc20..c991438c40e3a 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -410,12 +410,12 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
markUseAsWrite(DRE_LHS);
}
if (!RHSList) {
- // RHS has no tracked origins (e.g., assigning a non-lambda functor to a
- // std::function). Emit a kill-only flow to clear old loans.
- if (OriginList *LHSInner = LHSList->peelOuterOrigin())
- CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
- LHSInner->getOuterOriginID(), /*OIDSrc=*/std::nullopt,
- /*KillDest=*/true));
+ // RHS has no tracked origins (e.g., assigning a callable without origins
+ // to std::function). Clear loans of the destination.
+ for (OriginList *LHSInner = LHSList->peelOuterOrigin(); LHSInner;
+ LHSInner = LHSInner->peelOuterOrigin())
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<KillOriginFact>(LHSInner->getOuterOriginID()));
return;
}
// Kill the old loans of the destination origin and flow the new loans
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index bc7494360624e..cfbcacf04b1b0 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -166,6 +166,10 @@ class AnalysisImpl
return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID()));
}
+ Lattice transfer(Lattice In, const KillOriginFact &F) {
+ return Lattice(Factory.remove(In.LiveOrigins, F.getKilledOrigin()));
+ }
+
Lattice transfer(Lattice In, const ExpireFact &F) {
if (auto OID = F.getOriginID())
return Lattice(Factory.remove(In.LiveOrigins, *OID));
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index a53e8d0419282..adbc0458516e1 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -55,8 +55,7 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
case Fact::Kind::OriginFlow: {
const auto *OF = F->getAs<OriginFlowFact>();
CheckOrigin(OF->getDestOriginID());
- if (OF->getSrcOriginID())
- CheckOrigin(*OF->getSrcOriginID());
+ CheckOrigin(OF->getSrcOriginID());
break;
}
case Fact::Kind::Use:
@@ -64,6 +63,9 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
Cur = Cur->peelOuterOrigin())
CheckOrigin(Cur->getOuterOriginID());
break;
+ case Fact::Kind::KillOrigin:
+ CheckOrigin(F->getAs<KillOriginFact>()->getKilledOrigin());
+ break;
case Fact::Kind::MovedOrigin:
case Fact::Kind::OriginEscapes:
case Fact::Kind::Expire:
@@ -169,20 +171,23 @@ class AnalysisImpl
/// A flow from source to destination. If `KillDest` is true, this replaces
/// the destination's loans with the source's. Otherwise, the source's loans
- /// are merged into the destination's. If the source is nullopt, this is a
- /// kill-only flow that clears the destination without adding new loans.
+ /// are merged into the destination's.
Lattice transfer(Lattice In, const OriginFlowFact &F) {
OriginID DestOID = F.getDestOriginID();
+ OriginID SrcOID = F.getSrcOriginID();
LoanSet DestLoans =
F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
- LoanSet SrcLoans = F.getSrcOriginID() ? getLoans(In, *F.getSrcOriginID())
- : LoanSetFactory.getEmptySet();
+ LoanSet SrcLoans = getLoans(In, SrcOID);
LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
return setLoans(In, DestOID, MergedLoans);
}
+ Lattice transfer(Lattice In, const KillOriginFact &F) {
+ return setLoans(In, F.getKilledOrigin(), LoanSetFactory.getEmptySet());
+ }
+
Lattice transfer(Lattice In, const ExpireFact &F) {
if (auto OID = F.getOriginID())
return setLoans(In, *OID, LoanSetFactory.getEmptySet());
>From 120b661bad96423cc2ca482681965ecf81808005 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 12 Apr 2026 17:29:22 -0700
Subject: [PATCH 04/12] Add copy/move assign tests
---
clang/test/Sema/Inputs/lifetime-analysis.h | 4 ++++
clang/test/Sema/warn-lifetime-safety.cpp | 19 +++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 58ba1aa0c03c3..2b904f88bc475 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -274,7 +274,11 @@ template<class R, class... Args>
class function<R(Args...)> {
public:
template<class F> function(F) {}
+ function(const function&) {}
+ function(function&&) {}
template<class F> function& operator=(F) { return *this; }
+ function& operator=(const function&) { return *this; }
+ function& operator=(function&&) { return *this; }
~function();
};
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index b650cf6ed086c..7bd93ee4ba8f3 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2568,6 +2568,25 @@ std::function<void()> copy_function() {
return f2; // expected-note {{returned here}}
}
+std::function<void()> copy_assign() {
+ int x;
+ std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{address of stack memory is returned later}}
+ std::function<void()> f2 = []() {};
+ f2 = f;
+ return f2; // expected-note {{returned here}}
+}
+
+// FIXME: False negative. std::move's lifetimebound handling in
+// `handleFunctionCall` only flows the outermost origin, missing inner origins
+// that carry the lambda's loans.
+std::function<void()> move_assign() {
+ int x;
+ std::function<void()> f = [&x]() { (void)x; }; // Should warn.
+ std::function<void()> f2 = []() {};
+ f2 = std::move(f);
+ return f2;
+}
+
std::function<void()> reassign_safe_then_unsafe() {
static int safe = 1;
int local = 2;
>From 4127ef03f34e6fadc4e2ee1491eff5a260533885 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 12 Apr 2026 18:22:02 -0700
Subject: [PATCH 05/12] Add test function_ref_from_non_capturing_lambda
---
clang/test/Sema/warn-lifetime-safety.cpp | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 7bd93ee4ba8f3..e48e3fa38f526 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2626,4 +2626,15 @@ std::function<void()> reassign_lambda_to_functor() {
return f;
}
+struct [[gsl::Pointer]] function_ref {
+ template <typename Callable>
+ function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
+ void (*ref)();
+};
+
+function_ref function_ref_from_non_capturing_lambda() {
+ return []() {}; // expected-warning {{address of stack memory is returned later}} \
+ // expected-note {{returned here}}
+}
+
} // namespace callable_wrappers
>From 26479172e21203bf2f124f685d1a160c2b983c3e Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 12 Apr 2026 20:59:02 -0700
Subject: [PATCH 06/12] add test
---
clang/test/Sema/warn-lifetime-safety.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index e48e3fa38f526..ac4ef9cd8e49f 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2637,4 +2637,9 @@ function_ref function_ref_from_non_capturing_lambda() {
// expected-note {{returned here}}
}
+void assign_non_capturing_to_function_ref(function_ref &r) {
+ r = []() {};
+ (void)r;
+}
+
} // namespace callable_wrappers
>From d7c9b492f925e2abeefb09fa0c657cc348aa6576 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 12 Apr 2026 21:19:04 -0700
Subject: [PATCH 07/12] update doc
---
clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index d375e94ea9771..b70fecd5ab1d1 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,8 +55,7 @@ class Fact {
OriginEscapes,
/// An origin is invalidated (e.g. vector resized).
InvalidateOrigin,
- /// All loans are cleared from an origin (e.g., assigning a callable without
- /// tracked origins to std::function).
+ /// All loans of an origin are cleared.
KillOrigin,
};
@@ -319,6 +318,8 @@ class TestPointFact : public Fact {
const OriginManager &) const override;
};
+/// All loans are cleared from an origin (e.g., assigning a callable without
+/// tracked origins to std::function).
class KillOriginFact : public Fact {
OriginID OID;
>From 6c4867b6ad4005ae613d6c34a0b7a8a4cba7b72b Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 12 Apr 2026 21:33:31 -0700
Subject: [PATCH 08/12] update test
---
clang/test/Sema/warn-lifetime-safety.cpp | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index ac4ef9cd8e49f..2a2291600c27b 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2632,14 +2632,10 @@ struct [[gsl::Pointer]] function_ref {
void (*ref)();
};
-function_ref function_ref_from_non_capturing_lambda() {
- return []() {}; // expected-warning {{address of stack memory is returned later}} \
- // expected-note {{returned here}}
-}
-
void assign_non_capturing_to_function_ref(function_ref &r) {
- r = []() {};
- (void)r;
+ r = []() {}; // expected-warning {{object whose reference is captured does not live long enough}} \
+ // expected-note {{destroyed here}}
+ (void)r; // expected-note {{later used here}}
}
} // namespace callable_wrappers
>From aea1bacd391b80c2afa6144ffbc4b5575e855ef8 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 12 Apr 2026 22:40:28 -0700
Subject: [PATCH 09/12] update test
---
clang/test/Sema/warn-lifetime-safety.cpp | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 2a2291600c27b..c1ea383ce92ee 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2626,16 +2626,24 @@ std::function<void()> reassign_lambda_to_functor() {
return f;
}
+} // namespace callable_wrappers
+
+namespace GH126600 {
+// https://github.com/llvm/llvm-project/issues/126600
struct [[gsl::Pointer]] function_ref {
template <typename Callable>
function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
void (*ref)();
};
+// FIXME: The lifetimebound annotation tracks the outer callable object's
+// storage rather than what the callable captures. A mechanism like
+// lifetimebound(2) could enable tracking inner lifetimes, which would
+// avoid this warning for non-capturing lambdas.
void assign_non_capturing_to_function_ref(function_ref &r) {
r = []() {}; // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
(void)r; // expected-note {{later used here}}
}
-} // namespace callable_wrappers
+} // namespace GH126600
>From 1d7aa1c8351296c60958388a4df4d2d546a7e91d Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Mon, 13 Apr 2026 22:18:23 -0700
Subject: [PATCH 10/12] add tests
---
.../warn-lifetime-safety-dangling-field.cpp | 13 +++++++++++
.../warn-lifetime-safety-invalidations.cpp | 12 ++++++++++
.../Sema/warn-lifetime-safety-noescape.cpp | 8 +++++++
.../Sema/warn-lifetime-safety-suggestions.cpp | 23 +++++++++++++++++++
clang/test/Sema/warn-lifetime-safety.cpp | 14 ++++++++++-
5 files changed, 69 insertions(+), 1 deletion(-)
diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
index 79b0183ed91ec..2afcd4a3dd97b 100644
--- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp
@@ -196,3 +196,16 @@ struct DerivedWithCtor : BaseWithPointer {
}
};
} // namespace DanglingPointerFieldInBaseClass
+
+namespace callable_wrappers {
+
+struct HasCallback {
+ std::function<void()> callback; // expected-note {{this field dangles}}
+
+ void set_callback() {
+ int local;
+ callback = [&local]() { (void)local; }; // expected-warning {{address of stack memory escapes to a field}}
+ }
+};
+
+} // namespace callable_wrappers
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index c7f920c03736a..59d70a13bb963 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -512,3 +512,15 @@ void ref_capture_reassigned_to_safe() {
lambda(); // should not warn
}
} // namespace lambda_capture_invalidation
+
+namespace callable_wrappers {
+
+void function_captured_ref_invalidated() {
+ std::vector<int> v;
+ v.push_back(1);
+ std::function<void()> f = [&r = v[0]]() { (void)r; }; // expected-warning {{object whose reference is captured is later invalidated}}
+ v.push_back(2); // expected-note {{invalidated here}}
+ (void)f; // expected-note {{later used here}}
+}
+
+} // namespace callable_wrappers
diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
index f233ec546faa5..4bb57e6b9df95 100644
--- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp
@@ -191,3 +191,11 @@ MyObj& return_ref_from_noescape_ptr(
int* return_spaced_brackets(int* p [ [clang::noescape] /*some comment*/ ]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
return p; // expected-note {{returned here}}
}
+
+namespace callable_wrappers {
+
+std::function<void()> escape_noescape_via_function(int &x [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}}
+ return [&]() { (void)x; }; // expected-note {{returned here}}
+}
+
+} // namespace callable_wrappers
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index b3b13038dc344..d49143448ff14 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -62,6 +62,21 @@ struct ReturnThisPointer {
};
+namespace std {
+template<class> class function;
+template<class R, class... Args>
+class function<R(Args...)> {
+public:
+ template<class F> function(F) {}
+ function(const function&) {}
+ function(function&&) {}
+ template<class F> function& operator=(F) { return *this; }
+ function& operator=(const function&) { return *this; }
+ function& operator=(function&&) { return *this; }
+ ~function();
+};
+} // namespace std
+
#endif // TEST_HEADER_H
//--- test_source.cpp
@@ -529,3 +544,11 @@ CaptureRefToBaseView test_ref_to_base_view() {
return x; // expected-note {{returned here}}
}
} // namespace capturing_constructor
+
+namespace callable_wrappers {
+
+std::function<void()> return_lambda_capturing_param(int &x) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+ return [&]() { (void)x; }; // expected-note {{param returned here}}
+}
+
+} // namespace callable_wrappers
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index c1ea383ce92ee..001c9ae5480b7 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2626,10 +2626,22 @@ std::function<void()> reassign_lambda_to_functor() {
return f;
}
+std::function<void()> capture_lifetimebound_param(int &x [[clang::lifetimebound]]) {
+ return [&]() { (void)x; };
+}
+
+void uaf_via_lifetimebound() {
+ std::function<void()> f = []() {};
+ {
+ int local;
+ f = capture_lifetimebound_param(local); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)f; // expected-note {{later used here}}
+}
+
} // namespace callable_wrappers
namespace GH126600 {
-// https://github.com/llvm/llvm-project/issues/126600
struct [[gsl::Pointer]] function_ref {
template <typename Callable>
function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
>From a4dba26a3ce7d62d73eefab119c7236fe04d0a85 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Mon, 13 Apr 2026 22:27:45 -0700
Subject: [PATCH 11/12] reuse header
---
.../Sema/warn-lifetime-safety-suggestions.cpp | 22 ++++---------------
1 file changed, 4 insertions(+), 18 deletions(-)
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index d49143448ff14..c86d96046e771 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -1,8 +1,8 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
-// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -verify %t/test_source.cpp
-// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -fixit %t/test_source.cpp
-// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%t -Werror=lifetime-safety-suggestions %t/test_source.cpp
+// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -verify %t/test_source.cpp
+// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -fixit %t/test_source.cpp
+// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%t -I%S -Werror=lifetime-safety-suggestions %t/test_source.cpp
View definition_before_header(View a);
@@ -62,26 +62,12 @@ struct ReturnThisPointer {
};
-namespace std {
-template<class> class function;
-template<class R, class... Args>
-class function<R(Args...)> {
-public:
- template<class F> function(F) {}
- function(const function&) {}
- function(function&&) {}
- template<class F> function& operator=(F) { return *this; }
- function& operator=(const function&) { return *this; }
- function& operator=(function&&) { return *this; }
- ~function();
-};
-} // namespace std
-
#endif // TEST_HEADER_H
//--- test_source.cpp
#include "test_header.h"
+#include "Inputs/lifetime-analysis.h"
View definition_before_header(View a) {
return a; // expected-note {{param returned here}}
>From a712910329696327b2f6efce8bfd07a591a8f797 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Mon, 13 Apr 2026 22:41:54 -0700
Subject: [PATCH 12/12] add test: uaf_via_inferred_lifetimebound
---
clang/test/Sema/warn-lifetime-safety-suggestions.cpp | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index c86d96046e771..d2cf1c175eb57 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -537,4 +537,13 @@ std::function<void()> return_lambda_capturing_param(int &x) { // expected-warnin
return [&]() { (void)x; }; // expected-note {{param returned here}}
}
+void uaf_via_inferred_lifetimebound() {
+ std::function<void()> f = []() {};
+ {
+ int local;
+ f = return_lambda_capturing_param(local); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)f; // expected-note {{later used here}}
+}
+
} // namespace callable_wrappers
More information about the cfe-commits
mailing list