Thanks to visit codestin.com
Credit goes to clang.llvm.org

clang 22.0.0git
LifetimeSafety.cpp
Go to the documentation of this file.
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//===----------------------------------------------------------------------===//
9#include "clang/AST/Decl.h"
10#include "clang/AST/Expr.h"
12#include "clang/AST/Type.h"
16#include "clang/Analysis/CFG.h"
18#include "llvm/ADT/FoldingSet.h"
19#include "llvm/ADT/ImmutableMap.h"
20#include "llvm/ADT/ImmutableSet.h"
21#include "llvm/ADT/PointerUnion.h"
22#include "llvm/ADT/SmallBitVector.h"
23#include "llvm/ADT/SmallVector.h"
24#include "llvm/Support/Debug.h"
25#include "llvm/Support/TimeProfiler.h"
26#include <cstdint>
27#include <memory>
28
29namespace clang::lifetimes {
30namespace internal {
31
32/// Represents the storage location being borrowed, e.g., a specific stack
33/// variable.
34/// TODO: Model access paths of other types, e.g., s.field, heap and globals.
35struct AccessPath {
37
39};
40
41/// Information about a single borrow, or "Loan". A loan is created when a
42/// reference or pointer is created.
43struct Loan {
44 /// TODO: Represent opaque loans.
45 /// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it
46 /// is represented as empty LoanSet
49 /// The expression that creates the loan, e.g., &x.
51
53 : ID(id), Path(path), IssueExpr(IssueExpr) {}
54
55 void dump(llvm::raw_ostream &OS) const {
56 OS << ID << " (Path: ";
57 OS << Path.D->getNameAsString() << ")";
58 }
59};
60
61/// An Origin is a symbolic identifier that represents the set of possible
62/// loans a pointer-like object could hold at any given time.
63/// TODO: Enhance the origin model to handle complex types, pointer
64/// indirection and reborrowing. The plan is to move from a single origin per
65/// variable/expression to a "list of origins" governed by the Type.
66/// For example, the type 'int**' would have two origins.
67/// See discussion:
68/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238
69struct Origin {
71 /// A pointer to the AST node that this origin represents. This union
72 /// distinguishes between origins from declarations (variables or parameters)
73 /// and origins from expressions.
74 llvm::PointerUnion<const clang::ValueDecl *, const clang::Expr *> Ptr;
75
76 Origin(OriginID ID, const clang::ValueDecl *D) : ID(ID), Ptr(D) {}
77 Origin(OriginID ID, const clang::Expr *E) : ID(ID), Ptr(E) {}
78
79 const clang::ValueDecl *getDecl() const {
80 return Ptr.dyn_cast<const clang::ValueDecl *>();
81 }
82 const clang::Expr *getExpr() const {
83 return Ptr.dyn_cast<const clang::Expr *>();
84 }
85};
86
87/// Manages the creation, storage and retrieval of loans.
89public:
90 LoanManager() = default;
91
92 Loan &addLoan(AccessPath Path, const Expr *IssueExpr) {
93 AllLoans.emplace_back(getNextLoanID(), Path, IssueExpr);
94 return AllLoans.back();
95 }
96
97 const Loan &getLoan(LoanID ID) const {
98 assert(ID.Value < AllLoans.size());
99 return AllLoans[ID.Value];
100 }
101 llvm::ArrayRef<Loan> getLoans() const { return AllLoans; }
102
103private:
104 LoanID getNextLoanID() { return NextLoanID++; }
105
106 LoanID NextLoanID{0};
107 /// TODO(opt): Profile and evaluate the usefullness of small buffer
108 /// optimisation.
110};
111
112/// Manages the creation, storage, and retrieval of origins for pointer-like
113/// variables and expressions.
115public:
116 OriginManager() = default;
117
119 AllOrigins.emplace_back(ID, &D);
120 return AllOrigins.back();
121 }
123 AllOrigins.emplace_back(ID, &E);
124 return AllOrigins.back();
125 }
126
127 // TODO: Mark this method as const once we remove the call to getOrCreate.
128 OriginID get(const Expr &E) {
129 auto It = ExprToOriginID.find(&E);
130 if (It != ExprToOriginID.end())
131 return It->second;
132 // If the expression itself has no specific origin, and it's a reference
133 // to a declaration, its origin is that of the declaration it refers to.
134 // For pointer types, where we don't pre-emptively create an origin for the
135 // DeclRefExpr itself.
136 if (const auto *DRE = dyn_cast<DeclRefExpr>(&E))
137 return get(*DRE->getDecl());
138 // TODO: This should be an assert(It != ExprToOriginID.end()). The current
139 // implementation falls back to getOrCreate to avoid crashing on
140 // yet-unhandled pointer expressions, creating an empty origin for them.
141 return getOrCreate(E);
142 }
143
145 auto It = DeclToOriginID.find(&D);
146 // TODO: This should be an assert(It != DeclToOriginID.end()). The current
147 // implementation falls back to getOrCreate to avoid crashing on
148 // yet-unhandled pointer expressions, creating an empty origin for them.
149 if (It == DeclToOriginID.end())
150 return getOrCreate(D);
151
152 return It->second;
153 }
154
156 auto It = ExprToOriginID.find(&E);
157 if (It != ExprToOriginID.end())
158 return It->second;
159
160 OriginID NewID = getNextOriginID();
161 addOrigin(NewID, E);
162 ExprToOriginID[&E] = NewID;
163 return NewID;
164 }
165
166 const Origin &getOrigin(OriginID ID) const {
167 assert(ID.Value < AllOrigins.size());
168 return AllOrigins[ID.Value];
169 }
170
171 llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }
172
174 auto It = DeclToOriginID.find(&D);
175 if (It != DeclToOriginID.end())
176 return It->second;
177 OriginID NewID = getNextOriginID();
178 addOrigin(NewID, D);
179 DeclToOriginID[&D] = NewID;
180 return NewID;
181 }
182
183 void dump(OriginID OID, llvm::raw_ostream &OS) const {
184 OS << OID << " (";
185 Origin O = getOrigin(OID);
186 if (const ValueDecl *VD = O.getDecl())
187 OS << "Decl: " << VD->getNameAsString();
188 else if (const Expr *E = O.getExpr())
189 OS << "Expr: " << E->getStmtClassName();
190 else
191 OS << "Unknown";
192 OS << ")";
193 }
194
195private:
196 OriginID getNextOriginID() { return NextOriginID++; }
197
198 OriginID NextOriginID{0};
199 /// TODO(opt): Profile and evaluate the usefullness of small buffer
200 /// optimisation.
201 llvm::SmallVector<Origin> AllOrigins;
202 llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
203 llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
204};
205
206/// An abstract base class for a single, atomic lifetime-relevant event.
207class Fact {
208
209public:
210 enum class Kind : uint8_t {
211 /// A new loan is issued from a borrow expression (e.g., &x).
213 /// A loan expires as its underlying storage is freed (e.g., variable goes
214 /// out of scope).
216 /// An origin is propagated from a source to a destination (e.g., p = q).
217 /// This can also optionally kill the destination origin before flowing into
218 /// it. Otherwise, the source's loan set is merged into the destination's
219 /// loan set.
221 /// An origin escapes the function by flowing into the return value.
223 /// An origin is used (eg. appears as l-value expression like DeclRefExpr).
225 /// A marker for a specific point in the code, for testing.
227 };
228
229private:
230 Kind K;
231
232protected:
233 Fact(Kind K) : K(K) {}
234
235public:
236 virtual ~Fact() = default;
237 Kind getKind() const { return K; }
238
239 template <typename T> const T *getAs() const {
240 if (T::classof(this))
241 return static_cast<const T *>(this);
242 return nullptr;
243 }
244
245 virtual void dump(llvm::raw_ostream &OS, const LoanManager &,
246 const OriginManager &) const {
247 OS << "Fact (Kind: " << static_cast<int>(K) << ")\n";
248 }
249};
250
251class IssueFact : public Fact {
252 LoanID LID;
253 OriginID OID;
254
255public:
256 static bool classof(const Fact *F) { return F->getKind() == Kind::Issue; }
257
258 IssueFact(LoanID LID, OriginID OID) : Fact(Kind::Issue), LID(LID), OID(OID) {}
259 LoanID getLoanID() const { return LID; }
260 OriginID getOriginID() const { return OID; }
261 void dump(llvm::raw_ostream &OS, const LoanManager &LM,
262 const OriginManager &OM) const override {
263 OS << "Issue (";
264 LM.getLoan(getLoanID()).dump(OS);
265 OS << ", ToOrigin: ";
266 OM.dump(getOriginID(), OS);
267 OS << ")\n";
268 }
269};
270
271class ExpireFact : public Fact {
272 LoanID LID;
273 SourceLocation ExpiryLoc;
274
275public:
276 static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; }
277
279 : Fact(Kind::Expire), LID(LID), ExpiryLoc(ExpiryLoc) {}
280
281 LoanID getLoanID() const { return LID; }
282 SourceLocation getExpiryLoc() const { return ExpiryLoc; }
283
284 void dump(llvm::raw_ostream &OS, const LoanManager &LM,
285 const OriginManager &) const override {
286 OS << "Expire (";
287 LM.getLoan(getLoanID()).dump(OS);
288 OS << ")\n";
289 }
290};
291
292class OriginFlowFact : public Fact {
293 OriginID OIDDest;
294 OriginID OIDSrc;
295 // True if the destination origin should be killed (i.e., its current loans
296 // cleared) before the source origin's loans are flowed into it.
297 bool KillDest;
298
299public:
300 static bool classof(const Fact *F) {
301 return F->getKind() == Kind::OriginFlow;
302 }
303
304 OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
305 : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc),
306 KillDest(KillDest) {}
307
308 OriginID getDestOriginID() const { return OIDDest; }
309 OriginID getSrcOriginID() const { return OIDSrc; }
310 bool getKillDest() const { return KillDest; }
311
312 void dump(llvm::raw_ostream &OS, const LoanManager &,
313 const OriginManager &OM) const override {
314 OS << "OriginFlow (Dest: ";
315 OM.dump(getDestOriginID(), OS);
316 OS << ", Src: ";
317 OM.dump(getSrcOriginID(), OS);
318 OS << (getKillDest() ? "" : ", Merge");
319 OS << ")\n";
320 }
321};
322
323class ReturnOfOriginFact : public Fact {
324 OriginID OID;
325
326public:
327 static bool classof(const Fact *F) {
328 return F->getKind() == Kind::ReturnOfOrigin;
329 }
330
332 OriginID getReturnedOriginID() const { return OID; }
333 void dump(llvm::raw_ostream &OS, const LoanManager &,
334 const OriginManager &OM) const override {
335 OS << "ReturnOfOrigin (";
336 OM.dump(getReturnedOriginID(), OS);
337 OS << ")\n";
338 }
339};
340
341class UseFact : public Fact {
342 const Expr *UseExpr;
343 // True if this use is a write operation (e.g., left-hand side of assignment).
344 // Write operations are exempted from use-after-free checks.
345 bool IsWritten = false;
346
347public:
348 static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
349
350 UseFact(const Expr *UseExpr) : Fact(Kind::Use), UseExpr(UseExpr) {}
351
353 // TODO: Remove const cast and make OriginManager::get as const.
354 return const_cast<OriginManager &>(OM).get(*UseExpr);
355 }
356 const Expr *getUseExpr() const { return UseExpr; }
357 void markAsWritten() { IsWritten = true; }
358 bool isWritten() const { return IsWritten; }
359
360 void dump(llvm::raw_ostream &OS, const LoanManager &,
361 const OriginManager &OM) const override {
362 OS << "Use (";
363 OM.dump(getUsedOrigin(OM), OS);
364 OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
365 }
366};
367
368/// A dummy-fact used to mark a specific point in the code for testing.
369/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
370class TestPointFact : public Fact {
371 StringRef Annotation;
372
373public:
374 static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; }
375
376 explicit TestPointFact(StringRef Annotation)
377 : Fact(Kind::TestPoint), Annotation(Annotation) {}
378
379 StringRef getAnnotation() const { return Annotation; }
380
381 void dump(llvm::raw_ostream &OS, const LoanManager &,
382 const OriginManager &) const override {
383 OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
384 }
385};
386
388public:
390 auto It = BlockToFactsMap.find(B);
391 if (It != BlockToFactsMap.end())
392 return It->second;
393 return {};
394 }
395
397 if (!NewFacts.empty())
398 BlockToFactsMap[B].assign(NewFacts.begin(), NewFacts.end());
399 }
400
401 template <typename FactType, typename... Args>
402 FactType *createFact(Args &&...args) {
403 void *Mem = FactAllocator.Allocate<FactType>();
404 return new (Mem) FactType(std::forward<Args>(args)...);
405 }
406
407 void dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
408 llvm::dbgs() << "==========================================\n";
409 llvm::dbgs() << " Lifetime Analysis Facts:\n";
410 llvm::dbgs() << "==========================================\n";
411 if (const Decl *D = AC.getDecl())
412 if (const auto *ND = dyn_cast<NamedDecl>(D))
413 llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n";
414 // Print blocks in the order as they appear in code for a stable ordering.
415 for (const CFGBlock *B : *AC.getAnalysis<PostOrderCFGView>()) {
416 llvm::dbgs() << " Block B" << B->getBlockID() << ":\n";
417 auto It = BlockToFactsMap.find(B);
418 if (It != BlockToFactsMap.end()) {
419 for (const Fact *F : It->second) {
420 llvm::dbgs() << " ";
421 F->dump(llvm::dbgs(), LoanMgr, OriginMgr);
422 }
423 }
424 llvm::dbgs() << " End of Block\n";
425 }
426 }
427
428 LoanManager &getLoanMgr() { return LoanMgr; }
429 OriginManager &getOriginMgr() { return OriginMgr; }
430
431private:
432 LoanManager LoanMgr;
433 OriginManager OriginMgr;
434 llvm::DenseMap<const clang::CFGBlock *, llvm::SmallVector<const Fact *>>
435 BlockToFactsMap;
436 llvm::BumpPtrAllocator FactAllocator;
437};
438
439class FactGenerator : public ConstStmtVisitor<FactGenerator> {
441
442public:
444 : FactMgr(FactMgr), AC(AC) {}
445
446 void run() {
447 llvm::TimeTraceScope TimeProfile("FactGenerator");
448 // Iterate through the CFG blocks in reverse post-order to ensure that
449 // initializations and destructions are processed in the correct sequence.
450 for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
451 CurrentBlockFacts.clear();
452 for (unsigned I = 0; I < Block->size(); ++I) {
453 const CFGElement &Element = Block->Elements[I];
454 if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
455 Visit(CS->getStmt());
456 else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
457 Element.getAs<CFGAutomaticObjDtor>())
458 handleDestructor(*DtorOpt);
459 }
460 FactMgr.addBlockFacts(Block, CurrentBlockFacts);
461 }
462 }
463
464 void VisitDeclStmt(const DeclStmt *DS) {
465 for (const Decl *D : DS->decls())
466 if (const auto *VD = dyn_cast<VarDecl>(D))
467 if (hasOrigin(VD))
468 if (const Expr *InitExpr = VD->getInit())
469 killAndFlowOrigin(*VD, *InitExpr);
470 }
471
472 void VisitDeclRefExpr(const DeclRefExpr *DRE) {
473 handleUse(DRE);
474 // For non-pointer/non-view types, a reference to the variable's storage
475 // is a borrow. We create a loan for it.
476 // For pointer/view types, we stick to the existing model for now and do
477 // not create an extra origin for the l-value expression itself.
478
479 // TODO: A single origin for a `DeclRefExpr` for a pointer or view type is
480 // not sufficient to model the different levels of indirection. The current
481 // single-origin model cannot distinguish between a loan to the variable's
482 // storage and a loan to what it points to. A multi-origin model would be
483 // required for this.
484 if (!isPointerType(DRE->getType())) {
485 if (const Loan *L = createLoan(DRE)) {
486 OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE);
487 CurrentBlockFacts.push_back(
488 FactMgr.createFact<IssueFact>(L->ID, ExprOID));
489 }
490 }
491 }
492
494 if (isGslPointerType(CCE->getType())) {
495 handleGSLPointerConstruction(CCE);
496 return;
497 }
498 }
499
501 // Specifically for conversion operators,
502 // like `std::string_view p = std::string{};`
503 if (isGslPointerType(MCE->getType()) &&
505 // The argument is the implicit object itself.
506 handleFunctionCall(MCE, MCE->getMethodDecl(),
507 {MCE->getImplicitObjectArgument()},
508 /*IsGslConstruction=*/true);
509 }
510 if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
511 // Construct the argument list, with the implicit 'this' object as the
512 // first argument.
514 Args.push_back(MCE->getImplicitObjectArgument());
515 Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs());
516
517 handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false);
518 }
519 }
520
521 void VisitCallExpr(const CallExpr *CE) {
522 handleFunctionCall(CE, CE->getDirectCallee(),
523 {CE->getArgs(), CE->getNumArgs()});
524 }
525
527 /// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
528 /// pointers can use the same type of loan.
529 FactMgr.getOriginMgr().getOrCreate(*N);
530 }
531
533 if (!hasOrigin(ICE))
534 return;
535 // An ImplicitCastExpr node itself gets an origin, which flows from the
536 // origin of its sub-expression (after stripping its own parens/casts).
537 killAndFlowOrigin(*ICE, *ICE->getSubExpr());
538 }
539
541 if (UO->getOpcode() == UO_AddrOf) {
542 const Expr *SubExpr = UO->getSubExpr();
543 // Taking address of a pointer-type expression is not yet supported and
544 // will be supported in multi-origin model.
545 if (isPointerType(SubExpr->getType()))
546 return;
547 // The origin of an address-of expression (e.g., &x) is the origin of
548 // its sub-expression (x). This fact will cause the dataflow analysis
549 // to propagate any loans held by the sub-expression's origin to the
550 // origin of this UnaryOperator expression.
551 killAndFlowOrigin(*UO, *SubExpr);
552 }
553 }
554
555 void VisitReturnStmt(const ReturnStmt *RS) {
556 if (const Expr *RetExpr = RS->getRetValue()) {
557 if (hasOrigin(RetExpr)) {
558 OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
559 CurrentBlockFacts.push_back(
560 FactMgr.createFact<ReturnOfOriginFact>(OID));
561 }
562 }
563 }
564
566 if (BO->isAssignmentOp())
567 handleAssignment(BO->getLHS(), BO->getRHS());
568 }
569
571 // Assignment operators have special "kill-then-propagate" semantics
572 // and are handled separately.
573 if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) {
574 handleAssignment(OCE->getArg(0), OCE->getArg(1));
575 return;
576 }
577 handleFunctionCall(OCE, OCE->getDirectCallee(),
578 {OCE->getArgs(), OCE->getNumArgs()},
579 /*IsGslConstruction=*/false);
580 }
581
583 // Check if this is a test point marker. If so, we are done with this
584 // expression.
585 if (handleTestPoint(FCE))
586 return;
587 if (isGslPointerType(FCE->getType()))
588 killAndFlowOrigin(*FCE, *FCE->getSubExpr());
589 }
590
592 if (!hasOrigin(ILE))
593 return;
594 // For list initialization with a single element, like `View{...}`, the
595 // origin of the list itself is the origin of its single element.
596 if (ILE->getNumInits() == 1)
597 killAndFlowOrigin(*ILE, *ILE->getInit(0));
598 }
599
601 if (!hasOrigin(MTE))
602 return;
603 // A temporary object's origin is the same as the origin of the
604 // expression that initializes it.
605 killAndFlowOrigin(*MTE, *MTE->getSubExpr());
606 }
607
609 /// TODO: Also handle trivial destructors (e.g., for `int`
610 /// variables) which will never have a CFGAutomaticObjDtor node.
611 /// TODO: Handle loans to temporaries.
612 /// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the
613 /// lifetime ends.
614 const VarDecl *DestructedVD = DtorOpt.getVarDecl();
615 if (!DestructedVD)
616 return;
617 // Iterate through all loans to see if any expire.
618 /// TODO(opt): Do better than a linear search to find loans associated with
619 /// 'DestructedVD'.
620 for (const Loan &L : FactMgr.getLoanMgr().getLoans()) {
621 const AccessPath &LoanPath = L.Path;
622 // Check if the loan is for a stack variable and if that variable
623 // is the one being destructed.
624 if (LoanPath.D == DestructedVD)
625 CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
626 L.ID, DtorOpt.getTriggerStmt()->getEndLoc()));
627 }
628 }
629
630private:
631 static bool isGslPointerType(QualType QT) {
632 if (const auto *RD = QT->getAsCXXRecordDecl()) {
633 // We need to check the template definition for specializations.
634 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
635 return CTSD->getSpecializedTemplate()
636 ->getTemplatedDecl()
637 ->hasAttr<PointerAttr>();
638 return RD->hasAttr<PointerAttr>();
639 }
640 return false;
641 }
642
643 static bool isPointerType(QualType QT) {
644 return QT->isPointerOrReferenceType() || isGslPointerType(QT);
645 }
646 // Check if a type has an origin.
647 static bool hasOrigin(const Expr *E) {
648 return E->isGLValue() || isPointerType(E->getType());
649 }
650
651 static bool hasOrigin(const VarDecl *VD) {
652 return isPointerType(VD->getType());
653 }
654
655 void handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
656 assert(isGslPointerType(CCE->getType()));
657 if (CCE->getNumArgs() != 1)
658 return;
659 if (hasOrigin(CCE->getArg(0)))
660 killAndFlowOrigin(*CCE, *CCE->getArg(0));
661 else
662 // This could be a new borrow.
663 handleFunctionCall(CCE, CCE->getConstructor(),
664 {CCE->getArgs(), CCE->getNumArgs()},
665 /*IsGslConstruction=*/true);
666 }
667
668 /// Checks if a call-like expression creates a borrow by passing a value to a
669 /// reference parameter, creating an IssueFact if it does.
670 /// \param IsGslConstruction True if this is a GSL construction where all
671 /// argument origins should flow to the returned origin.
672 void handleFunctionCall(const Expr *Call, const FunctionDecl *FD,
673 ArrayRef<const Expr *> Args,
674 bool IsGslConstruction = false) {
675 // Ignore functions returning values with no origin.
676 if (!FD || !hasOrigin(Call))
677 return;
678 auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
679 const ParmVarDecl *PVD = nullptr;
680 if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
681 Method && Method->isInstance()) {
682 if (I == 0)
683 // For the 'this' argument, the attribute is on the method itself.
685 if ((I - 1) < Method->getNumParams())
686 // For explicit arguments, find the corresponding parameter
687 // declaration.
688 PVD = Method->getParamDecl(I - 1);
689 } else if (I < FD->getNumParams())
690 // For free functions or static methods.
691 PVD = FD->getParamDecl(I);
692 return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
693 };
694 if (Args.empty())
695 return;
696 bool killedSrc = false;
697 for (unsigned I = 0; I < Args.size(); ++I)
698 if (IsGslConstruction || IsArgLifetimeBound(I)) {
699 if (!killedSrc) {
700 killedSrc = true;
701 killAndFlowOrigin(*Call, *Args[I]);
702 } else
703 flowOrigin(*Call, *Args[I]);
704 }
705 }
706
707 /// Creates a loan for the storage path of a given declaration reference.
708 /// This function should be called whenever a DeclRefExpr represents a borrow.
709 /// \param DRE The declaration reference expression that initiates the borrow.
710 /// \return The new Loan on success, nullptr otherwise.
711 const Loan *createLoan(const DeclRefExpr *DRE) {
712 if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) {
713 AccessPath Path(VD);
714 // The loan is created at the location of the DeclRefExpr.
715 return &FactMgr.getLoanMgr().addLoan(Path, DRE);
716 }
717 return nullptr;
718 }
719
720 template <typename Destination, typename Source>
721 void flowOrigin(const Destination &D, const Source &S) {
722 OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
723 OriginID SrcOID = FactMgr.getOriginMgr().get(S);
724 CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
725 DestOID, SrcOID, /*KillDest=*/false));
726 }
727
728 template <typename Destination, typename Source>
729 void killAndFlowOrigin(const Destination &D, const Source &S) {
730 OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
731 OriginID SrcOID = FactMgr.getOriginMgr().get(S);
732 CurrentBlockFacts.push_back(
733 FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID, /*KillDest=*/true));
734 }
735
736 /// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
737 /// If so, creates a `TestPointFact` and returns true.
738 bool handleTestPoint(const CXXFunctionalCastExpr *FCE) {
739 if (!FCE->getType()->isVoidType())
740 return false;
741
742 const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
743 if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) {
744 llvm::StringRef LiteralValue = SL->getString();
745 const std::string Prefix = "__lifetime_test_point_";
746
747 if (LiteralValue.starts_with(Prefix)) {
748 StringRef Annotation = LiteralValue.drop_front(Prefix.length());
749 CurrentBlockFacts.push_back(
750 FactMgr.createFact<TestPointFact>(Annotation));
751 return true;
752 }
753 }
754 return false;
755 }
756
757 void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr) {
758 if (!hasOrigin(LHSExpr))
759 return;
760 // Find the underlying variable declaration for the left-hand side.
761 if (const auto *DRE_LHS =
762 dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
763 markUseAsWrite(DRE_LHS);
764 if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) {
765 // Kill the old loans of the destination origin and flow the new loans
766 // from the source origin.
767 killAndFlowOrigin(*VD_LHS, *RHSExpr);
768 }
769 }
770 }
771
772 // A DeclRefExpr will be treated as a use of the referenced decl. It will be
773 // checked for use-after-free unless it is later marked as being written to
774 // (e.g. on the left-hand side of an assignment).
775 void handleUse(const DeclRefExpr *DRE) {
776 if (isPointerType(DRE->getType())) {
777 UseFact *UF = FactMgr.createFact<UseFact>(DRE);
778 CurrentBlockFacts.push_back(UF);
779 assert(!UseFacts.contains(DRE));
780 UseFacts[DRE] = UF;
781 }
782 }
783
784 void markUseAsWrite(const DeclRefExpr *DRE) {
785 if (!isPointerType(DRE->getType()))
786 return;
787 assert(UseFacts.contains(DRE));
788 UseFacts[DRE]->markAsWritten();
789 }
790
791 FactManager &FactMgr;
792 AnalysisDeclContext &AC;
793 llvm::SmallVector<Fact *> CurrentBlockFacts;
794 // To distinguish between reads and writes for use-after-free checks, this map
795 // stores the `UseFact` for each `DeclRefExpr`. We initially identify all
796 // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use
797 // corresponding to the left-hand side is updated to be a "write", thereby
798 // exempting it from the check.
799 llvm::DenseMap<const DeclRefExpr *, UseFact *> UseFacts;
800};
801
802// ========================================================================= //
803// Generic Dataflow Analysis
804// ========================================================================= //
805
806enum class Direction { Forward, Backward };
807
808/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
809/// `Fact`. identified by a lifetime-related event (`Fact`).
810///
811/// A `ProgramPoint` has "after" semantics: it represents the location
812/// immediately after its corresponding `Fact`.
813using ProgramPoint = const Fact *;
814
815/// A generic, policy-based driver for dataflow analyses. It combines
816/// the dataflow runner and the transferer logic into a single class hierarchy.
817///
818/// The derived class is expected to provide:
819/// - A `Lattice` type.
820/// - `StringRef getAnalysisName() const`
821/// - `Lattice getInitialState();` The initial state of the analysis.
822/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths.
823/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single
824/// lifetime-relevant `Fact` transforms the lattice state. Only overloads
825/// for facts relevant to the analysis need to be implemented.
826///
827/// \tparam Derived The CRTP derived class that implements the specific
828/// analysis.
829/// \tparam LatticeType The dataflow lattice used by the analysis.
830/// \tparam Dir The direction of the analysis (Forward or Backward).
831/// TODO: Maybe use the dataflow framework! The framework might need changes
832/// to support the current comparison done at block-entry.
833template <typename Derived, typename LatticeType, Direction Dir>
835public:
836 using Lattice = LatticeType;
838
839private:
840 const CFG &Cfg;
842
843 /// The dataflow state before a basic block is processed.
844 llvm::DenseMap<const CFGBlock *, Lattice> InStates;
845 /// The dataflow state after a basic block is processed.
846 llvm::DenseMap<const CFGBlock *, Lattice> OutStates;
847 /// The dataflow state at a Program Point.
848 /// In a forward analysis, this is the state after the Fact at that point has
849 /// been applied, while in a backward analysis, it is the state before.
850 llvm::DenseMap<ProgramPoint, Lattice> PerPointStates;
851
852 static constexpr bool isForward() { return Dir == Direction::Forward; }
853
854protected:
856
858 FactManager &F)
859 : Cfg(C), AC(AC), AllFacts(F) {}
860
861public:
862 void run() {
863 Derived &D = static_cast<Derived &>(*this);
864 llvm::TimeTraceScope Time(D.getAnalysisName());
865
866 using Worklist =
867 std::conditional_t<Dir == Direction::Forward, ForwardDataflowWorklist,
869 Worklist W(Cfg, AC);
870
871 const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit();
872 InStates[Start] = D.getInitialState();
873 W.enqueueBlock(Start);
874
875 llvm::SmallBitVector Visited(Cfg.getNumBlockIDs() + 1);
876
877 while (const CFGBlock *B = W.dequeue()) {
878 Lattice StateIn = getInState(B);
879 Lattice StateOut = transferBlock(B, StateIn);
880 OutStates[B] = StateOut;
881 Visited.set(B->getBlockID());
882 for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) {
883 if (!AdjacentB)
884 continue;
885 Lattice OldInState = getInState(AdjacentB);
886 Lattice NewInState = D.join(OldInState, StateOut);
887 // Enqueue the adjacent block if its in-state has changed or if we have
888 // never visited it.
889 if (!Visited.test(AdjacentB->getBlockID()) ||
890 NewInState != OldInState) {
891 InStates[AdjacentB] = NewInState;
892 W.enqueueBlock(AdjacentB);
893 }
894 }
895 }
896 }
897
898protected:
899 Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); }
900
901 Lattice getInState(const CFGBlock *B) const { return InStates.lookup(B); }
902
903 Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); }
904
905 void dump() const {
906 const Derived *D = static_cast<const Derived *>(this);
907 llvm::dbgs() << "==========================================\n";
908 llvm::dbgs() << D->getAnalysisName() << " results:\n";
909 llvm::dbgs() << "==========================================\n";
910 const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry();
911 getOutState(&B).dump(llvm::dbgs());
912 }
913
914private:
915 /// Computes the state at one end of a block by applying all its facts
916 /// sequentially to a given state from the other end.
917 Lattice transferBlock(const CFGBlock *Block, Lattice State) {
918 auto Facts = AllFacts.getFacts(Block);
919 if constexpr (isForward()) {
920 for (const Fact *F : Facts) {
921 State = transferFact(State, F);
922 PerPointStates[F] = State;
923 }
924 } else {
925 for (const Fact *F : llvm::reverse(Facts)) {
926 // In backward analysis, capture the state before applying the fact.
927 PerPointStates[F] = State;
928 State = transferFact(State, F);
929 }
930 }
931 return State;
932 }
933
934 Lattice transferFact(Lattice In, const Fact *F) {
935 assert(F);
936 Derived *D = static_cast<Derived *>(this);
937 switch (F->getKind()) {
939 return D->transfer(In, *F->getAs<IssueFact>());
941 return D->transfer(In, *F->getAs<ExpireFact>());
943 return D->transfer(In, *F->getAs<OriginFlowFact>());
945 return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
946 case Fact::Kind::Use:
947 return D->transfer(In, *F->getAs<UseFact>());
949 return D->transfer(In, *F->getAs<TestPointFact>());
950 }
951 llvm_unreachable("Unknown fact kind");
952 }
953
954public:
955 Lattice transfer(Lattice In, const IssueFact &) { return In; }
956 Lattice transfer(Lattice In, const ExpireFact &) { return In; }
957 Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
958 Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
959 Lattice transfer(Lattice In, const UseFact &) { return In; }
960 Lattice transfer(Lattice In, const TestPointFact &) { return In; }
961};
962
963namespace utils {
964
965/// Computes the union of two ImmutableSets.
966template <typename T>
967static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
968 llvm::ImmutableSet<T> B,
969 typename llvm::ImmutableSet<T>::Factory &F) {
970 if (A.getHeight() < B.getHeight())
971 std::swap(A, B);
972 for (const T &E : B)
973 A = F.add(A, E);
974 return A;
975}
976
977/// Checks if set A is a subset of set B.
978template <typename T>
979static bool isSubsetOf(const llvm::ImmutableSet<T> &A,
980 const llvm::ImmutableSet<T> &B) {
981 // Empty set is a subset of all sets.
982 if (A.isEmpty())
983 return true;
984
985 for (const T &Elem : A)
986 if (!B.contains(Elem))
987 return false;
988 return true;
989}
990
991/// Computes the key-wise union of two ImmutableMaps.
992// TODO(opt): This key-wise join is a performance bottleneck. A more
993// efficient merge could be implemented using a Patricia Trie or HAMT
994// instead of the current AVL-tree-based ImmutableMap.
995template <typename K, typename V, typename Joiner>
996static llvm::ImmutableMap<K, V>
997join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
998 typename llvm::ImmutableMap<K, V>::Factory &F, Joiner JoinValues) {
999 if (A.getHeight() < B.getHeight())
1000 std::swap(A, B);
1001
1002 // For each element in B, join it with the corresponding element in A
1003 // (or with an empty value if it doesn't exist in A).
1004 for (const auto &Entry : B) {
1005 const K &Key = Entry.first;
1006 const V &ValB = Entry.second;
1007 if (const V *ValA = A.lookup(Key))
1008 A = F.add(A, Key, JoinValues(*ValA, ValB));
1009 else
1010 A = F.add(A, Key, ValB);
1011 }
1012 return A;
1013}
1014} // namespace utils
1015
1016// ========================================================================= //
1017// Loan Propagation Analysis
1018// ========================================================================= //
1019
1020using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
1021using ExpiredLoanMap = llvm::ImmutableMap<LoanID, const ExpireFact *>;
1022
1023/// An object to hold the factories for immutable collections, ensuring
1024/// that all created states share the same underlying memory management.
1026 llvm::BumpPtrAllocator Allocator;
1027 OriginLoanMap::Factory OriginMapFactory{Allocator, /*canonicalize=*/false};
1028 LoanSet::Factory LoanSetFactory{Allocator, /*canonicalize=*/false};
1029 ExpiredLoanMap::Factory ExpiredLoanMapFactory{Allocator,
1030 /*canonicalize=*/false};
1031};
1032
1033/// Represents the dataflow lattice for loan propagation.
1034///
1035/// This lattice tracks which loans each origin may hold at a given program
1036/// point.The lattice has a finite height: An origin's loan set is bounded by
1037/// the total number of loans in the function.
1038/// TODO(opt): To reduce the lattice size, propagate origins of declarations,
1039/// not expressions, because expressions are not visible across blocks.
1041 /// The map from an origin to the set of loans it contains.
1043
1046
1048 return Origins == Other.Origins;
1049 }
1051 return !(*this == Other);
1052 }
1053
1054 void dump(llvm::raw_ostream &OS) const {
1055 OS << "LoanPropagationLattice State:\n";
1056 if (Origins.isEmpty())
1057 OS << " <empty>\n";
1058 for (const auto &Entry : Origins) {
1059 if (Entry.second.isEmpty())
1060 OS << " Origin " << Entry.first << " contains no loans\n";
1061 for (const LoanID &LID : Entry.second)
1062 OS << " Origin " << Entry.first << " contains Loan " << LID << "\n";
1063 }
1064 }
1065};
1066
1067/// The analysis that tracks which loans belong to which origins.
1069 : public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice,
1070 Direction::Forward> {
1071 OriginLoanMap::Factory &OriginLoanMapFactory;
1072 LoanSet::Factory &LoanSetFactory;
1073
1074public:
1076 LifetimeFactory &LFactory)
1077 : DataflowAnalysis(C, AC, F),
1078 OriginLoanMapFactory(LFactory.OriginMapFactory),
1079 LoanSetFactory(LFactory.LoanSetFactory) {}
1080
1081 using Base::transfer;
1082
1083 StringRef getAnalysisName() const { return "LoanPropagation"; }
1084
1086
1087 /// Merges two lattices by taking the union of loans for each origin.
1088 // TODO(opt): Keep the state small by removing origins which become dead.
1090 OriginLoanMap JoinedOrigins =
1091 utils::join(A.Origins, B.Origins, OriginLoanMapFactory,
1092 [&](LoanSet S1, LoanSet S2) {
1093 return utils::join(S1, S2, LoanSetFactory);
1094 });
1095 return Lattice(JoinedOrigins);
1096 }
1097
1098 /// A new loan is issued to the origin. Old loans are erased.
1100 OriginID OID = F.getOriginID();
1101 LoanID LID = F.getLoanID();
1102 return LoanPropagationLattice(OriginLoanMapFactory.add(
1103 In.Origins, OID,
1104 LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
1105 }
1106
1107 /// A flow from source to destination. If `KillDest` is true, this replaces
1108 /// the destination's loans with the source's. Otherwise, the source's loans
1109 /// are merged into the destination's.
1111 OriginID DestOID = F.getDestOriginID();
1112 OriginID SrcOID = F.getSrcOriginID();
1113
1114 LoanSet DestLoans =
1115 F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
1116 LoanSet SrcLoans = getLoans(In, SrcOID);
1117 LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
1118
1120 OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans));
1121 }
1122
1124 return getLoans(getState(P), OID);
1125 }
1126
1127private:
1129 if (auto *Loans = L.Origins.lookup(OID))
1130 return *Loans;
1131 return LoanSetFactory.getEmptySet();
1132 }
1133};
1134
1135// ========================================================================= //
1136// Expired Loans Analysis
1137// ========================================================================= //
1138
1139/// The dataflow lattice for tracking the set of expired loans.
1141 /// Map from an expired `LoanID` to the `ExpireFact` that made it expire.
1143
1146
1147 bool operator==(const ExpiredLattice &Other) const {
1148 return Expired == Other.Expired;
1149 }
1150 bool operator!=(const ExpiredLattice &Other) const {
1151 return !(*this == Other);
1152 }
1153
1154 void dump(llvm::raw_ostream &OS) const {
1155 OS << "ExpiredLattice State:\n";
1156 if (Expired.isEmpty())
1157 OS << " <empty>\n";
1158 for (const auto &[ID, _] : Expired)
1159 OS << " Loan " << ID << " is expired\n";
1160 }
1161};
1162
1163/// The analysis that tracks which loans have expired.
1165 : public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice,
1166 Direction::Forward> {
1167
1168 ExpiredLoanMap::Factory &Factory;
1169
1170public:
1172 LifetimeFactory &Factory)
1173 : DataflowAnalysis(C, AC, F), Factory(Factory.ExpiredLoanMapFactory) {}
1174
1175 using Base::transfer;
1176
1177 StringRef getAnalysisName() const { return "ExpiredLoans"; }
1178
1179 Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); }
1180
1181 /// Merges two lattices by taking the union of the two expired loans.
1183 return Lattice(
1184 utils::join(L1.Expired, L2.Expired, Factory,
1185 // Take the last expiry fact to make this hermetic.
1186 [](const ExpireFact *F1, const ExpireFact *F2) {
1187 return F1->getExpiryLoc() > F2->getExpiryLoc() ? F1 : F2;
1188 }));
1189 }
1190
1192 return Lattice(Factory.add(In.Expired, F.getLoanID(), &F));
1193 }
1194
1195 // Removes the loan from the set of expired loans.
1196 //
1197 // When a loan is re-issued (e.g., in a loop), it is no longer considered
1198 // expired. A loan can be in the expired set at the point of issue due to
1199 // the dataflow state from a previous loop iteration being propagated along
1200 // a backedge in the CFG.
1201 //
1202 // Note: This has a subtle false-negative though where a loan from previous
1203 // iteration is not overwritten by a reissue. This needs careful tracking
1204 // of loans "across iterations" which can be considered for future
1205 // enhancements.
1206 //
1207 // void foo(int safe) {
1208 // int* p = &safe;
1209 // int* q = &safe;
1210 // while (condition()) {
1211 // int x = 1;
1212 // p = &x; // A loan to 'x' is issued to 'p' in every iteration.
1213 // if (condition()) {
1214 // q = p;
1215 // }
1216 // (void)*p; // OK — 'p' points to 'x' from new iteration.
1217 // (void)*q; // UaF - 'q' still points to 'x' from previous iteration
1218 // // which is now destroyed.
1219 // }
1220 // }
1222 return Lattice(Factory.remove(In.Expired, F.getLoanID()));
1223 }
1224
1226};
1227
1228// ========================================================================= //
1229// Lifetime checker and Error reporter
1230// ========================================================================= //
1231
1232/// Struct to store the complete context for a potential lifetime violation.
1234 SourceLocation ExpiryLoc; // Where the loan expired.
1235 const Expr *UseExpr; // Where the origin holding this loan was used.
1237};
1238
1240private:
1241 llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
1242 LoanPropagationAnalysis &LoanPropagation;
1243 ExpiredLoansAnalysis &ExpiredLoans;
1244 FactManager &FactMgr;
1246 LifetimeSafetyReporter *Reporter;
1247
1248public:
1251 LifetimeSafetyReporter *Reporter)
1252 : LoanPropagation(LPA), ExpiredLoans(ELA), FactMgr(FM), ADC(ADC),
1253 Reporter(Reporter) {}
1254
1255 void run() {
1256 llvm::TimeTraceScope TimeProfile("LifetimeChecker");
1257 for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
1258 for (const Fact *F : FactMgr.getFacts(B))
1259 if (const auto *UF = F->getAs<UseFact>())
1260 checkUse(UF);
1262 }
1263
1264 /// Checks for use-after-free errors for a given use of an Origin.
1265 ///
1266 /// This method is called for each 'UseFact' identified in the control flow
1267 /// graph. It determines if the loans held by the used origin have expired
1268 /// at the point of use.
1269 void checkUse(const UseFact *UF) {
1270 if (UF->isWritten())
1271 return;
1272 OriginID O = UF->getUsedOrigin(FactMgr.getOriginMgr());
1273
1274 // Get the set of loans that the origin might hold at this program point.
1275 LoanSet HeldLoans = LoanPropagation.getLoans(O, UF);
1276
1277 // Get the set of all loans that have expired at this program point.
1278 ExpiredLoanMap AllExpiredLoans = ExpiredLoans.getExpiredLoans(UF);
1279
1280 // If the pointer holds no loans or no loans have expired, there's nothing
1281 // to check.
1282 if (HeldLoans.isEmpty() || AllExpiredLoans.isEmpty())
1283 return;
1284
1285 // Identify loans that which have expired but are held by the pointer. Using
1286 // them is a use-after-free.
1287 llvm::SmallVector<LoanID> DefaultedLoans;
1288 // A definite UaF error occurs if all loans the origin might hold have
1289 // expired.
1290 bool IsDefiniteError = true;
1291 for (LoanID L : HeldLoans) {
1292 if (AllExpiredLoans.contains(L))
1293 DefaultedLoans.push_back(L);
1294 else
1295 // If at least one loan is not expired, this use is not a definite UaF.
1296 IsDefiniteError = false;
1297 }
1298 // If there are no defaulted loans, the use is safe.
1299 if (DefaultedLoans.empty())
1300 return;
1301
1302 // Determine the confidence level of the error (definite or maybe).
1303 Confidence CurrentConfidence =
1304 IsDefiniteError ? Confidence::Definite : Confidence::Maybe;
1305
1306 // For each expired loan, create a pending warning.
1307 for (LoanID DefaultedLoan : DefaultedLoans) {
1308 // If we already have a warning for this loan with a higher or equal
1309 // confidence, skip this one.
1310 if (FinalWarningsMap.count(DefaultedLoan) &&
1311 CurrentConfidence <= FinalWarningsMap[DefaultedLoan].ConfidenceLevel)
1312 continue;
1313
1314 auto *EF = AllExpiredLoans.lookup(DefaultedLoan);
1315 assert(EF && "Could not find ExpireFact for an expired loan.");
1316
1317 FinalWarningsMap[DefaultedLoan] = {/*ExpiryLoc=*/(*EF)->getExpiryLoc(),
1318 /*UseExpr=*/UF->getUseExpr(),
1319 /*ConfidenceLevel=*/CurrentConfidence};
1320 }
1321 }
1322
1324 if (!Reporter)
1325 return;
1326 for (const auto &[LID, Warning] : FinalWarningsMap) {
1327 const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
1328 const Expr *IssueExpr = L.IssueExpr;
1329 Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr,
1330 Warning.ExpiryLoc, Warning.ConfidenceLevel);
1331 }
1332 }
1333};
1334
1335// ========================================================================= //
1336// LifetimeSafetyAnalysis Class Implementation
1337// ========================================================================= //
1338
1339// We need this here for unique_ptr with forward declared class.
1341
1343 LifetimeSafetyReporter *Reporter)
1344 : AC(AC), Reporter(Reporter), Factory(std::make_unique<LifetimeFactory>()),
1345 FactMgr(std::make_unique<FactManager>()) {}
1346
1348 llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
1349
1350 const CFG &Cfg = *AC.getCFG();
1351 DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
1352 /*ShowColors=*/true));
1353
1354 FactGenerator FactGen(*FactMgr, AC);
1355 FactGen.run();
1356 DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC));
1357
1358 /// TODO(opt): Consider optimizing individual blocks before running the
1359 /// dataflow analysis.
1360 /// 1. Expression Origins: These are assigned once and read at most once,
1361 /// forming simple chains. These chains can be compressed into a single
1362 /// assignment.
1363 /// 2. Block-Local Loans: Origins of expressions are never read by other
1364 /// blocks; only Decls are visible. Therefore, loans in a block that
1365 /// never reach an Origin associated with a Decl can be safely dropped by
1366 /// the analysis.
1367 /// 3. Collapse ExpireFacts belonging to same source location into a single
1368 /// Fact.
1369 LoanPropagation =
1370 std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
1371 LoanPropagation->run();
1372
1373 ExpiredLoans =
1374 std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory);
1375 ExpiredLoans->run();
1376
1377 LifetimeChecker Checker(*LoanPropagation, *ExpiredLoans, *FactMgr, AC,
1378 Reporter);
1379 Checker.run();
1380}
1381
1383 ProgramPoint PP) const {
1384 assert(LoanPropagation && "Analysis has not been run.");
1385 return LoanPropagation->getLoans(OID, PP);
1386}
1387
1388std::vector<LoanID>
1390 assert(ExpiredLoans && "ExpiredLoansAnalysis has not been run.");
1391 std::vector<LoanID> Result;
1392 for (const auto &pair : ExpiredLoans->getExpiredLoans(PP))
1393 Result.push_back(pair.first);
1394 return Result;
1395}
1396
1397std::optional<OriginID>
1399 assert(FactMgr && "FactManager not initialized");
1400 // This assumes the OriginManager's `get` can find an existing origin.
1401 // We might need a `find` method on OriginManager to avoid `getOrCreate` logic
1402 // in a const-query context if that becomes an issue.
1403 return FactMgr->getOriginMgr().get(*D);
1404}
1405
1406std::vector<LoanID>
1408 assert(FactMgr && "FactManager not initialized");
1409 std::vector<LoanID> Result;
1410 for (const Loan &L : FactMgr->getLoanMgr().getLoans())
1411 if (L.Path.D == VD)
1412 Result.push_back(L.ID);
1413 return Result;
1414}
1415
1416llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const {
1417 assert(FactMgr && "FactManager not initialized");
1418 llvm::StringMap<ProgramPoint> AnnotationToPointMap;
1419 for (const CFGBlock *Block : *AC.getCFG()) {
1420 for (const Fact *F : FactMgr->getFacts(Block)) {
1421 if (const auto *TPF = F->getAs<TestPointFact>()) {
1422 StringRef PointName = TPF->getAnnotation();
1423 assert(AnnotationToPointMap.find(PointName) ==
1424 AnnotationToPointMap.end() &&
1425 "more than one test points with the same name");
1426 AnnotationToPointMap[PointName] = F;
1427 }
1428 }
1429 }
1430 return AnnotationToPointMap;
1431}
1432} // namespace internal
1433
1435 LifetimeSafetyReporter *Reporter) {
1436 internal::LifetimeSafetyAnalysis Analysis(AC, Reporter);
1437 Analysis.run();
1438}
1439} // namespace clang::lifetimes
#define V(N, I)
This file defines AnalysisDeclContext, a class that manages the analysis context data for context sen...
C Language Family Type Representation.
AnalysisDeclContext contains the context data for the function, method or block under analysis.
A builtin binary operation expression such as "x + y" or "x <= y".
Definition Expr.h:3972
Expr * getLHS() const
Definition Expr.h:4022
Expr * getRHS() const
Definition Expr.h:4024
static bool isAssignmentOp(Opcode Opc)
Definition Expr.h:4108
Represents C++ object destructor implicitly generated for automatic object or temporary bound to cons...
Definition CFG.h:418
const VarDecl * getVarDecl() const
Definition CFG.h:423
const Stmt * getTriggerStmt() const
Definition CFG.h:428
Represents a single basic block in a source-level CFG.
Definition CFG.h:605
succ_range succs()
Definition CFG.h:1000
void dump() const
Definition CFG.cpp:6255
Represents a top-level expression in a basic block.
Definition CFG.h:55
std::optional< T > getAs() const
Convert to the specified CFGElement type, returning std::nullopt if this CFGElement is not of the des...
Definition CFG.h:109
Represents a source-level, intra-procedural CFG that represents the control-flow of a Stmt.
Definition CFG.h:1222
void dump(const LangOptions &LO, bool ShowColors) const
dump - A simple pretty printer of a CFG that outputs to stderr.
Definition CFG.cpp:6219
Represents a call to a C++ constructor.
Definition ExprCXX.h:1549
Represents an explicit C++ type conversion that uses "functional" notation (C++ [expr....
Definition ExprCXX.h:1833
Represents a call to a member function that may be written either with member call syntax (e....
Definition ExprCXX.h:179
CXXMethodDecl * getMethodDecl() const
Retrieve the declaration of the called method.
Definition ExprCXX.cpp:741
Expr * getImplicitObjectArgument() const
Retrieve the implicit object argument for the member call.
Definition ExprCXX.cpp:722
Represents a static or instance method of a struct/union/class.
Definition DeclCXX.h:2129
The null pointer literal (C++11 [lex.nullptr])
Definition ExprCXX.h:768
A call to an overloaded operator written using operator syntax.
Definition ExprCXX.h:84
static bool isAssignmentOp(OverloadedOperatorKind Opc)
Definition ExprCXX.h:119
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition Expr.h:2877
Expr * getArg(unsigned Arg)
getArg - Return the specified argument.
Definition Expr.h:3081
FunctionDecl * getDirectCallee()
If the callee is a FunctionDecl, return it. Otherwise return null.
Definition Expr.h:3060
unsigned getNumArgs() const
getNumArgs - Return the number of actual arguments to this call.
Definition Expr.h:3068
Expr ** getArgs()
Retrieve the call arguments.
Definition Expr.h:3071
Decl * getCalleeDecl()
Definition Expr.h:3054
Expr * getSubExpr()
Definition Expr.h:3660
ConstStmtVisitor - This class implements a simple visitor for Stmt subclasses.
A reference to a declared variable, function, enum, etc.
Definition Expr.h:1270
DeclStmt - Adaptor class for mixing declarations with statements and expressions.
Definition Stmt.h:1611
decl_range decls()
Definition Stmt.h:1659
Decl - This represents one declaration (or definition), e.g.
Definition DeclBase.h:86
This represents one expression.
Definition Expr.h:112
QualType getType() const
Definition Expr.h:144
ImplicitCastExpr - Allows us to explicitly represent implicit type conversions, which have no direct ...
Definition Expr.h:3787
Describes an C or C++ initializer list.
Definition Expr.h:5233
unsigned getNumInits() const
Definition Expr.h:5263
const Expr * getInit(unsigned Init) const
Definition Expr.h:5287
Represents a prvalue temporary that is written into memory so that a reference can bind to it.
Definition ExprCXX.h:4914
Expr * getSubExpr() const
Retrieve the temporary-generating subexpression whose value will be materialized into a glvalue.
Definition ExprCXX.h:4931
A (possibly-)qualified type.
Definition TypeBase.h:937
ReturnStmt - This represents a return, optionally of an expression: return; return 4;.
Definition Stmt.h:3160
Expr * getRetValue()
Definition Stmt.h:3187
Encodes a location in the source.
RetTy Visit(PTR(Stmt) S, ParamTys... P)
Definition StmtVisitor.h:45
SourceLocation getEndLoc() const LLVM_READONLY
Definition Stmt.cpp:358
CXXRecordDecl * getAsCXXRecordDecl() const
Retrieves the CXXRecordDecl that this type refers to, either because the type is a RecordType or beca...
Definition Type.h:26
bool isPointerOrReferenceType() const
Definition TypeBase.h:8526
UnaryOperator - This represents the unary-expression's (except sizeof and alignof),...
Definition Expr.h:2244
Expr * getSubExpr() const
Definition Expr.h:2285
Opcode getOpcode() const
Definition Expr.h:2280
Represent the declaration of a variable (in which case it is an lvalue) a function (in which case it ...
Definition Decl.h:711
Represents a variable declaration or definition.
Definition Decl.h:925
Lattice getInState(const CFGBlock *B) const
Lattice transfer(Lattice In, const OriginFlowFact &)
Lattice transfer(Lattice In, const TestPointFact &)
Lattice getOutState(const CFGBlock *B) const
DataflowAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F)
Lattice transfer(Lattice In, const ReturnOfOriginFact &)
Lattice transfer(Lattice In, const UseFact &)
Lattice transfer(Lattice In, const IssueFact &)
Lattice transfer(Lattice In, const ExpireFact &)
DataflowAnalysis< Derived, Lattice, Dir > Base
ExpireFact(LoanID LID, SourceLocation ExpiryLoc)
void dump(llvm::raw_ostream &OS, const LoanManager &LM, const OriginManager &) const override
The analysis that tracks which loans have expired.
Lattice transfer(Lattice In, const ExpireFact &F)
Lattice transfer(Lattice In, const IssueFact &F)
ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, LifetimeFactory &Factory)
Lattice join(Lattice L1, Lattice L2)
Merges two lattices by taking the union of the two expired loans.
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE)
void VisitUnaryOperator(const UnaryOperator *UO)
void VisitInitListExpr(const InitListExpr *ILE)
void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N)
void VisitImplicitCastExpr(const ImplicitCastExpr *ICE)
void VisitBinaryOperator(const BinaryOperator *BO)
void VisitCXXConstructExpr(const CXXConstructExpr *CCE)
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt)
void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE)
FactGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE)
void VisitDeclRefExpr(const DeclRefExpr *DRE)
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE)
llvm::ArrayRef< const Fact * > getFacts(const CFGBlock *B) const
void dump(const CFG &Cfg, AnalysisDeclContext &AC) const
void addBlockFacts(const CFGBlock *B, llvm::ArrayRef< Fact * > NewFacts)
An abstract base class for a single, atomic lifetime-relevant event.
@ TestPoint
A marker for a specific point in the code, for testing.
@ Expire
A loan expires as its underlying storage is freed (e.g., variable goes out of scope).
@ ReturnOfOrigin
An origin escapes the function by flowing into the return value.
@ Issue
A new loan is issued from a borrow expression (e.g., &x).
@ OriginFlow
An origin is propagated from a source to a destination (e.g., p = q).
@ Use
An origin is used (eg. appears as l-value expression like DeclRefExpr).
virtual void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &) const
void dump(llvm::raw_ostream &OS, const LoanManager &LM, const OriginManager &OM) const override
LifetimeChecker(LoanPropagationAnalysis &LPA, ExpiredLoansAnalysis &ELA, FactManager &FM, AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
void checkUse(const UseFact *UF)
Checks for use-after-free errors for a given use of an Origin.
Running the lifetime safety analysis and querying its results.
LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const
Returns the set of loans an origin holds at a specific program point.
std::optional< OriginID > getOriginIDForDecl(const ValueDecl *D) const
Finds the OriginID for a given declaration.
std::vector< LoanID > getExpiredLoansAtPoint(ProgramPoint PP) const
Returns the set of loans that have expired at a specific program point.
std::vector< LoanID > getLoanIDForVar(const VarDecl *VD) const
Finds the LoanID's for the loan created with the specific variable as their Path.
llvm::StringMap< ProgramPoint > getTestPoints() const
Retrieves program points that were specially marked in the source code for testing.
LifetimeSafetyAnalysis(AnalysisDeclContext &AC, LifetimeSafetyReporter *Reporter)
Manages the creation, storage and retrieval of loans.
const Loan & getLoan(LoanID ID) const
Loan & addLoan(AccessPath Path, const Expr *IssueExpr)
llvm::ArrayRef< Loan > getLoans() const
The analysis that tracks which loans belong to which origins.
LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, LifetimeFactory &LFactory)
Lattice transfer(Lattice In, const IssueFact &F)
A new loan is issued to the origin. Old loans are erased.
Lattice join(Lattice A, Lattice B)
Merges two lattices by taking the union of loans for each origin.
Lattice transfer(Lattice In, const OriginFlowFact &F)
A flow from source to destination.
LoanSet getLoans(OriginID OID, ProgramPoint P)
void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const override
OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
Manages the creation, storage, and retrieval of origins for pointer-like variables and expressions.
OriginID getOrCreate(const ValueDecl &D)
const Origin & getOrigin(OriginID ID) const
Origin & addOrigin(OriginID ID, const clang::Expr &E)
llvm::ArrayRef< Origin > getOrigins() const
void dump(OriginID OID, llvm::raw_ostream &OS) const
Origin & addOrigin(OriginID ID, const clang::ValueDecl &D)
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.
void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &) const override
void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const override
OriginID getUsedOrigin(const OriginManager &OM) const
static bool classof(const Fact *F)
static bool isSubsetOf(const llvm::ImmutableSet< T > &A, const llvm::ImmutableSet< T > &B)
Checks if set A is a subset of set B.
static llvm::ImmutableSet< T > join(llvm::ImmutableSet< T > A, llvm::ImmutableSet< T > B, typename llvm::ImmutableSet< T >::Factory &F)
Computes the union of two ImmutableSets.
const Fact * ProgramPoint
A ProgramPoint identifies a location in the CFG by pointing to a specific Fact.
llvm::ImmutableSet< LoanID > LoanSet
llvm::ImmutableMap< OriginID, LoanSet > OriginLoanMap
ID< struct OriginTag > OriginID
llvm::ImmutableMap< LoanID, const ExpireFact * > ExpiredLoanMap
ID< struct LoanTag > LoanID
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, LifetimeSafetyReporter *Reporter)
The main entry point for the analysis.
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD)
Returns true if the implicit object parameter (this) should be considered lifetimebound,...
Confidence
Enum to track the confidence level of a potential error.
bool isa(CodeGen::Address addr)
Definition Address.h:330
nullptr
This class represents a compute construct, representing a 'Kind' of ‘parallel’, 'serial',...
@ Result
The result type of a method or function.
Definition TypeBase.h:905
const FunctionProtoType * T
@ Other
Other implicit parameter.
Definition Decl.h:1745
#define false
Definition stdbool.h:26
A worklist implementation for backward dataflow analysis.
A worklist implementation for forward dataflow analysis.
Represents the storage location being borrowed, e.g., a specific stack variable.
AccessPath(const clang::ValueDecl *D)
ExpiredLoanMap Expired
Map from an expired LoanID to the ExpireFact that made it expire.
bool operator==(const ExpiredLattice &Other) const
void dump(llvm::raw_ostream &OS) const
bool operator!=(const ExpiredLattice &Other) const
A generic, type-safe wrapper for an ID, distinguished by its Tag type.
An object to hold the factories for immutable collections, ensuring that all created states share the...
Represents the dataflow lattice for loan propagation.
bool operator!=(const LoanPropagationLattice &Other) const
OriginLoanMap Origins
The map from an origin to the set of loans it contains.
bool operator==(const LoanPropagationLattice &Other) const
Information about a single borrow, or "Loan".
Loan(LoanID id, AccessPath path, const Expr *IssueExpr)
const Expr * IssueExpr
The expression that creates the loan, e.g., &x.
LoanID ID
TODO: Represent opaque loans.
void dump(llvm::raw_ostream &OS) const
An Origin is a symbolic identifier that represents the set of possible loans a pointer-like object co...
Origin(OriginID ID, const clang::Expr *E)
const clang::Expr * getExpr() const
const clang::ValueDecl * getDecl() const
llvm::PointerUnion< const clang::ValueDecl *, const clang::Expr * > Ptr
A pointer to the AST node that this origin represents.
Origin(OriginID ID, const clang::ValueDecl *D)
Struct to store the complete context for a potential lifetime violation.