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/ADT/DenseSet.h"
28#include "llvm/Support/Casting.h"
29#include "llvm/Support/ErrorHandling.h"
30#include "llvm/Support/TimeProfiler.h"
31
33
35 switch (K) {
39 return Confidence::Maybe;
41 return Confidence::None;
42 }
43 llvm_unreachable("unknown liveness kind");
44}
45
46namespace {
47
48/// Struct to store the complete context for a potential lifetime violation.
49struct PendingWarning {
50 SourceLocation ExpiryLoc; // Where the loan expired.
51 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
52 const Expr *MovedExpr;
53 const Expr *InvalidatedByExpr;
54 Confidence ConfidenceLevel;
55};
56
57using AnnotationTarget =
58 llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
59using EscapingTarget =
60 llvm::PointerUnion<const Expr *, const FieldDecl *, const VarDecl *>;
61
62class LifetimeChecker {
63private:
64 llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
65 llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap;
66 llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
67 const LoanPropagationAnalysis &LoanPropagation;
68 const MovedLoansAnalysis &MovedLoans;
69 const LiveOriginsAnalysis &LiveOrigins;
70 FactManager &FactMgr;
71 LifetimeSafetySemaHelper *SemaHelper;
72 ASTContext &AST;
73
74public:
75 LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
76 const MovedLoansAnalysis &MovedLoans,
77 const LiveOriginsAnalysis &LiveOrigins, FactManager &FM,
79 LifetimeSafetySemaHelper *SemaHelper)
80 : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans),
81 LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper),
82 AST(ADC.getASTContext()) {
83 for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
84 for (const Fact *F : FactMgr.getFacts(B))
85 if (const auto *EF = F->getAs<ExpireFact>())
86 checkExpiry(EF);
87 else if (const auto *IOF = F->getAs<InvalidateOriginFact>())
88 checkInvalidation(IOF);
89 else if (const auto *OEF = F->getAs<OriginEscapesFact>())
90 checkAnnotations(OEF);
91 issuePendingWarnings();
92 suggestAnnotations();
93 reportNoescapeViolations();
94 // Annotation inference is currently guarded by a frontend flag. In the
95 // future, this might be replaced by a design that differentiates between
96 // explicit and inferred findings with separate warning groups.
97 if (AST.getLangOpts().EnableLifetimeSafetyInference)
98 inferAnnotations();
99 }
100
101 /// Checks if an escaping origin holds a placeholder loan, indicating a
102 /// missing [[clang::lifetimebound]] annotation or a violation of
103 /// [[clang::noescape]].
104 void checkAnnotations(const OriginEscapesFact *OEF) {
105 OriginID EscapedOID = OEF->getEscapedOriginID();
106 LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
107 auto CheckParam = [&](const ParmVarDecl *PVD) {
108 // NoEscape param should not escape.
109 if (PVD->hasAttr<NoEscapeAttr>()) {
110 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
111 NoescapeWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
112 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(OEF))
113 NoescapeWarningsMap.try_emplace(PVD, FieldEsc->getFieldDecl());
114 if (auto *GlobalEsc = dyn_cast<GlobalEscapeFact>(OEF))
115 NoescapeWarningsMap.try_emplace(PVD, GlobalEsc->getGlobal());
116 return;
117 }
118 // Suggest lifetimebound for parameter escaping through return.
119 if (!PVD->hasAttr<LifetimeBoundAttr>())
120 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
121 AnnotationWarningsMap.try_emplace(PVD, ReturnEsc->getReturnExpr());
122 // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
123 // field!
124 };
125 auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
127 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(OEF))
128 AnnotationWarningsMap.try_emplace(MD, ReturnEsc->getReturnExpr());
129 };
130 for (LoanID LID : EscapedLoans) {
131 const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
132 const auto *PL = dyn_cast<PlaceholderLoan>(L);
133 if (!PL)
134 continue;
135 if (const auto *PVD = PL->getParmVarDecl())
136 CheckParam(PVD);
137 else if (const auto *MD = PL->getMethodDecl())
138 CheckImplicitThis(MD);
139 }
140 }
141
142 /// Checks for use-after-free & use-after-return errors when a loan expires.
143 ///
144 /// This method examines all live origins at the expiry point and determines
145 /// if any of them hold the expiring loan. If so, it creates a pending
146 /// warning with the appropriate confidence level based on the liveness
147 /// information. The confidence reflects whether the origin is definitely
148 /// or maybe live at this point.
149 ///
150 /// Note: This implementation considers only the confidence of origin
151 /// liveness. Future enhancements could also consider the confidence of loan
152 /// propagation (e.g., a loan may only be held on some execution paths).
153 void checkExpiry(const ExpireFact *EF) {
154 LoanID ExpiredLoan = EF->getLoanID();
155 const Expr *MovedExpr = nullptr;
156 if (auto *ME = MovedLoans.getMovedLoans(EF).lookup(ExpiredLoan))
157 MovedExpr = *ME;
158
159 LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
160 Confidence CurConfidence = Confidence::None;
161 // The UseFact or OriginEscapesFact most indicative of a lifetime error,
162 // prioritized by earlier source location.
163 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
164 BestCausingFact = nullptr;
165
166 for (auto &[OID, LiveInfo] : Origins) {
167 LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
168 if (!HeldLoans.contains(ExpiredLoan))
169 continue;
170 // Loan is defaulted.
171 Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
172 if (CurConfidence < NewConfidence) {
173 CurConfidence = NewConfidence;
174 BestCausingFact = LiveInfo.CausingFact;
175 }
176 }
177 if (!BestCausingFact)
178 return;
179 // We have a use-after-free.
180 Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
181 if (LastConf >= CurConfidence)
182 return;
183 FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
184 /*BestCausingFact=*/BestCausingFact,
185 /*MovedExpr=*/MovedExpr,
186 /*InvalidatedByExpr=*/nullptr,
187 /*ConfidenceLevel=*/CurConfidence};
188 }
189
190 /// Checks for use-after-invalidation errors when a container is modified.
191 ///
192 /// This method identifies origins that are live at the point of invalidation
193 /// and checks if they hold loans that are invalidated by the operation
194 /// (e.g., iterators into a vector that is being pushed to).
195 void checkInvalidation(const InvalidateOriginFact *IOF) {
196 OriginID InvalidatedOrigin = IOF->getInvalidatedOrigin();
197 /// Get loans directly pointing to the invalidated container
198 LoanSet DirectlyInvalidatedLoans =
199 LoanPropagation.getLoans(InvalidatedOrigin, IOF);
200 auto IsInvalidated = [&](const Loan *L) {
201 auto *PathL = dyn_cast<PathLoan>(L);
202 auto *PlaceholderL = dyn_cast<PlaceholderLoan>(L);
203 for (LoanID InvalidID : DirectlyInvalidatedLoans) {
204 const Loan *L = FactMgr.getLoanMgr().getLoan(InvalidID);
205 auto *InvalidPathL = dyn_cast<PathLoan>(L);
206 auto *InvalidPlaceholderL = dyn_cast<PlaceholderLoan>(L);
207 if (PathL && InvalidPathL &&
208 PathL->getAccessPath() == InvalidPathL->getAccessPath())
209 return true;
210 if (PlaceholderL && InvalidPlaceholderL &&
211 PlaceholderL->getParmVarDecl() ==
212 InvalidPlaceholderL->getParmVarDecl())
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 Confidence CurConfidence = livenessKindToConfidence(LiveInfo.Kind);
224 Confidence LastConf =
225 FinalWarningsMap.lookup(LiveLoanID).ConfidenceLevel;
226 if (LastConf < CurConfidence) {
227 FinalWarningsMap[LiveLoanID] = {
228 /*ExpiryLoc=*/{},
229 /*CausingFact=*/LiveInfo.CausingFact,
230 /*MovedExpr=*/nullptr,
231 /*InvalidatedByExpr=*/IOF->getInvalidationExpr(),
232 /*ConfidenceLevel=*/CurConfidence};
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
244 const Expr *IssueExpr = nullptr;
245 if (const auto *BL = dyn_cast<PathLoan>(L))
246 IssueExpr = BL->getIssueExpr();
247 const ParmVarDecl *InvalidatedPVD = nullptr;
248 if (const auto *PL = dyn_cast<PlaceholderLoan>(L))
249 InvalidatedPVD = PL->getParmVarDecl();
250 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
251 CausingFact = Warning.CausingFact;
252 Confidence Confidence = Warning.ConfidenceLevel;
253 const Expr *MovedExpr = Warning.MovedExpr;
254 SourceLocation ExpiryLoc = Warning.ExpiryLoc;
255
256 if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
257 if (Warning.InvalidatedByExpr) {
258 if (IssueExpr)
259 SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
260 Warning.InvalidatedByExpr);
261 if (InvalidatedPVD)
262 SemaHelper->reportUseAfterInvalidation(
263 InvalidatedPVD, UF->getUseExpr(), Warning.InvalidatedByExpr);
264
265 } else
266 SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
267 ExpiryLoc, Confidence);
268 } else if (const auto *OEF =
269 CausingFact.dyn_cast<const OriginEscapesFact *>()) {
270 if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
271 SemaHelper->reportUseAfterReturn(IssueExpr,
272 RetEscape->getReturnExpr(),
273 MovedExpr, ExpiryLoc, Confidence);
274 else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
275 SemaHelper->reportDanglingField(
276 IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
277 else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
278 SemaHelper->reportDanglingGlobal(IssueExpr, GlobalEscape->getGlobal(),
279 MovedExpr, ExpiryLoc);
280 else
281 llvm_unreachable("Unhandled OriginEscapesFact type");
282 } else
283 llvm_unreachable("Unhandled CausingFact type");
284 }
285 }
286
287 /// Returns the declaration of a function that is visible across translation
288 /// units, if such a declaration exists and is different from the definition.
289 static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
290 SourceManager &SM) {
291 if (!FD.isExternallyVisible())
292 return nullptr;
293 const FileID DefinitionFile = SM.getFileID(FD.getLocation());
294 for (const FunctionDecl *Redecl : FD.redecls())
295 if (SM.getFileID(Redecl->getLocation()) != DefinitionFile)
296 return Redecl;
297
298 return nullptr;
299 }
300
301 static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
302 SourceManager &SM) {
303 if (const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext()))
304 return getCrossTUDecl(*FD, SM);
305 return nullptr;
306 }
307
308 static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper,
309 const ParmVarDecl *PVD,
310 SourceManager &SM,
311 const Expr *EscapeExpr) {
312 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
313 SemaHelper->suggestLifetimeboundToParmVar(
315 CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()), EscapeExpr);
316 else
317 SemaHelper->suggestLifetimeboundToParmVar(SuggestionScope::IntraTU, PVD,
318 EscapeExpr);
319 }
320
321 static void
322 suggestWithScopeForImplicitThis(LifetimeSafetySemaHelper *SemaHelper,
323 const CXXMethodDecl *MD, SourceManager &SM,
324 const Expr *EscapeExpr) {
325 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*MD, SM))
326 SemaHelper->suggestLifetimeboundToImplicitThis(
328 EscapeExpr);
329 else
330 SemaHelper->suggestLifetimeboundToImplicitThis(SuggestionScope::IntraTU,
331 MD, EscapeExpr);
332 }
333
334 void suggestAnnotations() {
335 if (!SemaHelper)
336 return;
337 SourceManager &SM = AST.getSourceManager();
338 for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
339 if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>())
340 suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeExpr);
341 else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>())
342 suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
343 }
344 }
345
346 void reportNoescapeViolations() {
347 for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
348 if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
349 SemaHelper->reportNoescapeViolation(PVD, E);
350 else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
351 SemaHelper->reportNoescapeViolation(PVD, FD);
352 else if (const auto *G = EscapeTarget.dyn_cast<const VarDecl *>())
353 SemaHelper->reportNoescapeViolation(PVD, G);
354 else
355 llvm_unreachable("Unhandled EscapingTarget type");
356 }
357 }
358
359 void inferAnnotations() {
360 for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
361 if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
363 SemaHelper->addLifetimeBoundToImplicitThis(cast<CXXMethodDecl>(MD));
364 } else if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) {
365 const auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
366 if (!FD)
367 continue;
368 // Propagates inferred attributes via the most recent declaration to
369 // ensure visibility for callers in post-order analysis.
371 ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
372 FD->getParamDecl(PVD->getFunctionScopeIndex()));
373 if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
374 InferredPVD->addAttr(
375 LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
376 }
377 }
378 }
379};
380} // namespace
381
383 const MovedLoansAnalysis &MovedLoans,
384 const LiveOriginsAnalysis &LO, FactManager &FactMgr,
386 LifetimeSafetySemaHelper *SemaHelper) {
387 llvm::TimeTraceScope TimeProfile("LifetimeChecker");
388 LifetimeChecker Checker(LP, MovedLoans, LO, FactMgr, ADC, SemaHelper);
389}
390
391} // 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:226
const LangOptions & getLangOpts() const
Definition ASTContext.h:951
AnalysisDeclContext contains the context data for the function, method or block under analysis.
Represents a single basic block in a source-level CFG.
Definition CFG.h:632
This represents one expression.
Definition Expr.h:112
Encodes a location in the source.
Abstract interface for operations requiring Sema access.
llvm::ImmutableSet< LoanID > LoanSet
utils::ID< struct LoanTag > LoanID
Definition Loans.h:25
utils::ID< struct OriginTag > OriginID
Definition Origins.h:27
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:382
static Confidence livenessKindToConfidence(LivenessKind K)
Definition Checker.cpp:34
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD)
const FunctionDecl * getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD)
Confidence
Enum to track the confidence level of a potential error.
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