clang 23.0.0git
Checker.cpp
Go to the documentation of this file.
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
15#include "clang/AST/Decl.h"
16#include "clang/AST/Expr.h"
26#include "llvm/ADT/DenseMap.h"
27#include "llvm/Support/ErrorHandling.h"
28#include "llvm/Support/TimeProfiler.h"
29
31
33 switch (K) {
35 return true;
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 Decl *FD;
71
72 static SourceLocation
73 GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) {
74 if (const auto *UF = F.dyn_cast<const UseFact *>())
75 return UF->getUseExpr()->getExprLoc();
76 if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>()) {
77 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
78 return ReturnEsc->getReturnExpr()->getExprLoc();
79 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
80 return FieldEsc->getFieldDecl()->getLocation();
81 }
82 llvm_unreachable("unhandled causing fact in PointerUnion");
83 }
84
85public:
86 LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
87 const MovedLoansAnalysis &MovedLoans,
88 const LiveOriginsAnalysis &LiveOrigins, FactManager &FM,
89 AnalysisDeclContext &ADC,
90 LifetimeSafetySemaHelper *SemaHelper)
91 : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans),
92 LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper),
93 AST(ADC.getASTContext()), FD(ADC.getDecl()) {
94 for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
95 for (const Fact *F : FactMgr.getFacts(B))
96 if (const auto *EF = F->getAs<ExpireFact>())
97 checkExpiry(EF);
98 else if (const auto *IOF = F->getAs<InvalidateOriginFact>())
99 checkInvalidation(IOF);
100 else if (const auto *OEF = F->getAs<OriginEscapesFact>())
101 checkAnnotations(OEF);
102 issuePendingWarnings();
103 suggestAnnotations();
104 reportNoescapeViolations();
105 reportLifetimeboundViolations();
106 // Annotation inference is currently guarded by a frontend flag. In the
107 // future, this might be replaced by a design that differentiates between
108 // explicit and inferred findings with separate warning groups.
109 if (AST.getLangOpts().EnableLifetimeSafetyInference)
110 inferAnnotations();
111 }
112
113 /// Checks if an escaping origin holds a placeholder loan, indicating a
114 /// missing [[clang::lifetimebound]] annotation or a violation of
115 /// [[clang::noescape]].
116 void checkAnnotations(const OriginEscapesFact *OEF) {
117 OriginID EscapedOID = OEF->getEscapedOriginID();
118 LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
119 auto CheckParam = [&](const ParmVarDecl *PVD, bool IsMoved) {
120 // NoEscape param should not escape.
121 if (PVD->hasAttr<NoEscapeAttr>()) {
122 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
123 NoescapeWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
124 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
125 NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
126 if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
127 NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal());
128 return;
129 }
130 // Skip annotation suggestion for moved loans, as ownership transfer
131 // obscures the lifetime relationship (e.g., shared_ptr from unique_ptr).
132 if (IsMoved)
133 return;
134 if (PVD->hasAttr<LifetimeBoundAttr>()) {
135 // Track that this lifetimebound parameter correctly escapes.
136 VerifiedLiftimeboundEscapes.insert(PVD);
137 } else {
138 // Otherwise, suggest lifetimebound for parameter escaping through
139 // return or a field in constructor.
140 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
141 AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
142 else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF);
143 FieldEsc && isa<CXXConstructorDecl>(FD))
144 AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
145 }
146 // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
147 // field!
148 };
149 auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
151 VerifiedLiftimeboundEscapes.insert(MD);
152 else if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
153 AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
154 };
155 auto MovedAtEscape = MovedLoans.getMovedLoans(OEF);
156 for (LoanID LID : EscapedLoans) {
157 const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
158 const AccessPath &AP = L->getAccessPath();
159 if (const auto *PVD = AP.getAsPlaceholderParam())
160 CheckParam(PVD, /*IsMoved=*/MovedAtEscape.lookup(LID));
161 else if (const auto *MD = AP.getAsPlaceholderThis())
162 CheckImplicitThis(MD);
163 }
164 }
165
166 /// Checks for use-after-free & use-after-return errors when an access path
167 /// expires (e.g., a variable goes out of scope).
168 ///
169 /// When a path expires, all loans having this path expires.
170 /// This method examines all live origins and reports warnings for loans they
171 /// hold that are prefixed by the expired path.
172 void checkExpiry(const ExpireFact *EF) {
173 const AccessPath &ExpiredPath = EF->getAccessPath();
174 LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
175 for (auto &[OID, LiveInfo] : Origins) {
176 LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
177 for (LoanID HeldLoanID : HeldLoans) {
178 const Loan *HeldLoan = FactMgr.getLoanMgr().getLoan(HeldLoanID);
179 if (ExpiredPath != HeldLoan->getAccessPath())
180 continue;
181 // HeldLoan is expired because its AccessPath is expired.
182 PendingWarning &CurWarning = FinalWarningsMap[HeldLoan->getID()];
183 const Expr *MovedExpr = nullptr;
184 if (auto *ME = MovedLoans.getMovedLoans(EF).lookup(HeldLoanID))
185 MovedExpr = *ME;
186 // Skip if we already have a dominating causing fact.
187 if (CurWarning.CausingFactDominatesExpiry)
188 continue;
189 if (causingFactDominatesExpiry(LiveInfo.Kind))
190 CurWarning.CausingFactDominatesExpiry = true;
191 CurWarning.CausingFact = LiveInfo.CausingFact;
192 CurWarning.ExpiryLoc = EF->getExpiryLoc();
193 CurWarning.MovedExpr = MovedExpr;
194 CurWarning.InvalidatedByExpr = nullptr;
195 }
196 }
197 }
198
199 /// Checks for use-after-invalidation errors when a container is modified.
200 ///
201 /// This method identifies origins that are live at the point of invalidation
202 /// and checks if they hold loans that are invalidated by the operation
203 /// (e.g., iterators into a vector that is being pushed to).
204 void checkInvalidation(const InvalidateOriginFact *IOF) {
205 OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
206 /// Get loans directly pointing to the invalidated container
207 LoanSet DirectlyInvalidatedLoans =
208 LoanPropagation.getLoans(InvalidatedOrigin, IOF);
209 auto IsInvalidated = [&](const Loan *L) {
210 for (LoanID InvalidID : DirectlyInvalidatedLoans) {
211 const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
212 if (InvalidL->getAccessPath() == L->getAccessPath())
213 return true;
214 }
215 return false;
216 };
217 // For each live origin, check if it holds an invalidated loan and report.
218 LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
219 for (auto &[OID, LiveInfo] : Origins) {
220 LoanSet HeldLoans = LoanPropagation.getLoans(OID, IOF);
221 for (LoanID LiveLoanID : HeldLoans)
222 if (IsInvalidated(FactMgr.getLoanMgr().getLoan(LiveLoanID))) {
223 bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
224 bool LastDomination =
225 FinalWarningsMap.lookup(LiveLoanID).CausingFactDominatesExpiry;
226 if (!LastDomination) {
227 FinalWarningsMap[LiveLoanID] = {
228 /*ExpiryLoc=*/{},
229 /*CausingFact=*/LiveInfo.CausingFact,
230 /*MovedExpr=*/nullptr,
231 /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
232 /*CausingFactDominatesExpiry=*/CurDomination};
233 }
234 }
235 }
236 }
237
238 void issuePendingWarnings() {
239 if (!SemaHelper)
240 return;
241 for (const auto &[LID, Warning] : FinalWarningsMap) {
242 const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
243 const Expr *IssueExpr = L->getIssuingExpr();
244 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
245 CausingFact = Warning.CausingFact;
246 const ParmVarDecl *InvalidatedPVD =
247 L->getAccessPath().getAsPlaceholderParam();
248 const Expr *MovedExpr = Warning.MovedExpr;
249 SourceLocation ExpiryLoc = Warning.ExpiryLoc;
250
251 if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
252 if (Warning.InvalidatedByExpr) {
253 if (IssueExpr)
254 // Use-after-invalidation of an object on stack.
255 SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
256 Warning.InvalidatedByExpr);
257 else if (InvalidatedPVD)
258 // Use-after-invalidation of a parameter.
259 SemaHelper->reportUseAfterInvalidation(
260 InvalidatedPVD, UF->getUseExpr(), Warning.InvalidatedByExpr);
261
262 } else
263 // Scope-based expiry (use-after-scope).
264 SemaHelper->reportUseAfterScope(IssueExpr, UF->getUseExpr(),
265 MovedExpr, ExpiryLoc);
266 } else if (const auto *OEF =
267 CausingFact.dyn_cast<const OriginEscapesFact *>()) {
268 if (Warning.InvalidatedByExpr) {
269 if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF)) {
270 // Invalidated object escapes to a field.
271 if (IssueExpr)
272 // Invalidated object on stack escapes to a field.
273 SemaHelper->reportInvalidatedField(IssueExpr,
274 FieldEscape->getFieldDecl(),
275 Warning.InvalidatedByExpr);
276 else if (InvalidatedPVD)
277 // Invalidated parameter escapes to a field.
278 SemaHelper->reportInvalidatedField(InvalidatedPVD,
279 FieldEscape->getFieldDecl(),
280 Warning.InvalidatedByExpr);
281 } else if (const auto *GlobalEscape =
282 dyn_cast<GlobalEscapeFact>(OEF)) {
283 // Invalidated object escapes to global or static storage.
284 if (IssueExpr)
285 // Invalidated object on stack escapes to global or static
286 // storage.
287 SemaHelper->reportInvalidatedGlobal(IssueExpr,
288 GlobalEscape->getGlobal(),
289 Warning.InvalidatedByExpr);
290 else if (InvalidatedPVD)
291 // Invalidated parameter escapes to global or static storage.
292 SemaHelper->reportInvalidatedGlobal(InvalidatedPVD,
293 GlobalEscape->getGlobal(),
294 Warning.InvalidatedByExpr);
295 } else if (isa<ReturnEscapeFact>(OEF)) {
296 // FIXME: Diagnose invalidated return escapes separately.
297 } else
298 llvm_unreachable("Unhandled OriginEscapesFact type");
299 } else if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
300 // Return stack address.
301 SemaHelper->reportUseAfterReturn(
302 IssueExpr, RetEscape->getReturnExpr(), MovedExpr, ExpiryLoc);
303 else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
304 // Dangling field.
305 SemaHelper->reportDanglingField(
306 IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
307 else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
308 // Global escape.
309 SemaHelper->reportDanglingGlobal(IssueExpr, GlobalEscape->getGlobal(),
310 MovedExpr, ExpiryLoc);
311 else
312 llvm_unreachable("Unhandled OriginEscapesFact type");
313 } else
314 llvm_unreachable("Unhandled CausingFact type");
315 }
316 }
317
318 /// Returns the declaration of a function that is visible across translation
319 /// units, if such a declaration exists and is different from the definition.
320 static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
321 SourceManager &SM) {
322 if (!FD.isExternallyVisible())
323 return nullptr;
324 const FileID DefinitionFile = SM.getFileID(FD.getLocation());
325 for (const FunctionDecl *Redecl : FD.redecls())
326 if (SM.getFileID(Redecl->getLocation()) != DefinitionFile)
327 return Redecl;
328
329 return nullptr;
330 }
331
332 static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
333 SourceManager &SM) {
334 if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext()))
335 return getCrossTUDecl(*FD, SM);
336 return nullptr;
337 }
338
339 static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper,
340 const ParmVarDecl *PVD,
341 SourceManager &SM,
342 EscapingTarget EscapeTarget) {
343 if (llvm::isa<const VarDecl *>(EscapeTarget))
344 return;
345
346 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
347 SemaHelper->suggestLifetimeboundToParmVar(
349 CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()),
350 EscapeTarget);
351 else
352 SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD,
353 EscapeTarget);
354 }
355
356 static void
357 suggestWithScopeForImplicitThis(LifetimeSafetySemaHelper *SemaHelper,
358 const CXXMethodDecl *MD, SourceManager &SM,
359 const Expr *EscapeExpr) {
360 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*MD, SM))
361 SemaHelper->suggestLifetimeboundToImplicitThis(
363 EscapeExpr);
364 else
365 SemaHelper->suggestLifetimeboundToImplicitThis(SuggestionScope::IntraTU,
366 MD, EscapeExpr);
367 }
368
369 void suggestAnnotations() {
370 if (!SemaHelper)
371 return;
372 SourceManager &SM = AST.getSourceManager();
373 for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
374 if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>())
375 suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeTarget);
376 else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
377 if (const auto *EscapeExpr = EscapeTarget.dyn_cast<const Expr *>())
378 suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
379 else
380 llvm_unreachable("Implicit this can only escape via Expr (return)");
381 }
382 }
383 }
384
385 void reportNoescapeViolations() {
386 for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
387 if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
388 SemaHelper->reportNoescapeViolation(PVD, E);
389 else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
390 SemaHelper->reportNoescapeViolation(PVD, FD);
391 else if (const auto *G = EscapeTarget.dyn_cast<const VarDecl *>())
392 SemaHelper->reportNoescapeViolation(PVD, G);
393 else
394 llvm_unreachable("Unhandled EscapingTarget type");
395 }
396 }
397
398 void reportLifetimeboundViolations() {
399 if (!isa<FunctionDecl>(FD))
400 return;
401 if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
403 !VerifiedLiftimeboundEscapes.contains(MD))
404 SemaHelper->reportLifetimeboundViolation(MD);
405 for (const ParmVarDecl *PVD : cast<FunctionDecl>(FD)->parameters()) {
406 if (!PVD->hasAttr<LifetimeBoundAttr>())
407 continue;
408 bool isImplicit = PVD->getAttr<LifetimeBoundAttr>()->isImplicit();
409 bool Escapes = VerifiedLiftimeboundEscapes.contains(PVD);
410 assert((!isImplicit || Escapes || isInStlNamespace(FD)) &&
411 "Implicit lifetimebound parameters "
412 "should escape through return");
413 if (!isImplicit && !Escapes)
414 SemaHelper->reportLifetimeboundViolation(PVD);
415 }
416 }
417
418 void inferAnnotations() {
419 for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
420 if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
422 SemaHelper->addLifetimeBoundToImplicitThis(cast<CXXMethodDecl>(MD));
423 } else if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) {
424 const auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
425 if (!FD)
426 continue;
427 // Propagates inferred attributes via the most recent declaration to
428 // ensure visibility for callers in post-order analysis.
430 ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
431 FD->getParamDecl(PVD->getFunctionScopeIndex()));
432 if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
433 InferredPVD->addAttr(
434 LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
435 }
436 }
437 }
438};
439} // namespace
440
442 const MovedLoansAnalysis &MovedLoans,
443 const LiveOriginsAnalysis &LO, FactManager &FactMgr,
445 LifetimeSafetySemaHelper *SemaHelper) {
446 llvm::TimeTraceScope TimeProfile("LifetimeChecker");
447 LifetimeChecker Checker(LP, MovedLoans, LO, FactMgr, ADC, SemaHelper);
448}
449
450} // namespace clang::lifetimes::internal
This file defines AnalysisDeclContext, a class that manages the analysis context data for context sen...
#define SM(sm)
Defines the clang::SourceLocation class and associated facilities.
Defines the SourceManager interface.
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition ASTContext.h:227
AnalysisDeclContext contains the context data for the function, method or block under analysis.
Decl - This represents one declaration (or definition), e.g.
Definition DeclBase.h:86
This represents one expression.
Definition Expr.h:112
Encodes a location in the source.
Abstract interface for operations requiring Sema access.
llvm::PointerUnion< const Expr *, const FieldDecl *, const VarDecl * > EscapingTarget
llvm::ImmutableSet< LoanID > LoanSet
utils::ID< struct LoanTag > LoanID
Definition Loans.h:25
utils::ID< struct OriginTag > OriginID
Definition Origins.h:28
static bool causingFactDominatesExpiry(LivenessKind K)
Definition Checker.cpp:32
llvm::ImmutableMap< OriginID, LivenessInfo > LivenessMap
Definition LiveOrigins.h:76
void runLifetimeChecker(const LoanPropagationAnalysis &LoanPropagation, const MovedLoansAnalysis &MovedLoans, const LiveOriginsAnalysis &LiveOrigins, FactManager &FactMgr, AnalysisDeclContext &ADC, LifetimeSafetySemaHelper *SemaHelper)
Runs the lifetime checker, which detects use-after-free errors by examining loan expiration points an...
Definition Checker.cpp:441
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD)
const LifetimeBoundAttr * getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD)
const FunctionDecl * getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD)
bool isInStlNamespace(const Decl *D)
bool isa(CodeGen::Address addr)
Definition Address.h:330
if(T->getSizeExpr()) TRY_TO(TraverseStmt(const_cast< Expr * >(T -> getSizeExpr())))
for(const auto &A :T->param_types())
U cast(CodeGen::Address addr)
Definition Address.h:327