1//===--- SemaLifetimeSafety.h - Sema support for lifetime safety =---------==//
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 defines the Sema-specific implementation for lifetime safety
10// analysis. It provides diagnostic reporting and helper functions that bridge
11// the lifetime safety analysis framework with Sema's diagnostic engine.
12//
13//===----------------------------------------------------------------------===//
14
15#ifndef LLVM_CLANG_LIB_SEMA_SEMALIFETIMESAFETY_H
16#define LLVM_CLANG_LIB_SEMA_SEMALIFETIMESAFETY_H
17
18#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
19#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
20#include "clang/Basic/DiagnosticSema.h"
21#include "clang/Lex/Lexer.h"
22#include "clang/Lex/Preprocessor.h"
23#include "clang/Sema/Sema.h"
24#include <string>
25
26namespace clang::lifetimes {
27
28inline bool IsLifetimeSafetyEnabled(Sema &S, const Decl *D) {
29 // TODO: Enable ObjectiveC later when we know it's stable enough.
30 if (S.getLangOpts().ObjC)
31 return false;
32
33 // TODO: Default this flag to on in the future.
34 if (!S.getLangOpts().CPlusPlus && !S.getLangOpts().EnableLifetimeSafetyInC)
35 return false;
36
37 // Translation-unit mode: whole-program analysis runs once on TU.
38 // Individual function analysis is disabled when TU mode is enabled.
39 if (S.getLangOpts().EnableLifetimeSafetyTUAnalysis)
40 return isa<TranslationUnitDecl>(Val: D);
41
42 // Per-function mode: analysis runs on each function/method individually.
43 // Skip TU-level calls when per-function mode is enabled.
44 if (isa<TranslationUnitDecl>(Val: D))
45 return false;
46
47 // Enable per-function mode via debug flag or specific diagnostics.
48 if (S.getLangOpts().DebugRunLifetimeSafety)
49 return true;
50 DiagnosticsEngine &Diags = S.getDiagnostics();
51 constexpr unsigned DiagIDs[] = {
52 diag::warn_lifetime_safety_use_after_scope,
53 diag::warn_lifetime_safety_use_after_scope_moved,
54 diag::warn_lifetime_safety_use_after_free,
55 diag::warn_lifetime_safety_return_stack_addr,
56 diag::warn_lifetime_safety_return_stack_addr_moved,
57 diag::warn_lifetime_safety_invalidation,
58 diag::warn_lifetime_safety_dangling_field,
59 diag::warn_lifetime_safety_dangling_field_moved,
60 diag::warn_lifetime_safety_dangling_global,
61 diag::warn_lifetime_safety_dangling_global_moved,
62 diag::warn_lifetime_safety_noescape_escapes,
63 diag::warn_lifetime_safety_lifetimebound_violation,
64 diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound,
65 diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound,
66 diag::warn_lifetime_safety_invalidated_field,
67 diag::warn_lifetime_safety_invalidated_global,
68 diag::warn_lifetime_safety_cross_tu_param_suggestion,
69 diag::warn_lifetime_safety_intra_tu_param_suggestion,
70 diag::warn_lifetime_safety_cross_tu_ctor_param_suggestion,
71 diag::warn_lifetime_safety_intra_tu_ctor_param_suggestion,
72 diag::warn_lifetime_safety_cross_tu_this_suggestion,
73 diag::warn_lifetime_safety_intra_tu_this_suggestion,
74 diag::warn_lifetime_safety_inapplicable_lifetimebound};
75 for (unsigned DiagID : DiagIDs)
76 if (!Diags.isIgnored(DiagID, Loc: D->getBeginLoc()))
77 return true;
78 return false;
79}
80
81inline bool ShouldSuggestLifetimeAnnotations(Sema &S, const Decl *D) {
82 DiagnosticsEngine &Diags = S.getDiagnostics();
83 constexpr unsigned DiagIDs[] = {
84 diag::warn_lifetime_safety_intra_tu_param_suggestion,
85 diag::warn_lifetime_safety_cross_tu_param_suggestion,
86 diag::warn_lifetime_safety_intra_tu_ctor_param_suggestion,
87 diag::warn_lifetime_safety_cross_tu_ctor_param_suggestion,
88 diag::warn_lifetime_safety_intra_tu_this_suggestion,
89 diag::warn_lifetime_safety_cross_tu_this_suggestion};
90 for (unsigned DiagID : DiagIDs)
91 if (!Diags.isIgnored(DiagID, Loc: D->getBeginLoc()))
92 return true;
93 return false;
94}
95
96inline LifetimeSafetyOpts GetLifetimeSafetyOpts(Sema &S, const Decl *D) {
97 LifetimeSafetyOpts LSOpts;
98 LSOpts.MaxCFGBlocks = S.getLangOpts().LifetimeSafetyMaxCFGBlocks;
99 LSOpts.SuggestAnnotations = ShouldSuggestLifetimeAnnotations(S, D);
100 return LSOpts;
101}
102
103class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
104
105public:
106 LifetimeSafetySemaHelperImpl(Sema &S) : S(S) {}
107
108 void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
109 const Expr *MovedExpr, SourceLocation FreeLoc,
110 llvm::ArrayRef<const Expr *> ExprChain) override {
111 unsigned DiagID = MovedExpr
112 ? diag::warn_lifetime_safety_use_after_scope_moved
113 : diag::warn_lifetime_safety_use_after_scope;
114 std::string DestroyedSubject = getDiagSubjectDescription(E: IssueExpr);
115
116 S.Diag(Loc: IssueExpr->getExprLoc(), DiagID)
117 << DestroyedSubject << IssueExpr->getSourceRange();
118 if (MovedExpr)
119 S.Diag(Loc: MovedExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_moved_here)
120 << MovedExpr->getSourceRange();
121 S.Diag(Loc: FreeLoc, DiagID: diag::note_lifetime_safety_destroyed_here)
122 << DestroyedSubject;
123
124 reportAliasingChain(OriginExprChain: ExprChain);
125
126 S.Diag(Loc: UseExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_used_here)
127 << UseExpr->getSourceRange();
128 }
129
130 void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr,
131 const Expr *MovedExpr) override {
132 unsigned DiagID = MovedExpr
133 ? diag::warn_lifetime_safety_return_stack_addr_moved
134 : diag::warn_lifetime_safety_return_stack_addr;
135
136 S.Diag(Loc: IssueExpr->getExprLoc(), DiagID)
137 << getDiagSubjectDescription(E: IssueExpr) << IssueExpr->getSourceRange();
138
139 if (MovedExpr)
140 S.Diag(Loc: MovedExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_moved_here)
141 << MovedExpr->getSourceRange();
142 S.Diag(Loc: ReturnExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_returned_here)
143 << ReturnExpr->getSourceRange();
144 }
145
146 void reportDanglingField(const Expr *IssueExpr,
147 const FieldDecl *DanglingField,
148 const Expr *MovedExpr, bool IsCapturedByLambda,
149 SourceLocation ExpiryLoc) override {
150 unsigned DiagID =
151 IsCapturedByLambda
152 ? diag::warn_lifetime_safety_dangling_field_lambda_capture
153 : (MovedExpr ? diag::warn_lifetime_safety_dangling_field_moved
154 : diag::warn_lifetime_safety_dangling_field);
155
156 S.Diag(Loc: IssueExpr->getExprLoc(), DiagID)
157 << getDiagSubjectDescription(E: IssueExpr)
158 << getDiagSubjectDescription(VD: DanglingField)
159 << IssueExpr->getSourceRange();
160 if (MovedExpr)
161 S.Diag(Loc: MovedExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_moved_here)
162 << MovedExpr->getSourceRange();
163 S.Diag(Loc: DanglingField->getLocation(),
164 DiagID: diag::note_lifetime_safety_dangling_field_here)
165 << DanglingField->getEndLoc();
166 }
167
168 void reportDanglingGlobal(const Expr *IssueExpr,
169 const VarDecl *DanglingGlobal,
170 const Expr *MovedExpr,
171 SourceLocation ExpiryLoc) override {
172 unsigned DiagID = MovedExpr
173 ? diag::warn_lifetime_safety_dangling_global_moved
174 : diag::warn_lifetime_safety_dangling_global;
175
176 S.Diag(Loc: IssueExpr->getExprLoc(), DiagID)
177 << getDiagSubjectDescription(E: IssueExpr)
178 << getDiagSubjectDescription(VD: DanglingGlobal)
179 << IssueExpr->getSourceRange();
180 if (MovedExpr)
181 S.Diag(Loc: MovedExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_moved_here)
182 << MovedExpr->getSourceRange();
183 if (DanglingGlobal->isStaticLocal() || DanglingGlobal->isStaticDataMember())
184 S.Diag(Loc: DanglingGlobal->getLocation(),
185 DiagID: diag::note_lifetime_safety_dangling_static_here)
186 << DanglingGlobal->getEndLoc();
187 else
188 S.Diag(Loc: DanglingGlobal->getLocation(),
189 DiagID: diag::note_lifetime_safety_dangling_global_here)
190 << DanglingGlobal->getEndLoc();
191 }
192
193 void
194 reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr,
195 const Expr *InvalidationExpr,
196 llvm::ArrayRef<const Expr *> ExprChain) override {
197 auto WarnDiag = isa<CXXDeleteExpr>(Val: InvalidationExpr)
198 ? diag::warn_lifetime_safety_use_after_free
199 : diag::warn_lifetime_safety_invalidation;
200 std::string InvalidatedSubject = getDiagSubjectDescription(E: IssueExpr);
201 S.Diag(Loc: IssueExpr->getExprLoc(), DiagID: WarnDiag)
202 << InvalidatedSubject << IssueExpr->getSourceRange();
203 reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
204 reportAliasingChain(OriginExprChain: ExprChain);
205 S.Diag(Loc: UseExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_used_here)
206 << UseExpr->getSourceRange();
207 }
208 void
209 reportUseAfterInvalidation(const ParmVarDecl *PVD, const Expr *UseExpr,
210 const Expr *InvalidationExpr,
211 llvm::ArrayRef<const Expr *> ExprChain) override {
212
213 auto WarnDiag = isa<CXXDeleteExpr>(Val: InvalidationExpr)
214 ? diag::warn_lifetime_safety_use_after_free
215 : diag::warn_lifetime_safety_invalidation;
216 std::string InvalidatedSubject = getDiagSubjectDescription(VD: PVD);
217
218 S.Diag(Loc: PVD->getSourceRange().getBegin(), DiagID: WarnDiag)
219 << InvalidatedSubject << PVD->getSourceRange();
220 reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
221 reportAliasingChain(OriginExprChain: ExprChain);
222 S.Diag(Loc: UseExpr->getExprLoc(), DiagID: diag::note_lifetime_safety_used_here)
223 << UseExpr->getSourceRange();
224 }
225
226 void reportInvalidatedField(const Expr *IssueExpr,
227 const FieldDecl *DanglingField,
228 const Expr *InvalidationExpr) override {
229 std::string InvalidatedSubject = getDiagSubjectDescription(E: IssueExpr);
230 S.Diag(Loc: IssueExpr->getExprLoc(),
231 DiagID: diag::warn_lifetime_safety_invalidated_field)
232 << InvalidatedSubject << getDiagSubjectDescription(VD: DanglingField)
233 << IssueExpr->getSourceRange();
234 reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
235 S.Diag(Loc: DanglingField->getLocation(),
236 DiagID: diag::note_lifetime_safety_dangling_field_here)
237 << DanglingField->getEndLoc();
238 }
239
240 void reportInvalidatedField(const ParmVarDecl *PVD,
241 const FieldDecl *DanglingField,
242 const Expr *InvalidationExpr) override {
243 std::string InvalidatedSubject = getDiagSubjectDescription(VD: PVD);
244 S.Diag(Loc: PVD->getSourceRange().getBegin(),
245 DiagID: diag::warn_lifetime_safety_invalidated_field)
246 << InvalidatedSubject << getDiagSubjectDescription(VD: DanglingField)
247 << PVD->getSourceRange();
248 reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
249 S.Diag(Loc: DanglingField->getLocation(),
250 DiagID: diag::note_lifetime_safety_dangling_field_here)
251 << DanglingField->getEndLoc();
252 }
253
254 void reportInvalidatedGlobal(const Expr *IssueExpr,
255 const VarDecl *DanglingGlobal,
256 const Expr *InvalidationExpr) override {
257 std::string InvalidatedSubject = getDiagSubjectDescription(E: IssueExpr);
258 S.Diag(Loc: IssueExpr->getExprLoc(),
259 DiagID: diag::warn_lifetime_safety_invalidated_global)
260 << InvalidatedSubject << getDiagSubjectDescription(VD: DanglingGlobal)
261 << IssueExpr->getSourceRange();
262 reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
263 if (DanglingGlobal->isStaticLocal() || DanglingGlobal->isStaticDataMember())
264 S.Diag(Loc: DanglingGlobal->getLocation(),
265 DiagID: diag::note_lifetime_safety_dangling_static_here)
266 << DanglingGlobal->getEndLoc();
267 else
268 S.Diag(Loc: DanglingGlobal->getLocation(),
269 DiagID: diag::note_lifetime_safety_dangling_global_here)
270 << DanglingGlobal->getEndLoc();
271 }
272
273 void reportInvalidatedGlobal(const ParmVarDecl *PVD,
274 const VarDecl *DanglingGlobal,
275 const Expr *InvalidationExpr) override {
276 std::string InvalidatedSubject = getDiagSubjectDescription(VD: PVD);
277 S.Diag(Loc: PVD->getSourceRange().getBegin(),
278 DiagID: diag::warn_lifetime_safety_invalidated_global)
279 << InvalidatedSubject << getDiagSubjectDescription(VD: DanglingGlobal)
280 << PVD->getSourceRange();
281 reportInvalidationSite(InvalidationExpr, InvalidatedSubject);
282 if (DanglingGlobal->isStaticLocal() || DanglingGlobal->isStaticDataMember())
283 S.Diag(Loc: DanglingGlobal->getLocation(),
284 DiagID: diag::note_lifetime_safety_dangling_static_here)
285 << DanglingGlobal->getEndLoc();
286 else
287 S.Diag(Loc: DanglingGlobal->getLocation(),
288 DiagID: diag::note_lifetime_safety_dangling_global_here)
289 << DanglingGlobal->getEndLoc();
290 }
291
292 void suggestLifetimeboundToParmVar(WarningScope Scope,
293 const ParmVarDecl *ParmToAnnotate,
294 EscapingTarget Target) override {
295 unsigned DiagID;
296 if (isa<CXXConstructorDecl>(Val: ParmToAnnotate->getDeclContext()))
297 DiagID = (Scope == WarningScope::CrossTU)
298 ? diag::warn_lifetime_safety_cross_tu_ctor_param_suggestion
299 : diag::warn_lifetime_safety_intra_tu_ctor_param_suggestion;
300 else
301 DiagID = (Scope == WarningScope::CrossTU)
302 ? diag::warn_lifetime_safety_cross_tu_param_suggestion
303 : diag::warn_lifetime_safety_intra_tu_param_suggestion;
304
305 auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(Decl: ParmToAnnotate);
306
307 S.Diag(Loc: InsertionPoint, DiagID)
308 << ParmToAnnotate->getSourceRange()
309 << FixItHint::CreateInsertion(InsertionLoc: InsertionPoint, Code: FixItText);
310
311 if (const auto *EscapeExpr = Target.dyn_cast<const Expr *>())
312 S.Diag(Loc: EscapeExpr->getBeginLoc(),
313 DiagID: diag::note_lifetime_safety_suggestion_returned_here)
314 << EscapeExpr->getSourceRange();
315 else if (const auto *EscapeField = Target.dyn_cast<const FieldDecl *>())
316 S.Diag(Loc: EscapeField->getLocation(),
317 DiagID: diag::note_lifetime_safety_escapes_to_field_here)
318 << EscapeField->getSourceRange();
319 }
320
321 void reportLifetimeboundViolation(
322 const ParmVarDecl *ParmWithLifetimebound) override {
323 const auto *Attr = ParmWithLifetimebound->getAttr<LifetimeBoundAttr>();
324 StringRef ParamName = ParmWithLifetimebound->getName();
325 bool HasName = ParamName.size() > 0;
326 S.Diag(Loc: Attr->getLocation(),
327 DiagID: diag::warn_lifetime_safety_lifetimebound_violation)
328 << HasName << ParamName << Attr->getRange();
329 }
330
331 void reportLifetimeboundViolation(
332 const CXXMethodDecl *MDWithLifetimebound) override {
333 const auto *Attr =
334 getImplicitObjectParamLifetimeBoundAttr(FD: MDWithLifetimebound);
335 assert(Attr && "Expected lifetimebound attribute");
336 S.Diag(Loc: Attr->getLocation(),
337 DiagID: diag::warn_lifetime_safety_lifetimebound_violation)
338 << 2 << "" << Attr->getRange();
339 }
340
341 void reportMisplacedLifetimebound(WarningScope Scope,
342 const CXXMethodDecl *FDef,
343 const CXXMethodDecl *FDecl) override {
344 const auto *Attr = getDirectImplicitObjectLifetimeBoundAttr(FD: FDef);
345 assert(Attr && "Expected lifetimebound attribute");
346 unsigned DiagID =
347 Scope == WarningScope::CrossTU
348 ? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
349 : diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
350
351 auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(MD: FDecl);
352
353 // Do not emit fix-its in macros or at invalid locations.
354 bool IsMacro =
355 FDecl->getBeginLoc().isMacroID() || InsertionPoint.isMacroID();
356
357 if (IsMacro || InsertionPoint.isInvalid())
358 S.Diag(Loc: FDecl->getLocation(), DiagID);
359 else
360 S.Diag(Loc: InsertionPoint, DiagID)
361 << FixItHint::CreateInsertion(InsertionLoc: InsertionPoint, Code: FixItText);
362
363 S.Diag(Loc: Attr->getLocation(), DiagID: diag::note_lifetime_safety_lifetimebound_here)
364 << Attr->getRange();
365 }
366
367 void reportMisplacedLifetimebound(WarningScope Scope,
368 const ParmVarDecl *PVDDef,
369 const ParmVarDecl *PVDDecl) override {
370
371 const auto *Attr = PVDDef->getAttr<LifetimeBoundAttr>();
372 assert(Attr && "Expected lifetimebound attribute");
373 unsigned DiagID =
374 Scope == WarningScope::CrossTU
375 ? diag::warn_lifetime_safety_cross_tu_misplaced_lifetimebound
376 : diag::warn_lifetime_safety_intra_tu_misplaced_lifetimebound;
377
378 auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(Decl: PVDDecl);
379
380 // Do not emit fix-its in macros or at invalid locations.
381 bool IsMacro =
382 PVDDecl->getBeginLoc().isMacroID() || InsertionPoint.isMacroID();
383
384 if (IsMacro || InsertionPoint.isInvalid())
385 S.Diag(Loc: PVDDecl->getBeginLoc(), DiagID) << PVDDecl->getSourceRange();
386 else
387 S.Diag(Loc: InsertionPoint, DiagID)
388 << PVDDecl->getSourceRange()
389 << FixItHint::CreateInsertion(InsertionLoc: InsertionPoint, Code: FixItText);
390
391 S.Diag(Loc: Attr->getLocation(), DiagID: diag::note_lifetime_safety_lifetimebound_here)
392 << Attr->getRange();
393 }
394
395 void reportInapplicableLifetimebound(const ParmVarDecl *PVD) override {
396 assert(PVD->hasAttr<LifetimeBoundAttr>() &&
397 "Expected parameter to have lifetimebound attribute");
398 const auto *Attr = PVD->getAttr<LifetimeBoundAttr>();
399 S.Diag(Loc: Attr->getLocation(),
400 DiagID: diag::warn_lifetime_safety_inapplicable_lifetimebound)
401 << PVD->getType() << Attr->getRange();
402 }
403
404 void suggestLifetimeboundToImplicitThis(WarningScope Scope,
405 const CXXMethodDecl *MD,
406 const Expr *EscapeExpr) override {
407 unsigned DiagID = (Scope == WarningScope::CrossTU)
408 ? diag::warn_lifetime_safety_cross_tu_this_suggestion
409 : diag::warn_lifetime_safety_intra_tu_this_suggestion;
410
411 auto [InsertionPoint, FixItText] = getLifetimeBoundFixIt(MD);
412
413 S.Diag(Loc: InsertionPoint, DiagID)
414 << MD->getNameInfo().getSourceRange()
415 << FixItHint::CreateInsertion(InsertionLoc: InsertionPoint, Code: FixItText);
416
417 S.Diag(Loc: EscapeExpr->getBeginLoc(),
418 DiagID: diag::note_lifetime_safety_suggestion_returned_here)
419 << EscapeExpr->getSourceRange();
420 }
421
422 void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
423 const Expr *EscapeExpr) override {
424 S.Diag(Loc: ParmWithNoescape->getBeginLoc(),
425 DiagID: diag::warn_lifetime_safety_noescape_escapes)
426 << ParmWithNoescape->getSourceRange();
427
428 S.Diag(Loc: EscapeExpr->getBeginLoc(),
429 DiagID: diag::note_lifetime_safety_suggestion_returned_here)
430 << EscapeExpr->getSourceRange();
431 }
432
433 void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
434 const FieldDecl *EscapeField) override {
435 S.Diag(Loc: ParmWithNoescape->getBeginLoc(),
436 DiagID: diag::warn_lifetime_safety_noescape_escapes)
437 << ParmWithNoescape->getSourceRange();
438
439 S.Diag(Loc: EscapeField->getLocation(),
440 DiagID: diag::note_lifetime_safety_escapes_to_field_here)
441 << EscapeField->getEndLoc();
442 }
443
444 void reportNoescapeViolation(const ParmVarDecl *ParmWithNoescape,
445 const VarDecl *EscapeGlobal) override {
446 S.Diag(Loc: ParmWithNoescape->getBeginLoc(),
447 DiagID: diag::warn_lifetime_safety_noescape_escapes)
448 << ParmWithNoescape->getSourceRange();
449 if (EscapeGlobal->isStaticLocal() || EscapeGlobal->isStaticDataMember())
450 S.Diag(Loc: EscapeGlobal->getLocation(),
451 DiagID: diag::note_lifetime_safety_escapes_to_static_storage_here)
452 << EscapeGlobal->getEndLoc();
453 else
454 S.Diag(Loc: EscapeGlobal->getLocation(),
455 DiagID: diag::note_lifetime_safety_escapes_to_global_here)
456 << EscapeGlobal->getEndLoc();
457 }
458
459 void addLifetimeBoundToImplicitThis(const CXXMethodDecl *MD) override {
460 S.addLifetimeBoundToImplicitThis(MD: const_cast<CXXMethodDecl *>(MD));
461 }
462
463private:
464 struct LifetimeBoundMacroCache {
465 bool IsBuilt = false;
466 SmallVector<const IdentifierInfo *> Candidates;
467 };
468
469 void buildLifetimeBoundMacroCache(LifetimeBoundMacroCache &Cache,
470 ArrayRef<TokenValue> Tokens) {
471 if (Cache.IsBuilt)
472 return;
473
474 const Preprocessor &PP = S.getPreprocessor();
475 // Collect macro names that were ever defined as a lifetimebound attribute.
476 for (const auto &M : PP.macros()) {
477 const IdentifierInfo *II = M.first;
478 const MacroDirective *MD = PP.getLocalMacroDirectiveHistory(II);
479 if (!MD)
480 continue;
481
482 // Include earlier matching definitions to handle redefinitions.
483 for (MacroDirective::DefInfo Def = MD->getDefinition(); Def;
484 Def = Def.getPreviousDefinition()) {
485 const MacroInfo *MI = Def.getMacroInfo();
486 if (MI->isObjectLike() && Tokens.size() == MI->getNumTokens() &&
487 std::equal(first1: Tokens.begin(), last1: Tokens.end(), first2: MI->tokens_begin())) {
488 Cache.Candidates.push_back(Elt: II);
489 break;
490 }
491 }
492 }
493 Cache.IsBuilt = true;
494 }
495
496 StringRef getLastCachedMacroWithSpelling(SourceLocation Loc,
497 llvm::ArrayRef<TokenValue> Tokens,
498 LifetimeBoundMacroCache &Cache) {
499 if (Loc.isInvalid())
500 return {};
501
502 buildLifetimeBoundMacroCache(Cache, Tokens);
503
504 const Preprocessor &PP = S.getPreprocessor();
505 const SourceManager &SM = S.getSourceManager();
506 SourceLocation BestLocation;
507 StringRef BestSpelling;
508 for (const IdentifierInfo *II : Cache.Candidates) {
509 const MacroDirective *MD = PP.getLocalMacroDirectiveHistory(II);
510 const MacroDirective::DefInfo Def = MD->findDirectiveAtLoc(L: Loc, SM);
511 if (!Def || !Def.getMacroInfo())
512 continue;
513
514 // Ensure the macro definition active at Loc still has this spelling.
515 const MacroInfo *MI = Def.getMacroInfo();
516 if (!MI->isObjectLike() || Tokens.size() != MI->getNumTokens() ||
517 !std::equal(first1: Tokens.begin(), last1: Tokens.end(), first2: MI->tokens_begin()))
518 continue;
519
520 // Choose the matching macro defined latest before Loc.
521 SourceLocation Location = Def.getLocation();
522 assert(Location.isInvalid() ||
523 SM.isBeforeInTranslationUnit(Location, Loc));
524 if (BestLocation.isInvalid() ||
525 (Location.isValid() &&
526 SM.isBeforeInTranslationUnit(LHS: BestLocation, RHS: Location))) {
527 BestLocation = Location;
528 BestSpelling = II->getName();
529 }
530 }
531 return BestSpelling;
532 }
533
534 void reportInvalidationSite(const Expr *InvalidationExpr,
535 StringRef InvalidatedSubject) {
536 auto Diag = isa<CXXDeleteExpr>(Val: InvalidationExpr)
537 ? diag::note_lifetime_safety_freed_here
538 : diag::note_lifetime_safety_invalidated_here;
539 S.Diag(Loc: InvalidationExpr->getExprLoc(), DiagID: Diag)
540 << InvalidatedSubject << InvalidationExpr->getSourceRange();
541 }
542
543 std::string getLifetimeBoundFixItText(SourceLocation Loc, bool LeadingSpace,
544 bool AllowGNUAttrMacro = true) {
545 const bool UseCXX11AttrSpelling =
546 S.getLangOpts().CPlusPlus || S.getLangOpts().C23;
547 const StringRef Fallback = UseCXX11AttrSpelling
548 ? "[[clang::lifetimebound]]"
549 : "__attribute__((lifetimebound))";
550 StringRef Spelling = S.getLangOpts().LifetimeSafetyLifetimeBoundMacro;
551 if (Spelling.empty() && Loc.isValid()) {
552 const Preprocessor &PP = S.getPreprocessor();
553 if (UseCXX11AttrSpelling)
554 Spelling = getLastCachedMacroWithSpelling(
555 Loc,
556 Tokens: {tok::l_square, tok::l_square, PP.getIdentifierInfo(Name: "clang"),
557 tok::coloncolon, PP.getIdentifierInfo(Name: "lifetimebound"),
558 tok::r_square, tok::r_square},
559 Cache&: ClangLifetimeBoundMacroCache);
560
561 if (Spelling.empty() && AllowGNUAttrMacro)
562 Spelling = getLastCachedMacroWithSpelling(
563 Loc,
564 Tokens: {tok::kw___attribute, tok::l_paren, tok::l_paren,
565 PP.getIdentifierInfo(Name: "lifetimebound"), tok::r_paren, tok::r_paren},
566 Cache&: GNULifetimeBoundMacroCache);
567 }
568 const std::string Text = Spelling.empty() ? Fallback.str() : Spelling.str();
569 return LeadingSpace ? " " + Text : Text + " ";
570 }
571
572 std::pair<SourceLocation, std::string>
573 getLifetimeBoundFixIt(const ParmVarDecl *Decl) {
574 SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
575 Loc: Decl->getEndLoc(), Offset: 0, SM: S.getSourceManager(), LangOpts: S.getLangOpts());
576 bool LeadingSpace = true;
577
578 if (!Decl->getIdentifier()) {
579 // For unnamed parameters, placing attributes after the type would be
580 // parsed as a type attribute, not a parameter attribute.
581 InsertionPoint = Decl->getBeginLoc();
582 LeadingSpace = false;
583 } else if (Decl->hasDefaultArg()) {
584 // If the parameter has a default argument, place the attribute after the
585 // named argument.
586 InsertionPoint = Lexer::getLocForEndOfToken(
587 Loc: Decl->getLocation(), Offset: 0, SM: S.getSourceManager(), LangOpts: S.getLangOpts());
588 }
589 return {InsertionPoint,
590 getLifetimeBoundFixItText(Loc: InsertionPoint, LeadingSpace)};
591 }
592
593 std::pair<SourceLocation, std::string>
594 getLifetimeBoundFixIt(const CXXMethodDecl *MD) {
595 const auto MDL = MD->getTypeSourceInfo()->getTypeLoc();
596 SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
597 Loc: MDL.getEndLoc(), Offset: 0, SM: S.getSourceManager(), LangOpts: S.getLangOpts());
598
599 if (const auto *FPT = MD->getType()->getAs<FunctionProtoType>();
600 FPT && FPT->hasTrailingReturn()) {
601 // For trailing return types, 'getEndLoc()' includes the return type
602 // after '->', placing the attribute in an invalid position.
603 // Instead use 'getLocalRangeEnd()' which gives the '->' location
604 // for trailing returns, so find the last token before it.
605 const auto FTL = MDL.getAs<FunctionTypeLoc>();
606 assert(FTL);
607 InsertionPoint = Lexer::getLocForEndOfToken(
608 Loc: Lexer::findPreviousToken(Loc: FTL.getLocalRangeEnd(), SM: S.getSourceManager(),
609 LangOpts: S.getLangOpts(),
610 /*IncludeComments=*/IncludeComments: false)
611 ->getLocation(),
612 Offset: 0, SM: S.getSourceManager(), LangOpts: S.getLangOpts());
613 }
614 return {InsertionPoint,
615 getLifetimeBoundFixItText(Loc: InsertionPoint, /*LeadingSpace=*/LeadingSpace: true,
616 /*AllowGNUAttrMacro=*/AllowGNUAttrMacro: false)};
617 }
618
619 std::string getDiagSubjectDescription(const ValueDecl *VD) {
620 std::string Res;
621 llvm::raw_string_ostream OS(Res);
622 if (isa<FieldDecl>(Val: VD)) {
623 OS << "field";
624 } else if (isa<ParmVarDecl>(Val: VD)) {
625 OS << "parameter";
626 } else if (const auto *Var = dyn_cast<VarDecl>(Val: VD)) {
627 if (Var->isStaticLocal() || Var->isStaticDataMember())
628 OS << "static variable";
629 else if (Var->hasGlobalStorage())
630 OS << "global variable";
631 else
632 OS << "local variable";
633 } else {
634 OS << "variable";
635 }
636 OS << " '";
637 VD->getNameForDiagnostic(OS, Policy: S.getPrintingPolicy(), /*Qualified=*/Qualified: false);
638 OS << "'";
639 return Res;
640 }
641
642 std::string getDiagSubjectDescription(const Expr *E) {
643 E = E->IgnoreImpCasts();
644 if (isa<MaterializeTemporaryExpr>(Val: E))
645 return "temporary object";
646 if (isa<CXXNewExpr>(Val: E))
647 return "allocated object";
648 if (const auto *DRE = dyn_cast<DeclRefExpr>(Val: E))
649 return getDiagSubjectDescription(VD: DRE->getDecl());
650
651 if (const auto *CE = dyn_cast<CallExpr>(Val: E)) {
652 const auto *FD = CE->getDirectCallee();
653 if (!FD)
654 return "result of call";
655 if (FD->isOverloadedOperator() || isa<CXXConversionDecl>(Val: FD))
656 return "expression";
657 std::string Name;
658 llvm::raw_string_ostream OS(Name);
659 FD->getNameForDiagnostic(OS, Policy: S.getPrintingPolicy(),
660 /*Qualified=*/Qualified: false);
661 return "result of call to '" + Name + "'";
662 }
663
664 // TODO: Handle other expression types.
665 return "expression";
666 }
667
668 bool shouldShowInAliasChain(const Expr *CurrExpr, const Expr *LastExpr) {
669 CurrExpr = CurrExpr->IgnoreImpCasts();
670 LastExpr = LastExpr->IgnoreImpCasts();
671
672 if (!isa<CallExpr, DeclRefExpr>(Val: CurrExpr))
673 return false;
674 // Source ranges can be used to filter out many implicit expressions,
675 // because operations between class objects often involve numerous implicit
676 // conversions, yet they share the same source range.
677 return CurrExpr->getSourceRange() != LastExpr->getSourceRange();
678 }
679
680 void reportAliasingChain(llvm::ArrayRef<const Expr *> OriginExprChain) {
681 if (OriginExprChain.empty())
682 return;
683
684 const Expr *LastExpr = OriginExprChain.back();
685 const Expr *VisibleLastExpr = LastExpr;
686 std::string IssueStr = getDiagSubjectDescription(E: VisibleLastExpr);
687
688 for (const Expr *CurrExpr : reverse(C: OriginExprChain.drop_back())) {
689 if (!shouldShowInAliasChain(CurrExpr, LastExpr: VisibleLastExpr)) {
690 LastExpr = CurrExpr;
691 continue;
692 }
693 std::optional<LifetimeBoundParamInfo> ParamInfo =
694 getTrackingInfoForCallArg(Call: CurrExpr, Source: LastExpr);
695 LastExpr = CurrExpr;
696 if (ParamInfo) {
697 bool IsImplicitObject = isa<const CXXMethodDecl *>(Val: *ParamInfo);
698 std::string ParamName;
699 if (!IsImplicitObject) {
700 const auto *Param = cast<const ParmVarDecl *>(Val&: *ParamInfo);
701 ParamName = Param->getIdentifier()
702 ? "'" + Param->getNameAsString() + "'"
703 : "'<unnamed>'";
704 }
705 S.Diag(Loc: CurrExpr->getBeginLoc(),
706 DiagID: diag::note_lifetime_safety_aliases_storage_lifetimebound)
707 << CurrExpr->getSourceRange() << getDiagSubjectDescription(E: CurrExpr)
708 << IssueStr << IsImplicitObject << ParamName;
709 } else
710 S.Diag(Loc: CurrExpr->getBeginLoc(),
711 DiagID: diag::note_lifetime_safety_aliases_storage)
712 << CurrExpr->getSourceRange() << getDiagSubjectDescription(E: CurrExpr)
713 << IssueStr;
714 VisibleLastExpr = CurrExpr;
715 }
716 }
717
718 LifetimeBoundMacroCache ClangLifetimeBoundMacroCache;
719 LifetimeBoundMacroCache GNULifetimeBoundMacroCache;
720 Sema &S;
721};
722
723} // namespace clang::lifetimes
724
725#endif // LLVM_CLANG_LIB_SEMA_SEMALIFETIMESAFETY_H
726