Skip to content

Commit 1b804e2

Browse files
authored
[LifetimeSafety] Track origins through std::function (llvm#191123)
1. Recognizes `std::function` and `std::move_only_function` as types that can carry origins from a wrapped lambda's captures, propagating origins through both construction and assignment. 2. Adds a kill-only mechanism (i.e., a new `KillOriginFact`) to clear old loans when the RHS has no origins. Fixes llvm#186009
1 parent 06c1aa3 commit 1b804e2

15 files changed

+260
-6
lines changed

clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class Fact {
5555
OriginEscapes,
5656
/// An origin is invalidated (e.g. vector resized).
5757
InvalidateOrigin,
58+
/// All loans of an origin are cleared.
59+
KillOrigin,
5860
};
5961

6062
private:
@@ -316,6 +318,24 @@ class TestPointFact : public Fact {
316318
const OriginManager &) const override;
317319
};
318320

321+
/// All loans are cleared from an origin (e.g., assigning a callable without
322+
/// tracked origins to std::function).
323+
class KillOriginFact : public Fact {
324+
OriginID OID;
325+
326+
public:
327+
static bool classof(const Fact *F) {
328+
return F->getKind() == Kind::KillOrigin;
329+
}
330+
331+
KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {}
332+
333+
OriginID getKilledOrigin() const { return OID; }
334+
335+
void dump(llvm::raw_ostream &OS, const LoanManager &,
336+
const OriginManager &OM) const override;
337+
};
338+
319339
class FactManager {
320340
public:
321341
FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) : OriginMgr(AC) {

clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD);
8080
// https://en.cppreference.com/w/cpp/container#Iterator_invalidation
8181
bool isContainerInvalidationMethod(const CXXMethodDecl &MD);
8282

83+
/// Returns true for standard library callable wrappers (e.g., std::function)
84+
/// that can propagate the stored lambda's origins.
85+
bool isStdCallableWrapperType(const CXXRecordDecl *RD);
86+
8387
} // namespace clang::lifetimes
8488

8589
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H

clang/lib/Analysis/LifetimeSafety/Dataflow.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ class DataflowAnalysis {
180180
return D->transfer(In, *F->getAs<TestPointFact>());
181181
case Fact::Kind::InvalidateOrigin:
182182
return D->transfer(In, *F->getAs<InvalidateOriginFact>());
183+
case Fact::Kind::KillOrigin:
184+
return D->transfer(In, *F->getAs<KillOriginFact>());
183185
}
184186
llvm_unreachable("Unknown fact kind");
185187
}
@@ -193,6 +195,7 @@ class DataflowAnalysis {
193195
Lattice transfer(Lattice In, const UseFact &) { return In; }
194196
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
195197
Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; }
198+
Lattice transfer(Lattice In, const KillOriginFact &) { return In; }
196199
};
197200
} // namespace clang::lifetimes::internal
198201
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H

clang/lib/Analysis/LifetimeSafety/Facts.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
103103
OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
104104
}
105105

106+
void KillOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
107+
const OriginManager &OM) const {
108+
OS << "KillOrigin (";
109+
OM.dump(getKilledOrigin(), OS);
110+
OS << ")\n";
111+
}
112+
106113
llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const {
107114
llvm::StringMap<ProgramPoint> AnnotationToPointMap;
108115
for (const auto &BlockFacts : BlockToFacts) {

clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,8 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
190190
return;
191191
}
192192
// For defaulted (implicit or `= default`) copy/move constructors, propagate
193-
// origins directly. User-defined copy/move constructors have opaque semantics
194-
// and fall through to `handleFunctionCall`, where [[clang::lifetimebound]] is
195-
// needed to propagate origins.
193+
// origins directly. User-defined copy/move constructors are not handled here
194+
// as they have opaque semantics.
196195
if (CCE->getConstructor()->isCopyOrMoveConstructor() &&
197196
CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 &&
198197
hasOrigins(CCE->getType())) {
@@ -202,6 +201,16 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
202201
return;
203202
}
204203
}
204+
// Standard library callable wrappers (e.g., std::function) propagate the
205+
// stored lambda's origins.
206+
if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
207+
RD && isStdCallableWrapperType(RD) && CCE->getNumArgs() == 1) {
208+
const Expr *Arg = CCE->getArg(0);
209+
if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
210+
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
211+
return;
212+
}
213+
}
205214
handleFunctionCall(CCE, CCE->getConstructor(),
206215
{CCE->getArgs(), CCE->getNumArgs()},
207216
/*IsGslConstruction=*/false);
@@ -400,6 +409,15 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
400409
} else
401410
markUseAsWrite(DRE_LHS);
402411
}
412+
if (!RHSList) {
413+
// RHS has no tracked origins (e.g., assigning a callable without origins
414+
// to std::function). Clear loans of the destination.
415+
for (OriginList *LHSInner = LHSList->peelOuterOrigin(); LHSInner;
416+
LHSInner = LHSInner->peelOuterOrigin())
417+
CurrentBlockFacts.push_back(
418+
FactMgr.createFact<KillOriginFact>(LHSInner->getOuterOriginID()));
419+
return;
420+
}
403421
// Kill the old loans of the destination origin and flow the new loans
404422
// from the source origin.
405423
flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
@@ -493,6 +511,13 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
493511
handleAssignment(OCE->getArg(0), OCE->getArg(1));
494512
return;
495513
}
514+
// Standard library callable wrappers (e.g., std::function) can propagate
515+
// the stored lambda's origins.
516+
if (const auto *RD = LHSTy->getAsCXXRecordDecl();
517+
RD && isStdCallableWrapperType(RD)) {
518+
handleAssignment(OCE->getArg(0), OCE->getArg(1));
519+
return;
520+
}
496521
// Other tracked types: only defaulted operator= propagates origins.
497522
// User-defined operator= has opaque semantics, so don't handle them now.
498523
if (const auto *MD =

clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,4 +382,12 @@ bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
382382

383383
return InvalidatingMethods->contains(MD.getName());
384384
}
385+
386+
bool isStdCallableWrapperType(const CXXRecordDecl *RD) {
387+
if (!RD || !isInStlNamespace(RD))
388+
return false;
389+
StringRef Name = getName(*RD);
390+
return Name == "function" || Name == "move_only_function";
391+
}
392+
385393
} // namespace clang::lifetimes

clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ class AnalysisImpl
166166
return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID()));
167167
}
168168

169+
Lattice transfer(Lattice In, const KillOriginFact &F) {
170+
return Lattice(Factory.remove(In.LiveOrigins, F.getKilledOrigin()));
171+
}
172+
169173
Lattice transfer(Lattice In, const ExpireFact &F) {
170174
if (auto OID = F.getOriginID())
171175
return Lattice(Factory.remove(In.LiveOrigins, *OID));

clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
6363
Cur = Cur->peelOuterOrigin())
6464
CheckOrigin(Cur->getOuterOriginID());
6565
break;
66+
case Fact::Kind::KillOrigin:
67+
CheckOrigin(F->getAs<KillOriginFact>()->getKilledOrigin());
68+
break;
6669
case Fact::Kind::MovedOrigin:
6770
case Fact::Kind::OriginEscapes:
6871
case Fact::Kind::Expire:
@@ -181,6 +184,10 @@ class AnalysisImpl
181184
return setLoans(In, DestOID, MergedLoans);
182185
}
183186

187+
Lattice transfer(Lattice In, const KillOriginFact &F) {
188+
return setLoans(In, F.getKilledOrigin(), LoanSetFactory.getEmptySet());
189+
}
190+
184191
Lattice transfer(Lattice In, const ExpireFact &F) {
185192
if (auto OID = F.getOriginID())
186193
return setLoans(In, *OID, LoanSetFactory.getEmptySet());

clang/lib/Analysis/LifetimeSafety/Origins.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ bool OriginManager::hasOrigins(QualType QT) const {
107107
const auto *RD = QT->getAsCXXRecordDecl();
108108
if (!RD)
109109
return false;
110+
// Standard library callable wrappers (e.g., std::function) can propagate the
111+
// stored lambda's origins.
112+
if (isStdCallableWrapperType(RD))
113+
return true;
110114
// TODO: Limit to lambdas for now. This will be extended to user-defined
111115
// structs with pointer-like fields.
112116
if (!RD->isLambda())

clang/test/Sema/Inputs/lifetime-analysis.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,18 @@ struct true_type {
268268
template<class T> struct is_pointer : false_type {};
269269
template<class T> struct is_pointer<T*> : true_type {};
270270
template<class T> struct is_pointer<T* const> : true_type {};
271+
272+
template<class> class function;
273+
template<class R, class... Args>
274+
class function<R(Args...)> {
275+
public:
276+
template<class F> function(F) {}
277+
function(const function&) {}
278+
function(function&&) {}
279+
template<class F> function& operator=(F) { return *this; }
280+
function& operator=(const function&) { return *this; }
281+
function& operator=(function&&) { return *this; }
282+
~function();
283+
};
284+
271285
}

0 commit comments

Comments
 (0)