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