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 const LoanPropagationAnalysis &LoanPropagation;
64 const MovedLoansAnalysis &MovedLoans;
65 const LiveOriginsAnalysis &LiveOrigins;
66 FactManager &FactMgr;
67 LifetimeSafetySemaHelper *SemaHelper;
68 ASTContext &AST;
69 const Decl *FD;
70
71 static SourceLocation
72 GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) {
73 if (const auto *UF = F.dyn_cast<const UseFact *>())
74 return UF->getUseExpr()->getExprLoc();
75 if (const auto *OEF = F.dyn_cast<const OriginEscapesFact *>()) {
76 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
77 return ReturnEsc->getReturnExpr()->getExprLoc();
78 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
79 return FieldEsc->getFieldDecl()->getLocation();
80 }
81 llvm_unreachable("unhandled causing fact in PointerUnion");
82 }
83
84public:
85 LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
86 const MovedLoansAnalysis &MovedLoans,
87 const LiveOriginsAnalysis &LiveOrigins, FactManager &FM,
88 AnalysisDeclContext &ADC,
89 LifetimeSafetySemaHelper *SemaHelper)
90 : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans),
91 LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper),
92 AST(ADC.getASTContext()), FD(ADC.getDecl()) {
93 for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
94 for (const Fact *F : FactMgr.getFacts(B))
95 if (const auto *EF = F->getAs<ExpireFact>())
96 checkExpiry(EF);
97 else if (const auto *IOF = F->getAs<InvalidateOriginFact>())
98 checkInvalidation(IOF);
99 else if (const auto *OEF = F->getAs<OriginEscapesFact>())
100 checkAnnotations(OEF);
101 issuePendingWarnings();
102 suggestAnnotations();
103 reportNoescapeViolations();
104 // Annotation inference is currently guarded by a frontend flag. In the
105 // future, this might be replaced by a design that differentiates between
106 // explicit and inferred findings with separate warning groups.
107 if (AST.getLangOpts().EnableLifetimeSafetyInference)
108 inferAnnotations();
109 }
110
111 /// Checks if an escaping origin holds a placeholder loan, indicating a
112 /// missing [[clang::lifetimebound]] annotation or a violation of
113 /// [[clang::noescape]].
114 void checkAnnotations(const OriginEscapesFact *OEF) {
115 OriginID EscapedOID = OEF->getEscapedOriginID();
116 LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
117 auto CheckParam = [&](const ParmVarDecl *PVD) {
118 // NoEscape param should not escape.
119 if (PVD->hasAttr<NoEscapeAttr>()) {
120 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
121 NoescapeWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
122 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
123 NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
124 if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
125 NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal());
126 return;
127 }
128 // Suggest lifetimebound for parameter escaping through return or a field
129 // in constructor.
130 if (!PVD->hasAttr<LifetimeBoundAttr>()) {
131 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
132 AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
133 else if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF);
134 FieldEsc && isa<CXXConstructorDecl>(FD))
135 AnnotationWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
136 }
137 // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
138 // field!
139 };
140 auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
142 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
143 AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
144 };
145 for (LoanID LID : EscapedLoans) {
146 const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
147 const AccessPath &AP = L->getAccessPath();
148 if (const auto *PVD = AP.getAsPlaceholderParam())
149 CheckParam(PVD);
150 else if (const auto *MD = AP.getAsPlaceholderThis())
151 CheckImplicitThis(MD);
152 }
153 }
154
155 /// Checks for use-after-free & use-after-return errors when an access path
156 /// expires (e.g., a variable goes out of scope).
157 ///
158 /// When a path expires, all loans having this path expires.
159 /// This method examines all live origins and reports warnings for loans they
160 /// hold that are prefixed by the expired path.
161 void checkExpiry(const ExpireFact *EF) {
162 const AccessPath &ExpiredPath = EF->getAccessPath();
163 LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
164 for (auto &[OID, LiveInfo] : Origins) {
165 LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
166 for (LoanID HeldLoanID : HeldLoans) {
167 const Loan *HeldLoan = FactMgr.getLoanMgr().getLoan(HeldLoanID);
168 if (ExpiredPath != HeldLoan->getAccessPath())
169 continue;
170 // HeldLoan is expired because its AccessPath is expired.
171 PendingWarning &CurWarning = FinalWarningsMap[HeldLoan->getID()];
172 const Expr *MovedExpr = nullptr;
173 if (auto *ME = MovedLoans.getMovedLoans(EF).lookup(HeldLoanID))
174 MovedExpr = *ME;
175 // Skip if we already have a dominating causing fact.
176 if (CurWarning.CausingFactDominatesExpiry)
177 continue;
178 if (causingFactDominatesExpiry(LiveInfo.Kind))
179 CurWarning.CausingFactDominatesExpiry = true;
180 CurWarning.CausingFact = LiveInfo.CausingFact;
181 CurWarning.ExpiryLoc = EF->getExpiryLoc();
182 CurWarning.MovedExpr = MovedExpr;
183 CurWarning.InvalidatedByExpr = nullptr;
184 }
185 }
186 }
187
188 /// Checks for use-after-invalidation errors when a container is modified.
189 ///
190 /// This method identifies origins that are live at the point of invalidation
191 /// and checks if they hold loans that are invalidated by the operation
192 /// (e.g., iterators into a vector that is being pushed to).
193 void checkInvalidation(const InvalidateOriginFact *IOF) {
194 OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
195 /// Get loans directly pointing to the invalidated container
196 LoanSet DirectlyInvalidatedLoans =
197 LoanPropagation.getLoans(InvalidatedOrigin, IOF);
198 auto IsInvalidated = [&](const Loan *L) {
199 for (LoanID InvalidID : DirectlyInvalidatedLoans) {
200 const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
201 if (InvalidL->getAccessPath() == L->getAccessPath())
202 return true;
203 }
204 return false;
205 };
206 // For each live origin, check if it holds an invalidated loan and report.
207 LivenessMap Origins = LiveOrigins.getLiveOriginsAt(IOF);
208 for (auto &[OID, LiveInfo] : Origins) {
209 LoanSet HeldLoans = LoanPropagation.getLoans(OID, IOF);
210 for (LoanID LiveLoanID : HeldLoans)
211 if (IsInvalidated(FactMgr.getLoanMgr().getLoan(LiveLoanID))) {
212 bool CurDomination = causingFactDominatesExpiry(LiveInfo.Kind);
213 bool LastDomination =
214 FinalWarningsMap.lookup(LiveLoanID).CausingFactDominatesExpiry;
215 if (!LastDomination) {
216 FinalWarningsMap[LiveLoanID] = {
217 /*ExpiryLoc=*/{},
218 /*CausingFact=*/LiveInfo.CausingFact,
219 /*MovedExpr=*/nullptr,
220 /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
221 /*CausingFactDominatesExpiry=*/CurDomination};
222 }
223 }
224 }
225 }
226
227 void issuePendingWarnings() {
228 if (!SemaHelper)
229 return;
230 for (const auto &[LID, Warning] : FinalWarningsMap) {
231 const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
232 const Expr *IssueExpr = L->getIssuingExpr();
233 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
234 CausingFact = Warning.CausingFact;
235 const ParmVarDecl *InvalidatedPVD =
236 L->getAccessPath().getAsPlaceholderParam();
237 const Expr *MovedExpr = Warning.MovedExpr;
238 SourceLocation ExpiryLoc = Warning.ExpiryLoc;
239
240 if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
241 if (Warning.InvalidatedByExpr) {
242 if (IssueExpr)
243 // Use-after-invalidation of an object on stack.
244 SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
245 Warning.InvalidatedByExpr);
246 else if (InvalidatedPVD)
247 // Use-after-invalidation of a parameter.
248 SemaHelper->reportUseAfterInvalidation(
249 InvalidatedPVD, UF->getUseExpr(), Warning.InvalidatedByExpr);
250
251 } else
252 // Scope-based expiry (use-after-scope).
253 SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
254 ExpiryLoc);
255 } else if (const auto *OEF =
256 CausingFact.dyn_cast<const OriginEscapesFact *>()) {
257 if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
258 // Return stack address.
259 SemaHelper->reportUseAfterReturn(
260 IssueExpr, RetEscape->getReturnExpr(), MovedExpr, ExpiryLoc);
261 else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
262 // Dangling field.
263 SemaHelper->reportDanglingField(
264 IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
265 else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
266 // Global escape.
267 SemaHelper->reportDanglingGlobal(IssueExpr, GlobalEscape->getGlobal(),
268 MovedExpr, ExpiryLoc);
269 else
270 llvm_unreachable("Unhandled OriginEscapesFact type");
271 } else
272 llvm_unreachable("Unhandled CausingFact type");
273 }
274 }
275
276 /// Returns the declaration of a function that is visible across translation
277 /// units, if such a declaration exists and is different from the definition.
278 static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
279 SourceManager &SM) {
280 if (!FD.isExternallyVisible())
281 return nullptr;
282 const FileID DefinitionFile = SM.getFileID(FD.getLocation());
283 for (const FunctionDecl *Redecl : FD.redecls())
284 if (SM.getFileID(Redecl->getLocation()) != DefinitionFile)
285 return Redecl;
286
287 return nullptr;
288 }
289
290 static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
291 SourceManager &SM) {
292 if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext()))
293 return getCrossTUDecl(*FD, SM);
294 return nullptr;
295 }
296
297 static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper,
298 const ParmVarDecl *PVD,
299 SourceManager &SM,
300 EscapingTarget EscapeTarget) {
301 if (llvm::isa<const VarDecl *>(EscapeTarget))
302 return;
303
304 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
305 SemaHelper->suggestLifetimeboundToParmVar(
307 CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()),
308 EscapeTarget);
309 else
310 SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD,
311 EscapeTarget);
312 }
313
314 static void
315 suggestWithScopeForImplicitThis(LifetimeSafetySemaHelper *SemaHelper,
316 const CXXMethodDecl *MD, SourceManager &SM,
317 const Expr *EscapeExpr) {
318 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*MD, SM))
319 SemaHelper->suggestLifetimeboundToImplicitThis(
321 EscapeExpr);
322 else
323 SemaHelper->suggestLifetimeboundToImplicitThis(SuggestionScope::IntraTU,
324 MD, EscapeExpr);
325 }
326
327 void suggestAnnotations() {
328 if (!SemaHelper)
329 return;
330 SourceManager &SM = AST.getSourceManager();
331 for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
332 if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>())
333 suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeTarget);
334 else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
335 if (const auto *EscapeExpr = EscapeTarget.dyn_cast<const Expr *>())
336 suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
337 else
338 llvm_unreachable("Implicit this can only escape via Expr (return)");
339 }
340 }
341 }
342
343 void reportNoescapeViolations() {
344 for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
345 if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
346 SemaHelper->reportNoescapeViolation(PVD, E);
347 else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
348 SemaHelper->reportNoescapeViolation(PVD, FD);
349 else if (const auto *G = EscapeTarget.dyn_cast<const VarDecl *>())
350 SemaHelper->reportNoescapeViolation(PVD, G);
351 else
352 llvm_unreachable("Unhandled EscapingTarget type");
353 }
354 }
355
356 void inferAnnotations() {
357 for (auto [Target, EscapeTarget] : AnnotationWarningsMap) {
358 if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
360 SemaHelper->addLifetimeBoundToImplicitThis(cast<CXXMethodDecl>(MD));
361 } else if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) {
362 const auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
363 if (!FD)
364 continue;
365 // Propagates inferred attributes via the most recent declaration to
366 // ensure visibility for callers in post-order analysis.
368 ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
369 FD->getParamDecl(PVD->getFunctionScopeIndex()));
370 if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
371 InferredPVD->addAttr(
372 LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
373 }
374 }
375 }
376};
377} // namespace
378
380 const MovedLoansAnalysis &MovedLoans,
381 const LiveOriginsAnalysis &LO, FactManager &FactMgr,
383 LifetimeSafetySemaHelper *SemaHelper) {
384 llvm::TimeTraceScope TimeProfile("LifetimeChecker");
385 LifetimeChecker Checker(LP, MovedLoans, LO, FactMgr, ADC, SemaHelper);
386}
387
388} // 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:379
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD)
const FunctionDecl * getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD)
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