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 Confidence livenessKindToConfidence(LivenessKind K) {
33 switch (K) {
34 case LivenessKind::Must:
35 return Confidence::Definite;
36 case LivenessKind::Maybe:
37 return Confidence::Maybe;
38 case LivenessKind::Dead:
39 return Confidence::None;
40 }
41 llvm_unreachable("unknown liveness kind");
42}
43
44namespace {
45
46/// Struct to store the complete context for a potential lifetime violation.
47struct PendingWarning {
48 SourceLocation ExpiryLoc; // Where the loan expired.
49 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
50 Confidence ConfidenceLevel;
51};
52
53using AnnotationTarget =
54 llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *>;
55using EscapingTarget = llvm::PointerUnion<const Expr *, const FieldDecl *>;
56
57class LifetimeChecker {
58private:
59 llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
60 llvm::DenseMap<AnnotationTarget, const Expr *> AnnotationWarningsMap;
61 llvm::DenseMap<const ParmVarDecl *, EscapingTarget> NoescapeWarningsMap;
62 const LoanPropagationAnalysis &LoanPropagation;
63 const LiveOriginsAnalysis &LiveOrigins;
64 const FactManager &FactMgr;
65 LifetimeSafetySemaHelper *SemaHelper;
66 ASTContext &AST;
67
68public:
69 LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
70 const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM,
71 AnalysisDeclContext &ADC,
72 LifetimeSafetySemaHelper *SemaHelper)
73 : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
74 SemaHelper(SemaHelper), AST(ADC.getASTContext()) {
75 for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
76 for (const Fact *F : FactMgr.getFacts(B))
77 if (const auto *EF = F->getAs<ExpireFact>())
78 checkExpiry(EF);
79 else if (const auto *OEF = F->getAs<OriginEscapesFact>())
80 checkAnnotations(OEF);
81 issuePendingWarnings();
82 suggestAnnotations();
83 reportNoescapeViolations();
84 // Annotation inference is currently guarded by a frontend flag. In the
85 // future, this might be replaced by a design that differentiates between
86 // explicit and inferred findings with separate warning groups.
87 if (AST.getLangOpts().EnableLifetimeSafetyInference)
88 inferAnnotations();
89 }
90
91 /// Checks if an escaping origin holds a placeholder loan, indicating a
92 /// missing [[clang::lifetimebound]] annotation or a violation of
93 /// [[clang::noescape]].
94 void checkAnnotations(const OriginEscapesFact *OEF) {
95 OriginID EscapedOID = OEF->getEscapedOriginID();
96 LoanSet EscapedLoans = LoanPropagation.getLoans(OID: EscapedOID, P: OEF);
97 auto CheckParam = [&](const ParmVarDecl *PVD) {
98 // NoEscape param should not escape.
99 if (PVD->hasAttr<NoEscapeAttr>()) {
100 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(Val: OEF))
101 NoescapeWarningsMap.try_emplace(Key: PVD, Args: ReturnEsc->getReturnExpr());
102 if (auto *FieldEsc = dyn_cast<FieldEscapeFact>(Val: OEF))
103 NoescapeWarningsMap.try_emplace(Key: PVD, Args: FieldEsc->getFieldDecl());
104 return;
105 }
106 // Suggest lifetimebound for parameter escaping through return.
107 if (!PVD->hasAttr<LifetimeBoundAttr>())
108 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(Val: OEF))
109 AnnotationWarningsMap.try_emplace(Key: PVD, Args: ReturnEsc->getReturnExpr());
110 // TODO: Suggest lifetime_capture_by(this) for parameter escaping to a
111 // field!
112 };
113 auto CheckImplicitThis = [&](const CXXMethodDecl *MD) {
114 if (!implicitObjectParamIsLifetimeBound(FD: MD))
115 if (auto *ReturnEsc = dyn_cast<ReturnEscapeFact>(Val: OEF))
116 AnnotationWarningsMap.try_emplace(Key: MD, Args: ReturnEsc->getReturnExpr());
117 };
118 for (LoanID LID : EscapedLoans) {
119 const Loan *L = FactMgr.getLoanMgr().getLoan(ID: LID);
120 const auto *PL = dyn_cast<PlaceholderLoan>(Val: L);
121 if (!PL)
122 continue;
123 if (const auto *PVD = PL->getParmVarDecl())
124 CheckParam(PVD);
125 else if (const auto *MD = PL->getMethodDecl())
126 CheckImplicitThis(MD);
127 }
128 }
129
130 /// Checks for use-after-free & use-after-return errors when a loan expires.
131 ///
132 /// This method examines all live origins at the expiry point and determines
133 /// if any of them hold the expiring loan. If so, it creates a pending
134 /// warning with the appropriate confidence level based on the liveness
135 /// information. The confidence reflects whether the origin is definitely
136 /// or maybe live at this point.
137 ///
138 /// Note: This implementation considers only the confidence of origin
139 /// liveness. Future enhancements could also consider the confidence of loan
140 /// propagation (e.g., a loan may only be held on some execution paths).
141 void checkExpiry(const ExpireFact *EF) {
142 LoanID ExpiredLoan = EF->getLoanID();
143 LivenessMap Origins = LiveOrigins.getLiveOriginsAt(P: EF);
144 Confidence CurConfidence = Confidence::None;
145 // The UseFact or OriginEscapesFact most indicative of a lifetime error,
146 // prioritized by earlier source location.
147 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
148 BestCausingFact = nullptr;
149
150 for (auto &[OID, LiveInfo] : Origins) {
151 LoanSet HeldLoans = LoanPropagation.getLoans(OID, P: EF);
152 if (!HeldLoans.contains(V: ExpiredLoan))
153 continue;
154 // Loan is defaulted.
155 Confidence NewConfidence = livenessKindToConfidence(K: LiveInfo.Kind);
156 if (CurConfidence < NewConfidence) {
157 CurConfidence = NewConfidence;
158 BestCausingFact = LiveInfo.CausingFact;
159 }
160 }
161 if (!BestCausingFact)
162 return;
163 // We have a use-after-free.
164 Confidence LastConf = FinalWarningsMap.lookup(Val: ExpiredLoan).ConfidenceLevel;
165 if (LastConf >= CurConfidence)
166 return;
167 FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
168 /*BestCausingFact=*/.CausingFact: BestCausingFact,
169 /*ConfidenceLevel=*/CurConfidence};
170 }
171
172 void issuePendingWarnings() {
173 if (!SemaHelper)
174 return;
175 for (const auto &[LID, Warning] : FinalWarningsMap) {
176 const Loan *L = FactMgr.getLoanMgr().getLoan(ID: LID);
177 const auto *BL = cast<PathLoan>(Val: L);
178 const Expr *IssueExpr = BL->getIssueExpr();
179 llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
180 CausingFact = Warning.CausingFact;
181 Confidence Confidence = Warning.ConfidenceLevel;
182 SourceLocation ExpiryLoc = Warning.ExpiryLoc;
183
184 if (const auto *UF = CausingFact.dyn_cast<const UseFact *>())
185 SemaHelper->reportUseAfterFree(IssueExpr, UseExpr: UF->getUseExpr(), FreeLoc: ExpiryLoc,
186 Confidence);
187 else if (const auto *OEF =
188 CausingFact.dyn_cast<const OriginEscapesFact *>()) {
189 if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(Val: OEF))
190 SemaHelper->reportUseAfterReturn(
191 IssueExpr, ReturnExpr: RetEscape->getReturnExpr(), ExpiryLoc, Confidence);
192 else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(Val: OEF))
193 SemaHelper->reportDanglingField(
194 IssueExpr, Field: FieldEscape->getFieldDecl(), ExpiryLoc);
195 else
196 llvm_unreachable("Unhandled OriginEscapesFact type");
197 } else
198 llvm_unreachable("Unhandled CausingFact type");
199 }
200 }
201
202 /// Returns the declaration of a function that is visible across translation
203 /// units, if such a declaration exists and is different from the definition.
204 static const FunctionDecl *getCrossTUDecl(const FunctionDecl &FD,
205 SourceManager &SM) {
206 if (!FD.isExternallyVisible())
207 return nullptr;
208 const FileID DefinitionFile = SM.getFileID(SpellingLoc: FD.getLocation());
209 for (const FunctionDecl *Redecl : FD.redecls())
210 if (SM.getFileID(SpellingLoc: Redecl->getLocation()) != DefinitionFile)
211 return Redecl;
212
213 return nullptr;
214 }
215
216 static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
217 SourceManager &SM) {
218 if (const auto *FD = dyn_cast<FunctionDecl>(Val: PVD.getDeclContext()))
219 return getCrossTUDecl(FD: *FD, SM);
220 return nullptr;
221 }
222
223 static void suggestWithScopeForParmVar(LifetimeSafetySemaHelper *SemaHelper,
224 const ParmVarDecl *PVD,
225 SourceManager &SM,
226 const Expr *EscapeExpr) {
227 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(PVD: *PVD, SM))
228 SemaHelper->suggestLifetimeboundToParmVar(
229 Scope: SuggestionScope::CrossTU,
230 ParmToAnnotate: CrossTUDecl->getParamDecl(i: PVD->getFunctionScopeIndex()), EscapeExpr);
231 else
232 SemaHelper->suggestLifetimeboundToParmVar(Scope: SuggestionScope::IntraTU, ParmToAnnotate: PVD,
233 EscapeExpr);
234 }
235
236 static void
237 suggestWithScopeForImplicitThis(LifetimeSafetySemaHelper *SemaHelper,
238 const CXXMethodDecl *MD, SourceManager &SM,
239 const Expr *EscapeExpr) {
240 if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(FD: *MD, SM))
241 SemaHelper->suggestLifetimeboundToImplicitThis(
242 Scope: SuggestionScope::CrossTU, MD: cast<CXXMethodDecl>(Val: CrossTUDecl),
243 EscapeExpr);
244 else
245 SemaHelper->suggestLifetimeboundToImplicitThis(Scope: SuggestionScope::IntraTU,
246 MD, EscapeExpr);
247 }
248
249 void suggestAnnotations() {
250 if (!SemaHelper)
251 return;
252 SourceManager &SM = AST.getSourceManager();
253 for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
254 if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>())
255 suggestWithScopeForParmVar(SemaHelper, PVD, SM, EscapeExpr);
256 else if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>())
257 suggestWithScopeForImplicitThis(SemaHelper, MD, SM, EscapeExpr);
258 }
259 }
260
261 void reportNoescapeViolations() {
262 for (auto [PVD, EscapeTarget] : NoescapeWarningsMap) {
263 if (const auto *E = EscapeTarget.dyn_cast<const Expr *>())
264 SemaHelper->reportNoescapeViolation(ParmWithNoescape: PVD, EscapeExpr: E);
265 else if (const auto *FD = EscapeTarget.dyn_cast<const FieldDecl *>())
266 SemaHelper->reportNoescapeViolation(ParmWithNoescape: PVD, EscapeField: FD);
267 else
268 llvm_unreachable("Unhandled EscapingTarget type");
269 }
270 }
271
272 void inferAnnotations() {
273 for (auto [Target, EscapeExpr] : AnnotationWarningsMap) {
274 if (const auto *MD = Target.dyn_cast<const CXXMethodDecl *>()) {
275 if (!implicitObjectParamIsLifetimeBound(FD: MD))
276 SemaHelper->addLifetimeBoundToImplicitThis(MD: cast<CXXMethodDecl>(Val: MD));
277 } else if (const auto *PVD = Target.dyn_cast<const ParmVarDecl *>()) {
278 const auto *FD = dyn_cast<FunctionDecl>(Val: PVD->getDeclContext());
279 if (!FD)
280 continue;
281 // Propagates inferred attributes via the most recent declaration to
282 // ensure visibility for callers in post-order analysis.
283 FD = getDeclWithMergedLifetimeBoundAttrs(FD);
284 ParmVarDecl *InferredPVD = const_cast<ParmVarDecl *>(
285 FD->getParamDecl(i: PVD->getFunctionScopeIndex()));
286 if (!InferredPVD->hasAttr<LifetimeBoundAttr>())
287 InferredPVD->addAttr(
288 A: LifetimeBoundAttr::CreateImplicit(Ctx&: AST, Range: PVD->getLocation()));
289 }
290 }
291 }
292};
293} // namespace
294
295void runLifetimeChecker(const LoanPropagationAnalysis &LP,
296 const LiveOriginsAnalysis &LO,
297 const FactManager &FactMgr, AnalysisDeclContext &ADC,
298 LifetimeSafetySemaHelper *SemaHelper) {
299 llvm::TimeTraceScope TimeProfile("LifetimeChecker");
300 LifetimeChecker Checker(LP, LO, FactMgr, ADC, SemaHelper);
301}
302
303} // namespace clang::lifetimes::internal
304