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