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