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