Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/clang/lib/Analysis/LifetimeSafety.cpp
213766 views
1
//===- LifetimeSafety.cpp - C++ Lifetime Safety Analysis -*--------- C++-*-===//
2
//
3
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4
// See https://llvm.org/LICENSE.txt for license information.
5
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6
//
7
//===----------------------------------------------------------------------===//
8
#include "clang/Analysis/Analyses/LifetimeSafety.h"
9
#include "clang/AST/Decl.h"
10
#include "clang/AST/Expr.h"
11
#include "clang/AST/StmtVisitor.h"
12
#include "clang/AST/Type.h"
13
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
14
#include "clang/Analysis/AnalysisDeclContext.h"
15
#include "clang/Analysis/CFG.h"
16
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
17
#include "llvm/ADT/FoldingSet.h"
18
#include "llvm/ADT/ImmutableMap.h"
19
#include "llvm/ADT/ImmutableSet.h"
20
#include "llvm/ADT/PointerUnion.h"
21
#include "llvm/ADT/SmallVector.h"
22
#include "llvm/Support/Debug.h"
23
#include "llvm/Support/TimeProfiler.h"
24
#include <cstdint>
25
26
namespace clang {
27
namespace {
28
29
/// Represents the storage location being borrowed, e.g., a specific stack
30
/// variable.
31
/// TODO: Model access paths of other types, e.g., s.field, heap and globals.
32
struct AccessPath {
33
const clang::ValueDecl *D;
34
35
AccessPath(const clang::ValueDecl *D) : D(D) {}
36
};
37
38
/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
39
/// Used for giving ID to loans and origins.
40
template <typename Tag> struct ID {
41
uint32_t Value = 0;
42
43
bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; }
44
bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); }
45
bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; }
46
ID<Tag> operator++(int) {
47
ID<Tag> Tmp = *this;
48
++Value;
49
return Tmp;
50
}
51
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
52
IDBuilder.AddInteger(Value);
53
}
54
};
55
56
template <typename Tag>
57
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
58
return OS << ID.Value;
59
}
60
61
using LoanID = ID<struct LoanTag>;
62
using OriginID = ID<struct OriginTag>;
63
64
/// Information about a single borrow, or "Loan". A loan is created when a
65
/// reference or pointer is created.
66
struct Loan {
67
/// TODO: Represent opaque loans.
68
/// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it
69
/// is represented as empty LoanSet
70
LoanID ID;
71
AccessPath Path;
72
SourceLocation IssueLoc;
73
74
Loan(LoanID id, AccessPath path, SourceLocation loc)
75
: ID(id), Path(path), IssueLoc(loc) {}
76
};
77
78
/// An Origin is a symbolic identifier that represents the set of possible
79
/// loans a pointer-like object could hold at any given time.
80
/// TODO: Enhance the origin model to handle complex types, pointer
81
/// indirection and reborrowing. The plan is to move from a single origin per
82
/// variable/expression to a "list of origins" governed by the Type.
83
/// For example, the type 'int**' would have two origins.
84
/// See discussion:
85
/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238
86
struct Origin {
87
OriginID ID;
88
/// A pointer to the AST node that this origin represents. This union
89
/// distinguishes between origins from declarations (variables or parameters)
90
/// and origins from expressions.
91
llvm::PointerUnion<const clang::ValueDecl *, const clang::Expr *> Ptr;
92
93
Origin(OriginID ID, const clang::ValueDecl *D) : ID(ID), Ptr(D) {}
94
Origin(OriginID ID, const clang::Expr *E) : ID(ID), Ptr(E) {}
95
96
const clang::ValueDecl *getDecl() const {
97
return Ptr.dyn_cast<const clang::ValueDecl *>();
98
}
99
const clang::Expr *getExpr() const {
100
return Ptr.dyn_cast<const clang::Expr *>();
101
}
102
};
103
104
/// Manages the creation, storage and retrieval of loans.
105
class LoanManager {
106
public:
107
LoanManager() = default;
108
109
Loan &addLoan(AccessPath Path, SourceLocation Loc) {
110
AllLoans.emplace_back(getNextLoanID(), Path, Loc);
111
return AllLoans.back();
112
}
113
114
const Loan &getLoan(LoanID ID) const {
115
assert(ID.Value < AllLoans.size());
116
return AllLoans[ID.Value];
117
}
118
llvm::ArrayRef<Loan> getLoans() const { return AllLoans; }
119
120
private:
121
LoanID getNextLoanID() { return NextLoanID++; }
122
123
LoanID NextLoanID{0};
124
/// TODO(opt): Profile and evaluate the usefullness of small buffer
125
/// optimisation.
126
llvm::SmallVector<Loan> AllLoans;
127
};
128
129
/// Manages the creation, storage, and retrieval of origins for pointer-like
130
/// variables and expressions.
131
class OriginManager {
132
public:
133
OriginManager() = default;
134
135
Origin &addOrigin(OriginID ID, const clang::ValueDecl &D) {
136
AllOrigins.emplace_back(ID, &D);
137
return AllOrigins.back();
138
}
139
Origin &addOrigin(OriginID ID, const clang::Expr &E) {
140
AllOrigins.emplace_back(ID, &E);
141
return AllOrigins.back();
142
}
143
144
OriginID get(const Expr &E) {
145
// Origin of DeclRefExpr is that of the declaration it refers to.
146
if (const auto *DRE = dyn_cast<DeclRefExpr>(&E))
147
return get(*DRE->getDecl());
148
auto It = ExprToOriginID.find(&E);
149
// TODO: This should be an assert(It != ExprToOriginID.end()). The current
150
// implementation falls back to getOrCreate to avoid crashing on
151
// yet-unhandled pointer expressions, creating an empty origin for them.
152
if (It == ExprToOriginID.end())
153
return getOrCreate(E);
154
155
return It->second;
156
}
157
158
OriginID get(const ValueDecl &D) {
159
auto It = DeclToOriginID.find(&D);
160
// TODO: This should be an assert(It != DeclToOriginID.end()). The current
161
// implementation falls back to getOrCreate to avoid crashing on
162
// yet-unhandled pointer expressions, creating an empty origin for them.
163
if (It == DeclToOriginID.end())
164
return getOrCreate(D);
165
166
return It->second;
167
}
168
169
OriginID getOrCreate(const Expr &E) {
170
auto It = ExprToOriginID.find(&E);
171
if (It != ExprToOriginID.end())
172
return It->second;
173
174
if (const auto *DRE = dyn_cast<DeclRefExpr>(&E)) {
175
// Origin of DeclRefExpr is that of the declaration it refers to.
176
return getOrCreate(*DRE->getDecl());
177
}
178
OriginID NewID = getNextOriginID();
179
addOrigin(NewID, E);
180
ExprToOriginID[&E] = NewID;
181
return NewID;
182
}
183
184
const Origin &getOrigin(OriginID ID) const {
185
assert(ID.Value < AllOrigins.size());
186
return AllOrigins[ID.Value];
187
}
188
189
llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }
190
191
OriginID getOrCreate(const ValueDecl &D) {
192
auto It = DeclToOriginID.find(&D);
193
if (It != DeclToOriginID.end())
194
return It->second;
195
OriginID NewID = getNextOriginID();
196
addOrigin(NewID, D);
197
DeclToOriginID[&D] = NewID;
198
return NewID;
199
}
200
201
private:
202
OriginID getNextOriginID() { return NextOriginID++; }
203
204
OriginID NextOriginID{0};
205
/// TODO(opt): Profile and evaluate the usefullness of small buffer
206
/// optimisation.
207
llvm::SmallVector<Origin> AllOrigins;
208
llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
209
llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
210
};
211
212
/// An abstract base class for a single, atomic lifetime-relevant event.
213
class Fact {
214
215
public:
216
enum class Kind : uint8_t {
217
/// A new loan is issued from a borrow expression (e.g., &x).
218
Issue,
219
/// A loan expires as its underlying storage is freed (e.g., variable goes
220
/// out of scope).
221
Expire,
222
/// An origin is propagated from a source to a destination (e.g., p = q).
223
AssignOrigin,
224
/// An origin escapes the function by flowing into the return value.
225
ReturnOfOrigin
226
};
227
228
private:
229
Kind K;
230
231
protected:
232
Fact(Kind K) : K(K) {}
233
234
public:
235
virtual ~Fact() = default;
236
Kind getKind() const { return K; }
237
238
template <typename T> const T *getAs() const {
239
if (T::classof(this))
240
return static_cast<const T *>(this);
241
return nullptr;
242
}
243
244
virtual void dump(llvm::raw_ostream &OS) const {
245
OS << "Fact (Kind: " << static_cast<int>(K) << ")\n";
246
}
247
};
248
249
class IssueFact : public Fact {
250
LoanID LID;
251
OriginID OID;
252
253
public:
254
static bool classof(const Fact *F) { return F->getKind() == Kind::Issue; }
255
256
IssueFact(LoanID LID, OriginID OID) : Fact(Kind::Issue), LID(LID), OID(OID) {}
257
LoanID getLoanID() const { return LID; }
258
OriginID getOriginID() const { return OID; }
259
void dump(llvm::raw_ostream &OS) const override {
260
OS << "Issue (LoanID: " << getLoanID() << ", OriginID: " << getOriginID()
261
<< ")\n";
262
}
263
};
264
265
class ExpireFact : public Fact {
266
LoanID LID;
267
268
public:
269
static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; }
270
271
ExpireFact(LoanID LID) : Fact(Kind::Expire), LID(LID) {}
272
LoanID getLoanID() const { return LID; }
273
void dump(llvm::raw_ostream &OS) const override {
274
OS << "Expire (LoanID: " << getLoanID() << ")\n";
275
}
276
};
277
278
class AssignOriginFact : public Fact {
279
OriginID OIDDest;
280
OriginID OIDSrc;
281
282
public:
283
static bool classof(const Fact *F) {
284
return F->getKind() == Kind::AssignOrigin;
285
}
286
287
AssignOriginFact(OriginID OIDDest, OriginID OIDSrc)
288
: Fact(Kind::AssignOrigin), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
289
OriginID getDestOriginID() const { return OIDDest; }
290
OriginID getSrcOriginID() const { return OIDSrc; }
291
void dump(llvm::raw_ostream &OS) const override {
292
OS << "AssignOrigin (DestID: " << getDestOriginID()
293
<< ", SrcID: " << getSrcOriginID() << ")\n";
294
}
295
};
296
297
class ReturnOfOriginFact : public Fact {
298
OriginID OID;
299
300
public:
301
static bool classof(const Fact *F) {
302
return F->getKind() == Kind::ReturnOfOrigin;
303
}
304
305
ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {}
306
OriginID getReturnedOriginID() const { return OID; }
307
void dump(llvm::raw_ostream &OS) const override {
308
OS << "ReturnOfOrigin (OriginID: " << getReturnedOriginID() << ")\n";
309
}
310
};
311
312
class FactManager {
313
public:
314
llvm::ArrayRef<const Fact *> getFacts(const CFGBlock *B) const {
315
auto It = BlockToFactsMap.find(B);
316
if (It != BlockToFactsMap.end())
317
return It->second;
318
return {};
319
}
320
321
void addBlockFacts(const CFGBlock *B, llvm::ArrayRef<Fact *> NewFacts) {
322
if (!NewFacts.empty())
323
BlockToFactsMap[B].assign(NewFacts.begin(), NewFacts.end());
324
}
325
326
template <typename FactType, typename... Args>
327
FactType *createFact(Args &&...args) {
328
void *Mem = FactAllocator.Allocate<FactType>();
329
return new (Mem) FactType(std::forward<Args>(args)...);
330
}
331
332
void dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
333
llvm::dbgs() << "==========================================\n";
334
llvm::dbgs() << " Lifetime Analysis Facts:\n";
335
llvm::dbgs() << "==========================================\n";
336
if (const Decl *D = AC.getDecl())
337
if (const auto *ND = dyn_cast<NamedDecl>(D))
338
llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n";
339
// Print blocks in the order as they appear in code for a stable ordering.
340
for (const CFGBlock *B : *AC.getAnalysis<PostOrderCFGView>()) {
341
llvm::dbgs() << " Block B" << B->getBlockID() << ":\n";
342
auto It = BlockToFactsMap.find(B);
343
if (It != BlockToFactsMap.end()) {
344
for (const Fact *F : It->second) {
345
llvm::dbgs() << " ";
346
F->dump(llvm::dbgs());
347
}
348
}
349
llvm::dbgs() << " End of Block\n";
350
}
351
}
352
353
LoanManager &getLoanMgr() { return LoanMgr; }
354
OriginManager &getOriginMgr() { return OriginMgr; }
355
356
private:
357
LoanManager LoanMgr;
358
OriginManager OriginMgr;
359
llvm::DenseMap<const clang::CFGBlock *, llvm::SmallVector<const Fact *>>
360
BlockToFactsMap;
361
llvm::BumpPtrAllocator FactAllocator;
362
};
363
364
class FactGenerator : public ConstStmtVisitor<FactGenerator> {
365
366
public:
367
FactGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
368
: FactMgr(FactMgr), AC(AC) {}
369
370
void run() {
371
llvm::TimeTraceScope TimeProfile("FactGenerator");
372
// Iterate through the CFG blocks in reverse post-order to ensure that
373
// initializations and destructions are processed in the correct sequence.
374
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
375
CurrentBlockFacts.clear();
376
for (unsigned I = 0; I < Block->size(); ++I) {
377
const CFGElement &Element = Block->Elements[I];
378
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
379
Visit(CS->getStmt());
380
else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
381
Element.getAs<CFGAutomaticObjDtor>())
382
handleDestructor(*DtorOpt);
383
}
384
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
385
}
386
}
387
388
void VisitDeclStmt(const DeclStmt *DS) {
389
for (const Decl *D : DS->decls())
390
if (const auto *VD = dyn_cast<VarDecl>(D))
391
if (hasOrigin(VD->getType()))
392
if (const Expr *InitExpr = VD->getInit())
393
addAssignOriginFact(*VD, *InitExpr);
394
}
395
396
void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N) {
397
/// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
398
/// pointers can use the same type of loan.
399
FactMgr.getOriginMgr().getOrCreate(*N);
400
}
401
402
void VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
403
if (!hasOrigin(ICE->getType()))
404
return;
405
Visit(ICE->getSubExpr());
406
// An ImplicitCastExpr node itself gets an origin, which flows from the
407
// origin of its sub-expression (after stripping its own parens/casts).
408
// TODO: Consider if this is actually useful in practice. Alternatively, we
409
// could directly use the sub-expression's OriginID instead of creating a
410
// new one.
411
addAssignOriginFact(*ICE, *ICE->getSubExpr());
412
}
413
414
void VisitUnaryOperator(const UnaryOperator *UO) {
415
if (UO->getOpcode() == UO_AddrOf) {
416
const Expr *SubExpr = UO->getSubExpr();
417
if (const auto *DRE = dyn_cast<DeclRefExpr>(SubExpr)) {
418
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
419
// Check if it's a local variable.
420
if (VD->hasLocalStorage()) {
421
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*UO);
422
AccessPath AddrOfLocalVarPath(VD);
423
const Loan &L = FactMgr.getLoanMgr().addLoan(AddrOfLocalVarPath,
424
UO->getOperatorLoc());
425
CurrentBlockFacts.push_back(
426
FactMgr.createFact<IssueFact>(L.ID, OID));
427
}
428
}
429
}
430
}
431
}
432
433
void VisitReturnStmt(const ReturnStmt *RS) {
434
if (const Expr *RetExpr = RS->getRetValue()) {
435
if (hasOrigin(RetExpr->getType())) {
436
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
437
CurrentBlockFacts.push_back(
438
FactMgr.createFact<ReturnOfOriginFact>(OID));
439
}
440
}
441
}
442
443
void VisitBinaryOperator(const BinaryOperator *BO) {
444
if (BO->isAssignmentOp()) {
445
const Expr *LHSExpr = BO->getLHS();
446
const Expr *RHSExpr = BO->getRHS();
447
448
// We are interested in assignments like `ptr1 = ptr2` or `ptr = &var`
449
// LHS must be a pointer/reference type that can be an origin.
450
// RHS must also represent an origin (either another pointer/ref or an
451
// address-of).
452
if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr))
453
if (const auto *VD_LHS =
454
dyn_cast<ValueDecl>(DRE_LHS->getDecl()->getCanonicalDecl());
455
VD_LHS && hasOrigin(VD_LHS->getType()))
456
addAssignOriginFact(*VD_LHS, *RHSExpr);
457
}
458
}
459
460
private:
461
// Check if a type has an origin.
462
bool hasOrigin(QualType QT) { return QT->isPointerOrReferenceType(); }
463
464
template <typename Destination, typename Source>
465
void addAssignOriginFact(const Destination &D, const Source &S) {
466
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
467
OriginID SrcOID = FactMgr.getOriginMgr().get(S);
468
CurrentBlockFacts.push_back(
469
FactMgr.createFact<AssignOriginFact>(DestOID, SrcOID));
470
}
471
472
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
473
/// TODO: Also handle trivial destructors (e.g., for `int`
474
/// variables) which will never have a CFGAutomaticObjDtor node.
475
/// TODO: Handle loans to temporaries.
476
/// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the
477
/// lifetime ends.
478
const VarDecl *DestructedVD = DtorOpt.getVarDecl();
479
if (!DestructedVD)
480
return;
481
// Iterate through all loans to see if any expire.
482
/// TODO(opt): Do better than a linear search to find loans associated with
483
/// 'DestructedVD'.
484
for (const Loan &L : FactMgr.getLoanMgr().getLoans()) {
485
const AccessPath &LoanPath = L.Path;
486
// Check if the loan is for a stack variable and if that variable
487
// is the one being destructed.
488
if (LoanPath.D == DestructedVD)
489
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(L.ID));
490
}
491
}
492
493
FactManager &FactMgr;
494
AnalysisDeclContext &AC;
495
llvm::SmallVector<Fact *> CurrentBlockFacts;
496
};
497
498
// ========================================================================= //
499
// The Dataflow Lattice
500
// ========================================================================= //
501
502
// Using LLVM's immutable collections is efficient for dataflow analysis
503
// as it avoids deep copies during state transitions.
504
// TODO(opt): Consider using a bitset to represent the set of loans.
505
using LoanSet = llvm::ImmutableSet<LoanID>;
506
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
507
508
/// An object to hold the factories for immutable collections, ensuring
509
/// that all created states share the same underlying memory management.
510
struct LifetimeFactory {
511
OriginLoanMap::Factory OriginMapFactory;
512
LoanSet::Factory LoanSetFact;
513
514
/// Creates a singleton set containing only the given loan ID.
515
LoanSet createLoanSet(LoanID LID) {
516
return LoanSetFact.add(LoanSetFact.getEmptySet(), LID);
517
}
518
};
519
520
/// LifetimeLattice represents the state of our analysis at a given program
521
/// point. It is an immutable object, and all operations produce a new
522
/// instance rather than modifying the existing one.
523
struct LifetimeLattice {
524
/// The map from an origin to the set of loans it contains.
525
/// The lattice has a finite height: An origin's loan set is bounded by the
526
/// total number of loans in the function.
527
/// TODO(opt): To reduce the lattice size, propagate origins of declarations,
528
/// not expressions, because expressions are not visible across blocks.
529
OriginLoanMap Origins = OriginLoanMap(nullptr);
530
531
explicit LifetimeLattice(const OriginLoanMap &S) : Origins(S) {}
532
LifetimeLattice() = default;
533
534
bool operator==(const LifetimeLattice &Other) const {
535
return Origins == Other.Origins;
536
}
537
bool operator!=(const LifetimeLattice &Other) const {
538
return !(*this == Other);
539
}
540
541
LoanSet getLoans(OriginID OID) const {
542
if (auto *Loans = Origins.lookup(OID))
543
return *Loans;
544
return LoanSet(nullptr);
545
}
546
547
/// Computes the union of two lattices by performing a key-wise join of
548
/// their OriginLoanMaps.
549
// TODO(opt): This key-wise join is a performance bottleneck. A more
550
// efficient merge could be implemented using a Patricia Trie or HAMT
551
// instead of the current AVL-tree-based ImmutableMap.
552
// TODO(opt): Keep the state small by removing origins which become dead.
553
LifetimeLattice join(const LifetimeLattice &Other,
554
LifetimeFactory &Factory) const {
555
/// Merge the smaller map into the larger one ensuring we iterate over the
556
/// smaller map.
557
if (Origins.getHeight() < Other.Origins.getHeight())
558
return Other.join(*this, Factory);
559
560
OriginLoanMap JoinedState = Origins;
561
// For each origin in the other map, union its loan set with ours.
562
for (const auto &Entry : Other.Origins) {
563
OriginID OID = Entry.first;
564
LoanSet OtherLoanSet = Entry.second;
565
JoinedState = Factory.OriginMapFactory.add(
566
JoinedState, OID, join(getLoans(OID), OtherLoanSet, Factory));
567
}
568
return LifetimeLattice(JoinedState);
569
}
570
571
LoanSet join(LoanSet a, LoanSet b, LifetimeFactory &Factory) const {
572
/// Merge the smaller set into the larger one ensuring we iterate over the
573
/// smaller set.
574
if (a.getHeight() < b.getHeight())
575
std::swap(a, b);
576
LoanSet Result = a;
577
for (LoanID LID : b) {
578
/// TODO(opt): Profiling shows that this loop is a major performance
579
/// bottleneck. Investigate using a BitVector to represent the set of
580
/// loans for improved join performance.
581
Result = Factory.LoanSetFact.add(Result, LID);
582
}
583
return Result;
584
}
585
586
void dump(llvm::raw_ostream &OS) const {
587
OS << "LifetimeLattice State:\n";
588
if (Origins.isEmpty())
589
OS << " <empty>\n";
590
for (const auto &Entry : Origins) {
591
if (Entry.second.isEmpty())
592
OS << " Origin " << Entry.first << " contains no loans\n";
593
for (const LoanID &LID : Entry.second)
594
OS << " Origin " << Entry.first << " contains Loan " << LID << "\n";
595
}
596
}
597
};
598
599
// ========================================================================= //
600
// The Transfer Function
601
// ========================================================================= //
602
class Transferer {
603
FactManager &AllFacts;
604
LifetimeFactory &Factory;
605
606
public:
607
explicit Transferer(FactManager &F, LifetimeFactory &Factory)
608
: AllFacts(F), Factory(Factory) {}
609
610
/// Computes the exit state of a block by applying all its facts sequentially
611
/// to a given entry state.
612
/// TODO: We might need to store intermediate states per-fact in the block for
613
/// later analysis.
614
LifetimeLattice transferBlock(const CFGBlock *Block,
615
LifetimeLattice EntryState) {
616
LifetimeLattice BlockState = EntryState;
617
llvm::ArrayRef<const Fact *> Facts = AllFacts.getFacts(Block);
618
619
for (const Fact *F : Facts) {
620
BlockState = transferFact(BlockState, F);
621
}
622
return BlockState;
623
}
624
625
private:
626
LifetimeLattice transferFact(LifetimeLattice In, const Fact *F) {
627
switch (F->getKind()) {
628
case Fact::Kind::Issue:
629
return transfer(In, *F->getAs<IssueFact>());
630
case Fact::Kind::AssignOrigin:
631
return transfer(In, *F->getAs<AssignOriginFact>());
632
// Expire and ReturnOfOrigin facts don't modify the Origins and the State.
633
case Fact::Kind::Expire:
634
case Fact::Kind::ReturnOfOrigin:
635
return In;
636
}
637
llvm_unreachable("Unknown fact kind");
638
}
639
640
/// A new loan is issued to the origin. Old loans are erased.
641
LifetimeLattice transfer(LifetimeLattice In, const IssueFact &F) {
642
OriginID OID = F.getOriginID();
643
LoanID LID = F.getLoanID();
644
return LifetimeLattice(Factory.OriginMapFactory.add(
645
In.Origins, OID, Factory.createLoanSet(LID)));
646
}
647
648
/// The destination origin's loan set is replaced by the source's.
649
/// This implicitly "resets" the old loans of the destination.
650
LifetimeLattice transfer(LifetimeLattice InState, const AssignOriginFact &F) {
651
OriginID DestOID = F.getDestOriginID();
652
OriginID SrcOID = F.getSrcOriginID();
653
LoanSet SrcLoans = InState.getLoans(SrcOID);
654
return LifetimeLattice(
655
Factory.OriginMapFactory.add(InState.Origins, DestOID, SrcLoans));
656
}
657
};
658
659
// ========================================================================= //
660
// Dataflow analysis
661
// ========================================================================= //
662
663
/// Drives the intra-procedural dataflow analysis.
664
///
665
/// Orchestrates the analysis by iterating over the CFG using a worklist
666
/// algorithm. It computes a fixed point by propagating the LifetimeLattice
667
/// state through each block until the state no longer changes.
668
/// TODO: Maybe use the dataflow framework! The framework might need changes
669
/// to support the current comparison done at block-entry.
670
class LifetimeDataflow {
671
const CFG &Cfg;
672
AnalysisDeclContext &AC;
673
LifetimeFactory LifetimeFact;
674
675
Transferer Xfer;
676
677
/// Stores the merged analysis state at the entry of each CFG block.
678
llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockEntryStates;
679
/// Stores the analysis state at the exit of each CFG block, after the
680
/// transfer function has been applied.
681
llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockExitStates;
682
683
public:
684
LifetimeDataflow(const CFG &C, FactManager &FS, AnalysisDeclContext &AC)
685
: Cfg(C), AC(AC), Xfer(FS, LifetimeFact) {}
686
687
void run() {
688
llvm::TimeTraceScope TimeProfile("Lifetime Dataflow");
689
ForwardDataflowWorklist Worklist(Cfg, AC);
690
const CFGBlock *Entry = &Cfg.getEntry();
691
BlockEntryStates[Entry] = LifetimeLattice{};
692
Worklist.enqueueBlock(Entry);
693
while (const CFGBlock *B = Worklist.dequeue()) {
694
LifetimeLattice EntryState = getEntryState(B);
695
LifetimeLattice ExitState = Xfer.transferBlock(B, EntryState);
696
BlockExitStates[B] = ExitState;
697
698
for (const CFGBlock *Successor : B->succs()) {
699
auto SuccIt = BlockEntryStates.find(Successor);
700
LifetimeLattice OldSuccEntryState = (SuccIt != BlockEntryStates.end())
701
? SuccIt->second
702
: LifetimeLattice{};
703
LifetimeLattice NewSuccEntryState =
704
OldSuccEntryState.join(ExitState, LifetimeFact);
705
// Enqueue the successor if its entry state has changed.
706
// TODO(opt): Consider changing 'join' to report a change if !=
707
// comparison is found expensive.
708
if (SuccIt == BlockEntryStates.end() ||
709
NewSuccEntryState != OldSuccEntryState) {
710
BlockEntryStates[Successor] = NewSuccEntryState;
711
Worklist.enqueueBlock(Successor);
712
}
713
}
714
}
715
}
716
717
void dump() const {
718
llvm::dbgs() << "==========================================\n";
719
llvm::dbgs() << " Dataflow results:\n";
720
llvm::dbgs() << "==========================================\n";
721
const CFGBlock &B = Cfg.getExit();
722
getExitState(&B).dump(llvm::dbgs());
723
}
724
725
LifetimeLattice getEntryState(const CFGBlock *B) const {
726
return BlockEntryStates.lookup(B);
727
}
728
729
LifetimeLattice getExitState(const CFGBlock *B) const {
730
return BlockExitStates.lookup(B);
731
}
732
};
733
734
// ========================================================================= //
735
// TODO: Analysing dataflow results and error reporting.
736
// ========================================================================= //
737
} // anonymous namespace
738
739
void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
740
AnalysisDeclContext &AC) {
741
llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
742
DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
743
/*ShowColors=*/true));
744
FactManager FactMgr;
745
FactGenerator FactGen(FactMgr, AC);
746
FactGen.run();
747
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
748
749
/// TODO(opt): Consider optimizing individual blocks before running the
750
/// dataflow analysis.
751
/// 1. Expression Origins: These are assigned once and read at most once,
752
/// forming simple chains. These chains can be compressed into a single
753
/// assignment.
754
/// 2. Block-Local Loans: Origins of expressions are never read by other
755
/// blocks; only Decls are visible. Therefore, loans in a block that
756
/// never reach an Origin associated with a Decl can be safely dropped by
757
/// the analysis.
758
LifetimeDataflow Dataflow(Cfg, FactMgr, AC);
759
Dataflow.run();
760
DEBUG_WITH_TYPE("LifetimeDataflow", Dataflow.dump());
761
}
762
} // namespace clang
763
764