1//===- Checker.cpp - C++ Lifetime Safety Checker ----------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the LifetimeChecker, which detects use-after-free
10// errors by checking if live origins hold loans that have expired.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h"
15#include "clang/AST/Decl.h"
16#include "clang/AST/Expr.h"
17#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
18#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
19#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
20#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
21#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
22#include "clang/Analysis/Analyses/PostOrderCFGView.h"
23#include "clang/Analysis/AnalysisDeclContext.h"
24#include "clang/Basic/SourceLocation.h"
25#include "clang/Basic/SourceManager.h"
26#include "llvm/ADT/DenseMap.h"
27#include "llvm/Support/ErrorHandling.h"
28#include "llvm/Support/TimeProfiler.h"
29
30namespace clang::lifetimes::internal {
31
32static bool causingFactDominatesExpiry(LivenessKind K) {
33 switch (K) {
34 case LivenessKind::Must:
35 return true;
36 case LivenessKind::Maybe:
37 case LivenessKind::Dead:
38 return false;
39 }
40 llvm_unreachable("unknown liveness kind");
41}
42
43namespace {
44
45/// Struct to store the complete context for a potential lifetime violation.
46struct PendingWarning {
47 SourceLocation ExpiryLoc; // Where the loan expired.
48 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
49 const Expr *MovedExpr;
50 const Expr *InvalidatedByExpr;
51 bool CausingFactDominatesExpiry;
52};
53
54using AnnotationTarget =
55 llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
56using EscapingTarget = LifetimeSafetySemaHelper::EscapingTarget;
57
58class LifetimeChecker {
59private:
60 llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
61 llvm::DenseMap<AnnotationTarget, EscapingTarget> AnnotationWarningsMap;
62 llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
63 llvm::DenseSet<const Decl *> VerifiedLiftimeboundEscapes;
64 const LoanPropagationAnalysis &LoanPropagation;
65 const MovedLoansAnalysis &MovedLoans;
66 const LiveOriginsAnalysis &LiveOrigins;
67 FactManager &FactMgr;
68 LifetimeSafetySemaHelper *SemaHelper;
69 ASTContext &AST;
70 const CFG *Cfg;
71 const Decl *FD;
72 const LifetimeSafetyOpts &LSOpts;
73
74 static SourceLocation
75 GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) {
76 if (const auto *UF = dyn_cast<const UseFact *>(Val&: F))
77 return UF->getUseExpr()->getExprLoc();
78 if (const auto *OEF = dyn_cast<const OriginEscapesFact *>(Val&: F)) {
79 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(Val: OEF))
80 return ReturnEsc->getReturnExpr()->getExprLoc();
81 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(Val: OEF))
82 return FieldEsc->getFieldDecl()->getLocation();
83 }
84 llvm_unreachable("unhandled causing fact in PointerUnion");
85 }
86
87public:
88 LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
89 const MovedLoansAnalysis &MovedLoans,
90 const LiveOriginsAnalysis &LiveOrigins, FactManager &FM,
91 AnalysisDeclContext &ADC,
92 LifetimeSafetySemaHelper *SemaHelper,
93 const LifetimeSafetyOpts &LSOpts)
94 : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans),
95 LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper),
96 AST(ADC.getASTContext()), Cfg(ADC.getCFG()), FD(ADC.getDecl()),
97 LSOpts(LSOpts) {
98 for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
99 for (const Fact *F : FactMgr.getFacts(B))
100 if (const auto *EF = F->getAs<ExpireFact>())
101 checkExpiry(EF);
102 else if (const auto *IOF = F->getAs<InvalidateOriginFact>())
103 checkInvalidation(IOF);
104 else if (const auto *OEF = F->getAs<OriginEscapesFact>())
105 checkAnnotations(OEF);
106 issuePendingWarnings();
107 suggestAnnotations();
108 if (LSOpts.CheckNoescapeViolations)
109 reportNoescapeViolations();
110 if (LSOpts.CheckLifetimeboundViolations)
111 reportLifetimeboundViolations();
112 if (LSOpts.CheckMisplacedLifetimebound)
113 reportMisplacedLifetimebound();
114 if (LSOpts.CheckInapplicableLifetimebound)
115 reportInapplicableLifetimebound();
116 // Annotation inference is currently guarded by a frontend flag. In the
117 // future, this might be replaced by a design that differentiates between
118 // explicit and inferred findings with separate warning groups.
119 if (AST.getLangOpts().EnableLifetimeSafetyInference)
120 inferAnnotations();
121 }
122
123 /// Checks if an escaping origin holds a placeholder loan, indicating a
124 /// missing [[clang::lifetimebound]] annotation or a violation of
125 /// [[clang::noescape]].
126 void checkAnnotations(const OriginEscapesFact *OEF) {
127 OriginID EscapedOID = OEF->getEscapedOriginID();
128 LoanSet EscapedLoans = LoanPropagation.getLoans(OID: EscapedOID, P: OEF);
129 auto CheckParam = [&](const ParmVarDecl *PVD, bool IsMoved) {
130 // NoEscape param should not escape.
131 if (PVD->hasAttr<NoEscapeAttr>()) {
132 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(Val: OEF))
133 NoescapeWarningsMap.try_emplace(Key: PVD, Args: ReturnEsc->getReturnExpr());
134 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(Val: OEF))
135 NoescapeWarningsMap.try_emplace(Key: PVD, Args: FieldEsc->getFieldDecl());
136 if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(Val: OEF))
137 NoescapeWarningsMap.try_emplace(Key: PVD, Args: GlobalEsc->getGlobal());
138 return;
139 }
140 // Skip annotation suggestion for moved loans, as ownership transfer
141 // obscures the lifetime relationship (e.g., shared_ptr from unique_ptr).
142 if (IsMoved)
143 return;
144 if (PVD->hasAttr<LifetimeBoundAttr>()) {
145 // Track that this lifetimebound parameter correctly escapes
146 // (via return or via field assignment in a constructor).
147 if (isa<ReturnEscapeFact>(Val: OEF) ||
148 (isa<FieldEscapeFact>(Val: OEF) && isa<CXXConstructorDecl>(Val: FD)))
149 VerifiedLiftimeboundEscapes.insert(V: PVD);
150 } else {
151 // Otherwise, suggest lifetimebound for parameter escaping through
152 // return or a field in constructor.
153 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(Val: OEF))
154 AnnotationWarningsMap.try_emplace(Key: PVD, Args: ReturnEsc->getReturnExpr());
155 else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(Val: OEF);
156 FieldEsc && isa<CXXConstructorDecl>(Val: FD)) {
157 // Disable inference for pointers being captured by an owner type,
158 // as owners typically consume these pointers rather than borrow them.
159 if (!isOwnerPtrCtor(Ctor: dyn_cast<CXXConstructorDecl>(Val: FD), PVD))
160 AnnotationWarningsMap.try_emplace(Key: PVD, Args: FieldEsc->getFieldDecl());
161 }
162 }
163 // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
164 // field!
165 };
166 auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
167 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(Val: OEF)) {
168 if (implicitObjectParamIsLifetimeBound(FD: MD))
169 VerifiedLiftimeboundEscapes.insert(V: MD);
170 else
171 AnnotationWarningsMap.try_emplace(Key: MD, Args: ReturnEsc->getReturnExpr());
172 }
173 };
174 auto MovedAtEscape = MovedLoans.getMovedLoans(P: OEF);
175 for (LoanID LID : EscapedLoans) {
176 const Loan *L = FactMgr.getLoanMgr().getLoan(ID: LID);
177 const PlaceholderBase *PB = L->getAccessPath().getAsPlaceholderBase();
178 if (!PB)
179 continue;
180 if (const auto *PVD = PB->getParmVarDecl())
181 CheckParam(PVD, /*IsMoved=*/MovedAtEscape.lookup(K: LID));
182 else if (const auto *MD = PB->getImplicitThisParent())
183 CheckImplicitThis(MD);
184 }
185 }
186
187 /// Checks for use-after-free & use-after-return errors when an access path
188 /// expires (e.g., a variable goes out of scope).
189 ///
190 /// When a path expires, all loans prefixed by that path expire. For example,
191 /// if `x` expires, loans to `x`, `x.field`, and `x.field.*` all expire.
192 /// This method examines all live origins and reports warnings for loans they
193 /// hold that are prefixed by the expired path.
194 void checkExpiry(const ExpireFact *EF) {
195 const AccessPath &ExpiredPath = EF->getAccessPath();
196 LiveOriginSet Origins = LiveOrigins.getLiveOriginsAt(P: EF);
197 for (const LivenessMap &Live : {Origins.Persistent, Origins.BlockLocal})
198 for (auto &[OID, LiveInfo] : Live) {
199 LoanSet HeldLoans = LoanPropagation.getLoans(OID, P: EF);
200 for (LoanID HeldLoanID : HeldLoans) {
201 const Loan *HeldLoan = FactMgr.getLoanMgr().getLoan(ID: HeldLoanID);
202 if (!ExpiredPath.isPrefixOf(Other: HeldLoan->getAccessPath()))
203 continue;
204 // HeldLoan is expired because its base or itself is expired.
205 PendingWarning &CurWarning = FinalWarningsMap[HeldLoan->getID()];
206 const Expr *MovedExpr = nullptr;
207 if (auto *ME = MovedLoans.getMovedLoans(P: EF).lookup(K: HeldLoanID))
208 MovedExpr = *ME;
209 // Skip if we already have a dominating causing fact.
210 if (CurWarning.CausingFactDominatesExpiry)
211 continue;
212 if (causingFactDominatesExpiry(K: LiveInfo.Kind))
213 CurWarning.CausingFactDominatesExpiry = true;
214 CurWarning.CausingFact = LiveInfo.CausingFact;
215 CurWarning.ExpiryLoc = EF->getExpiryLoc();
216 CurWarning.MovedExpr = MovedExpr;
217 CurWarning.InvalidatedByExpr = nullptr;
218 }
219 }
220 }
221
222 /// Checks for use-after-invalidation errors when a container is modified.
223 ///
224 /// When a container is invalidated, loans pointing into its interior are
225 /// invalidated. For example, if container `v` is invalidated, iterators with
226 /// loans to `v.*` are invalidated. This method finds live origins holding
227 /// such loans and reports warnings. A loan is invalidated if its path extends
228 /// an invalidated container's path (e.g., `v.*` extends `v`).
229 void checkInvalidation(const InvalidateOriginFact *IOF) {
230 OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
231 /// Get loans directly pointing to the invalidated container
232 LoanSet DirectlyInvalidatedLoans =
233 LoanPropagation.getLoans(OID: InvalidatedOrigin, P: IOF);
234 auto IsInvalidated = [&](const Loan *L) {
235 for (LoanID InvalidID : DirectlyInvalidatedLoans) {
236 const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(ID: InvalidID);
237 if (InvalidL->getAccessPath().isPrefixOf(Other: L->getAccessPath()))
238 return true;
239 }
240 return false;
241 };
242 // For each live origin, check if it holds an invalidated loan and report.
243 LiveOriginSet Origins = LiveOrigins.getLiveOriginsAt(P: IOF);
244 for (const LivenessMap &Live : {Origins.Persistent, Origins.BlockLocal})
245 for (auto &[OID, LiveInfo] : Live) {
246 LoanSet HeldLoans = LoanPropagation.getLoans(OID, P: IOF);
247 for (LoanID LiveLoanID : HeldLoans)
248 if (IsInvalidated(FactMgr.getLoanMgr().getLoan(ID: LiveLoanID))) {
249 bool CurDomination = causingFactDominatesExpiry(K: LiveInfo.Kind);
250 bool LastDomination =
251 FinalWarningsMap.lookup(Val: LiveLoanID).CausingFactDominatesExpiry;
252 if (!LastDomination) {
253 FinalWarningsMap[LiveLoanID] = {
254 /*ExpiryLoc=*/{},
255 /*CausingFact=*/LiveInfo.CausingFact,
256 /*MovedExpr=*/nullptr,
257 /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
258 /*CausingFactDominatesExpiry=*/CurDomination};
259 }
260 }
261 }
262 }
263
264 void issuePendingWarnings() {
265 llvm::TimeTraceScope TimeTrace("IssuePendingWarnings");
266 if (!SemaHelper)
267 return;
268 for (const auto &[LID, Warning] : FinalWarningsMap) {
269 const Loan *L = FactMgr.getLoanMgr().getLoan(ID: LID);
270 const Expr *IssueExpr = L->getIssueExpr();
271 const ParmVarDecl *InvalidatedPVD = nullptr;
272 if (const PlaceholderBase *PB = L->getAccessPath().getAsPlaceholderBase())
273 InvalidatedPVD = PB->getParmVarDecl();
274
275 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
276 CausingFact = Warning.CausingFact;
277 const Expr *MovedExpr = Warning.MovedExpr;
278 SourceLocation ExpiryLoc = Warning.ExpiryLoc;
279
280 if (const auto *UF = dyn_cast<const UseFact *>(Val&: CausingFact)) {
281 llvm::SmallVector<const Expr *> ExprChain =
282 getExprChain(OriginFlowChain: LoanPropagation.buildOriginFlowChain(UF, TargetLoan: LID, Cfg));
283 if (Warning.InvalidatedByExpr) {
284 if (IssueExpr)
285 // Use-after-invalidation of an object on stack.
286 SemaHelper->reportUseAfterInvalidation(IssueExpr, UseExpr: UF->getUseExpr(),
287 InvalidationExpr: Warning.InvalidatedByExpr,
288 ExprChain);
289 else if (InvalidatedPVD)
290 // Use-after-invalidation of a parameter.
291 SemaHelper->reportUseAfterInvalidation(
292 PVD: InvalidatedPVD, UseExpr: UF->getUseExpr(), InvalidationExpr: Warning.InvalidatedByExpr,
293 ExprChain);
294
295 } else
296 // Scope-based expiry (use-after-scope).
297 SemaHelper->reportUseAfterScope(IssueExpr, UseExpr: UF->getUseExpr(),
298 MovedExpr, FreeLoc: ExpiryLoc, ExprChain);
299
300 } else if (const auto *OEF =
301 dyn_cast<const OriginEscapesFact *>(Val&: CausingFact)) {
302 if (Warning.InvalidatedByExpr) {
303 if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(Val: OEF)) {
304 // Invalidated object escapes to a field.
305 if (IssueExpr)
306 // Invalidated object on stack escapes to a field.
307 SemaHelper->reportInvalidatedField(IssueExpr,
308 Field: FieldEscape->getFieldDecl(),
309 InvalidationExpr: Warning.InvalidatedByExpr);
310 else if (InvalidatedPVD)
311 // Invalidated parameter escapes to a field.
312 SemaHelper->reportInvalidatedField(PVD: InvalidatedPVD,
313 Field: FieldEscape->getFieldDecl(),
314 InvalidationExpr: Warning.InvalidatedByExpr);
315 } else if (const auto *GlobalEscape =
316 dyn_cast<GlobalEscapeFact>(Val: OEF)) {
317 // Invalidated object escapes to global or static storage.
318 if (IssueExpr)
319 // Invalidated object on stack escapes to global or static
320 // storage.
321 SemaHelper->reportInvalidatedGlobal(IssueExpr,
322 Global: GlobalEscape->getGlobal(),
323 InvalidationExpr: Warning.InvalidatedByExpr);
324 else if (InvalidatedPVD)
325 // Invalidated parameter escapes to global or static storage.
326 SemaHelper->reportInvalidatedGlobal(PVD: InvalidatedPVD,
327 Global: GlobalEscape->getGlobal(),
328 InvalidationExpr: Warning.InvalidatedByExpr);
329 } else if (isa<ReturnEscapeFact>(Val: OEF)) {
330 // FIXME: Diagnose invalidated return escapes separately.
331 } else
332 llvm_unreachable("Unhandled OriginEscapesFact type");
333 } else if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(Val: OEF)) {
334 // Return stack address.
335 SemaHelper->reportUseAfterReturn(
336 IssueExpr, ReturnExpr: RetEscape->getReturnExpr(), MovedExpr,
337 ExprChain: getExprChain(
338 OriginFlowChain: LoanPropagation.buildOriginFlowChain(OEF, TargetLoan: LID, Cfg)));
339 } else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(Val: OEF)) {
340 // Dangling field.
341 bool IsCapturedByLambda =
342 FactMgr.isFieldCapturedByLambda(FD: FieldEscape->getFieldDecl());
343 SemaHelper->reportDanglingField(
344 IssueExpr, Field: FieldEscape->getFieldDecl(), MovedExpr,
345 IsCapturedByLambda, ExpiryLoc);
346 } else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(Val: OEF)) {
347 // Global escape.
348 bool IsMain = false;
349 if (const auto *Func = dyn_cast_if_present<FunctionDecl>(Val: FD))
350 IsMain = Func->isMain();
351 SemaHelper->reportDanglingGlobal(IssueExpr, DanglingGlobal: GlobalEscape->getGlobal(),
352 MovedExpr, ExpiryLoc, IsMain);
353 } else
354 llvm_unreachable("Unhandled OriginEscapesFact type");
355 } else
356 llvm_unreachable("Unhandled CausingFact type");
357 }
358 }
359
360 // Returns declarations that should be annotated with lifetime attributes
361 // in order to annotate FDef: the canonical declaration and the earliest
362 // redeclarations in each other file. This defines the placement policy for
363 // lifetime annotations. Each target is paired with its corresponding warning
364 // scope.
365 llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2>
366 getTargetDeclsForAttr(const FunctionDecl *FDef) {
367 if (!FDef)
368 return {};
369
370 assert(FDef->isThisDeclarationADefinition() &&
371 "Expected FunctionDecl to be a definition");
372
373 const auto &SM = FDef->getASTContext().getSourceManager();
374
375 auto GetFile = [&SM](const FunctionDecl *FD) {
376 return SM.getFileID(SpellingLoc: SM.getExpansionLoc(Loc: FD->getLocation()));
377 };
378
379 const FileID DefFile = GetFile(FDef);
380 const FunctionDecl *CanonicalDecl = FDef->getCanonicalDecl();
381 llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> Targets{
382 {CanonicalDecl, GetFile(CanonicalDecl) == DefFile
383 ? WarningScope::IntraTU
384 : WarningScope::CrossTU}};
385
386 // Find the earliest redeclaration in each file other than the definition
387 // file.
388 auto AddCrossTUDecl = [&](const FunctionDecl *FD) {
389 FileID File = GetFile(FD);
390 if (File == DefFile)
391 return;
392 for (auto [SeenFD, _] : Targets)
393 if (GetFile(SeenFD) == File)
394 return;
395 Targets.push_back(Elt: {FD, WarningScope::CrossTU});
396 };
397
398 // We iterate in reverse order (from most recent to oldest) to find
399 // the first declaration in each file.
400
401 // Store in temporary variable to manually extend lifetime
402 auto redecls = llvm::to_vector(Range: FDef->redecls());
403
404 for (const FunctionDecl *Redecl : llvm::reverse(C&: redecls))
405 AddCrossTUDecl(Redecl);
406
407 return Targets;
408 }
409
410 void suggestWithScopeForParmVar(const ParmVarDecl *PVD,
411 EscapingTarget EscapeTarget) {
412 if (llvm::isa<const VarDecl *>(Val: EscapeTarget))
413 return;
414
415 for (auto [Decl, Scope] : getTargetDeclsForAttr(FDef: cast<FunctionDecl>(Val: FD))) {
416 const auto *ParmToAnnotate =
417 Decl->getParamDecl(i: PVD->getFunctionScopeIndex());
418 SemaHelper->suggestLifetimeboundToParmVar(Scope, ParmToAnnotate,
419 Target: EscapeTarget);
420 }
421 }
422
423 void suggestWithScopeForImplicitThis(const CXXMethodDecl *MD,
424 const Expr *EscapeExpr) {
425 for (auto [Decl, Scope] : getTargetDeclsForAttr(FDef: MD)) {
426 SemaHelper->suggestLifetimeboundToImplicitThis(
427 Scope, MD: cast<CXXMethodDecl>(Val: Decl), EscapeExpr);
428 }
429 }
430
431 void suggestAnnotations() {
432 if (!SemaHelper)
433 return;
434 if (!LSOpts.SuggestAnnotations)
435 return;
436 llvm::TimeTraceScope TimeTrace("SuggestAnnotations");
437 for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
438 if (const auto *PVD = dyn_cast<const ParmVarDecl *>(Val&: Target))
439 suggestWithScopeForParmVar(PVD, EscapeTarget);
440 else if (const auto *MD = dyn_cast<const CXXMethodDecl *>(Val&: Target)) {
441 if (const auto *EscapeExpr = dyn_cast<const Expr *>(Val&: EscapeTarget))
442 suggestWithScopeForImplicitThis(MD, EscapeExpr);
443 else
444 llvm_unreachable("Implicit this can only escape via Expr (return)");
445 }
446 }
447 }
448
449 void reportNoescapeViolations() {
450 llvm::TimeTraceScope TimeTrace("ReportNoescapeViolations");
451 for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
452 if (const auto *E = dyn_cast<const Expr *>(Val&: EscapeTarget))
453 SemaHelper->reportNoescapeViolation(ParmWithNoescape: PVD, EscapeExpr: E);
454 else if (const auto *FD = dyn_cast<const FieldDecl *>(Val&: EscapeTarget))
455 SemaHelper->reportNoescapeViolation(ParmWithNoescape: PVD, EscapeField: FD);
456 else if (const auto *G = dyn_cast<const VarDecl *>(Val&: EscapeTarget))
457 SemaHelper->reportNoescapeViolation(ParmWithNoescape: PVD, EscapeGlobal: G);
458 else
459 llvm_unreachable("Unhandled EscapingTarget type");
460 }
461 }
462
463 void reportLifetimeboundViolations() {
464 llvm::TimeTraceScope TimeTrace("ReportLifetimeboundViolations");
465 if (!isa<FunctionDecl>(Val: FD))
466 return;
467 if (const auto *MD = dyn_cast<CXXMethodDecl>(Val: FD);
468 MD && getImplicitObjectParamLifetimeBoundAttr(FD: MD) &&
469 !VerifiedLiftimeboundEscapes.contains(V: MD))
470 SemaHelper->reportLifetimeboundViolation(MDWithLifetimebound: MD);
471 for (const ParmVarDecl *PVD : cast<FunctionDecl>(Val: FD)->parameters()) {
472 if (!PVD->hasAttr<LifetimeBoundAttr>())
473 continue;
474 bool isImplicit = PVD->getAttr<LifetimeBoundAttr>()->isImplicit();
475 bool Escapes = VerifiedLiftimeboundEscapes.contains(V: PVD);
476 assert((!isImplicit || Escapes || isInStlNamespace(FD)) &&
477 "Implicit lifetimebound parameters "
478 "should escape through return");
479 if (!isImplicit && !Escapes)
480 SemaHelper->reportLifetimeboundViolation(ParmWithLifetimebound: PVD);
481 }
482 }
483
484 // Reports lifetimebound attributes that are placed on a function definition
485 // but not on the corresponding declaration.
486 void reportMisplacedLifetimebound() {
487 llvm::TimeTraceScope TimeTrace("ReportMisplacedLifetimebound");
488 const FunctionDecl *FDef = dyn_cast<FunctionDecl>(Val: FD);
489 if (!FDef)
490 return;
491
492 auto TargetDecls = getTargetDeclsForAttr(FDef);
493 // Check if implicit 'this' has lifetimebound on definition but not on
494 // declaration.
495 if (const auto *MDef = dyn_cast<CXXMethodDecl>(Val: FDef);
496 MDef && getDirectImplicitObjectLifetimeBoundAttr(FD: MDef))
497 for (auto [Decl, Scope] : TargetDecls) {
498 const auto *MDecl = cast<CXXMethodDecl>(Val: Decl);
499 if (!getDirectImplicitObjectLifetimeBoundAttr(FD: MDecl))
500 SemaHelper->reportMisplacedLifetimebound(Scope, FDef: MDef, FDecl: MDecl);
501 }
502
503 // Check each parameter for explicit lifetimebound on definition but not on
504 // declaration.
505 for (const auto *PDef : FDef->parameters()) {
506 const auto *Attr = PDef->getAttr<LifetimeBoundAttr>();
507 if (!Attr || Attr->isImplicit())
508 continue;
509 for (auto [Decl, Scope] : TargetDecls) {
510 const auto *PDecl = Decl->getParamDecl(i: PDef->getFunctionScopeIndex());
511 if (!PDecl->hasAttr<LifetimeBoundAttr>())
512 SemaHelper->reportMisplacedLifetimebound(Scope, PVDDef: PDef, PVDDecl: PDecl);
513 }
514 }
515 }
516
517 void reportInapplicableLifetimebound() {
518 llvm::TimeTraceScope TimeTrace("ReportInapplicableLifetimebound");
519 const auto *FDef = dyn_cast<FunctionDecl>(Val: FD);
520 if (!FDef)
521 return;
522
523 // If analyzed function is a template definition or an implicit
524 // instantiation, skip.
525 if (FDef->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate ||
526 FDef->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
527 return;
528
529 for (const auto &PVD : FDef->parameters())
530 if (PVD->hasAttr<LifetimeBoundAttr>() &&
531 !FactMgr.getOriginMgr().hasOrigins(QT: PVD->getType(),
532 /*IntrinsicOnly=*/true))
533 SemaHelper->reportInapplicableLifetimebound(PVD);
534 }
535
536 void inferAnnotations() {
537 for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
538 if (const auto *MD = dyn_cast<const CXXMethodDecl *>(Val&: Target)) {
539 if (!implicitObjectParamIsLifetimeBound(FD: MD))
540 SemaHelper->addLifetimeBoundToImplicitThis(MD: cast<CXXMethodDecl>(Val: MD));
541 } else if (const auto *PVD = dyn_cast<const ParmVarDecl *>(Val&: Target)) {
542 const auto *FD = dyn_cast<FunctionDecl>(Val: PVD->getDeclContext());
543 if (!FD)
544 continue;
545 // Propagates inferred attributes via the most recent declaration to
546 // ensure visibility for callers in post-order analysis.
547 FD = getDeclWithMergedLifetimeBoundAttrs(FD);
548 ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
549 FD->getParamDecl(i: PVD->getFunctionScopeIndex()));
550 if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
551 InferredPVD->addAttr(
552 A: LifetimeBoundAttr::CreateImplicit(Ctx&: AST, Range: PVD->getLocation()));
553 }
554 }
555 }
556
557 /// Extract expressions from the origin flow chain for diagnostic purposes.
558 ///
559 /// Given a chain of origins that shows how a loan propagates, this function
560 /// extracts the corresponding expressions for each origin. Origins that refer
561 /// to declarations (rather than expressions) are skipped.
562 llvm::SmallVector<const Expr *>
563 getExprChain(llvm::ArrayRef<OriginID> OriginFlowChain) {
564 llvm::SmallVector<const Expr *> rs;
565 for (const OriginID CurrOID : OriginFlowChain)
566 if (const Expr *CurrExpr =
567 FactMgr.getOriginMgr().getOrigin(ID: CurrOID).getExpr())
568 rs.push_back(Elt: CurrExpr);
569 return rs;
570 }
571};
572} // namespace
573
574void runLifetimeChecker(const LoanPropagationAnalysis &LP,
575 const MovedLoansAnalysis &MovedLoans,
576 const LiveOriginsAnalysis &LO, FactManager &FactMgr,
577 AnalysisDeclContext &ADC,
578 LifetimeSafetySemaHelper *SemaHelper,
579 const LifetimeSafetyOpts &LSOpts) {
580 llvm::TimeTraceScope TimeProfile("LifetimeChecker");
581 LifetimeChecker Checker(LP, MovedLoans, LO, FactMgr, ADC, SemaHelper, LSOpts);
582}
583
584} // namespace clang::lifetimes::internal
585