1//===- LifetimeAnnotations.cpp - -*--------------- C++------------------*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
9#include "clang/AST/ASTContext.h"
10#include "clang/AST/Attr.h"
11#include "clang/AST/Decl.h"
12#include "clang/AST/DeclCXX.h"
13#include "clang/AST/DeclTemplate.h"
14#include "clang/AST/Expr.h"
15#include "clang/AST/ExprCXX.h"
16#include "clang/AST/Type.h"
17#include "clang/AST/TypeLoc.h"
18#include "clang/Analysis/AnyCall.h"
19#include "clang/Basic/OperatorKinds.h"
20#include "llvm/ADT/StringSet.h"
21
22namespace clang::lifetimes {
23
24const FunctionDecl *
25getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
26 return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
27}
28
29const CXXMethodDecl *
30getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
31 const FunctionDecl *FD = CMD;
32 return cast_if_present<CXXMethodDecl>(
33 Val: getDeclWithMergedLifetimeBoundAttrs(FD));
34}
35
36bool isNormalAssignmentOperator(const FunctionDecl *FD) {
37 OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
38 bool IsAssignment = OO == OO_Equal || isCompoundAssignmentOperator(Kind: OO);
39 if (!IsAssignment)
40 return false;
41 QualType RetT = FD->getReturnType();
42 if (!RetT->isLValueReferenceType())
43 return false;
44 ASTContext &Ctx = FD->getASTContext();
45 QualType LHST;
46 auto *MD = dyn_cast<CXXMethodDecl>(Val: FD);
47 if (MD && MD->isCXXInstanceMember())
48 LHST = Ctx.getLValueReferenceType(T: MD->getFunctionObjectParameterType());
49 else
50 LHST = FD->getParamDecl(i: 0)->getType();
51 return Ctx.hasSameType(T1: RetT, T2: LHST);
52}
53
54bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
55 CMD = getDeclWithMergedLifetimeBoundAttrs(CMD);
56 return CMD && isNormalAssignmentOperator(FD: CMD) && CMD->param_size() == 1 &&
57 CMD->getParamDecl(i: 0)->hasAttr<clang::LifetimeBoundAttr>();
58}
59
60/// Check if a function has a lifetimebound attribute on its function type
61/// (which represents the implicit 'this' parameter for methods).
62/// Returns the attribute if found, nullptr otherwise.
63static const LifetimeBoundAttr *
64getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) {
65 // Walk through the type layers looking for a lifetimebound attribute.
66 TypeLoc TL = TSI.getTypeLoc();
67 while (true) {
68 auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
69 if (!ATL)
70 break;
71 if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
72 return LBAttr;
73 TL = ATL.getModifiedLoc();
74 }
75 return nullptr;
76}
77
78const LifetimeBoundAttr *
79getDirectImplicitObjectLifetimeBoundAttr(const FunctionDecl *FD) {
80 if (const TypeSourceInfo *TSI = FD->getTypeSourceInfo())
81 if (const auto *Attr = getLifetimeBoundAttrFromFunctionType(TSI: *TSI))
82 return Attr;
83 return nullptr;
84}
85
86const LifetimeBoundAttr *
87getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD) {
88 FD = getDeclWithMergedLifetimeBoundAttrs(FD);
89 // Attribute merging doesn't work well with attributes on function types (like
90 // 'this' param). We need to check all redeclarations.
91 auto CheckRedecls = [](const FunctionDecl *F) -> const LifetimeBoundAttr * {
92 for (const FunctionDecl *Redecl : F->redecls())
93 if (const auto *Attr = getDirectImplicitObjectLifetimeBoundAttr(FD: Redecl))
94 return Attr;
95 return nullptr;
96 };
97
98 if (const auto *Attr = CheckRedecls(FD))
99 return Attr;
100 if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern())
101 return CheckRedecls(Pattern);
102 return nullptr;
103}
104
105bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
106 if (getImplicitObjectParamLifetimeBoundAttr(FD))
107 return true;
108 return isNormalAssignmentOperator(FD);
109}
110
111FunctionCallInfo::FunctionCallInfo(const Expr *Call) {
112 if (!Call)
113 return;
114
115 std::optional<AnyCall> AC = AnyCall::forExpr(E: Call->IgnoreParenImpCasts());
116 if (!AC)
117 return;
118
119 FD = dyn_cast_or_null<FunctionDecl>(Val: AC->getDecl());
120 if (!FD)
121 return;
122
123 Args = AC->arguments();
124}
125
126std::optional<LifetimeBoundParamInfo>
127getTrackedArgInfo(const FunctionDecl *FD, llvm::ArrayRef<const Expr *> Args,
128 unsigned I) {
129 FD = getDeclWithMergedLifetimeBoundAttrs(FD);
130 if (!FD || I >= Args.size())
131 return std::nullopt;
132
133 const ParmVarDecl *PVD = nullptr;
134
135 if (const auto *Method = dyn_cast<CXXMethodDecl>(Val: FD);
136 Method && Method->isInstance() && !isa<CXXConstructorDecl>(Val: FD)) {
137 if (I == 0) {
138 // For the 'this' argument, the attribute is on the method itself.
139 if (implicitObjectParamIsLifetimeBound(FD: Method) ||
140 shouldTrackImplicitObjectArg(ImplicitObjectArgument: *Args[0], Callee: Method,
141 /*RunningUnderLifetimeSafety=*/true))
142 return LifetimeBoundParamInfo(Method);
143 return std::nullopt;
144 }
145 if ((I - 1) < Method->getNumParams())
146 // For explicit arguments, find the corresponding parameter declaration.
147 PVD = Method->getParamDecl(i: I - 1);
148 } else if (I == 0 && shouldTrackFirstArgument(FD)) {
149 return LifetimeBoundParamInfo(FD->getParamDecl(i: I));
150 } else if (I == 1 && shouldTrackSecondArgument(FD)) {
151 return LifetimeBoundParamInfo(FD->getParamDecl(i: I));
152 } else if (I < FD->getNumParams()) {
153 // For free functions or static methods.
154 PVD = FD->getParamDecl(i: I);
155 }
156
157 if (PVD && PVD->hasAttr<clang::LifetimeBoundAttr>())
158 return LifetimeBoundParamInfo(PVD);
159
160 return std::nullopt;
161}
162
163std::optional<LifetimeBoundParamInfo>
164getTrackingInfoForCallArg(const Expr *Call, const Expr *Source) {
165 if (!Call || !Source)
166 return std::nullopt;
167
168 auto [FD, Args] = FunctionCallInfo(Call);
169 if (!FD)
170 return std::nullopt;
171
172 for (unsigned I = 0; I < Args.size(); ++I)
173 if (Args[I]->IgnoreParenImpCasts() == Source->IgnoreParenImpCasts())
174 if (std::optional<LifetimeBoundParamInfo> ParamInfo =
175 getTrackedArgInfo(FD, Args, I))
176 return ParamInfo;
177
178 return std::nullopt;
179}
180
181bool isInStlNamespace(const Decl *D) {
182 for (const DeclContext *DC = D->getDeclContext(); DC; DC = DC->getParent()) {
183 if (DC->isStdNamespace())
184 return true;
185 if (const auto *ND = dyn_cast<NamespaceDecl>(Val: DC))
186 if (const IdentifierInfo *II = ND->getIdentifier()) {
187 StringRef Name = II->getName();
188 if (Name.size() >= 2 && Name.front() == '_' &&
189 (Name[1] == '_' || isUppercase(c: Name[1])))
190 return true;
191 }
192 }
193 return false;
194}
195
196bool isPointerLikeType(QualType QT) {
197 return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
198}
199
200static bool isReferenceOrPointerLikeType(QualType QT) {
201 return QT->isReferenceType() || isPointerLikeType(QT);
202}
203
204bool shouldTrackImplicitObjectArg(const Expr &ImplicitObjectArgument,
205 const CXXMethodDecl *Callee,
206 bool RunningUnderLifetimeSafety) {
207 if (!Callee)
208 return false;
209 // Check both the declaring class and the call-site object: a gsl::Owner
210 // may inherit its accessors from a non-Owner base (e.g. libc++ optional).
211 const bool IsGslOwnerImplicitObject =
212 isGslOwnerType(QT: Callee->getFunctionObjectParameterType()) ||
213 (RunningUnderLifetimeSafety &&
214 isGslOwnerType(RD: ImplicitObjectArgument.getBestDynamicClassType()));
215 if (auto *Conv = dyn_cast<CXXConversionDecl>(Val: Callee))
216 if (isGslPointerType(QT: Conv->getConversionType()) && IsGslOwnerImplicitObject)
217 return true;
218 if (!isGslPointerType(QT: Callee->getFunctionObjectParameterType()) &&
219 !IsGslOwnerImplicitObject)
220 return false;
221
222 // Begin and end iterators.
223 static const llvm::StringSet<> IteratorMembers = {
224 "begin", "end", "rbegin", "rend", "cbegin", "cend", "crbegin", "crend"};
225 static const llvm::StringSet<> InnerPointerGetters = {
226 // Inner pointer getters.
227 "c_str", "data", "get"};
228 static const llvm::StringSet<> ContainerFindFns = {
229 // Map and set types.
230 "find", "equal_range", "lower_bound", "upper_bound"};
231 // Track dereference operator and transparent functions like begin(), get(),
232 // etc. for all GSL pointers. Only do so for lifetime safety analysis and not
233 // for Sema's statement-local analysis as it starts to have false-positives.
234 if (RunningUnderLifetimeSafety &&
235 isGslPointerType(QT: Callee->getFunctionObjectParameterType()) &&
236 isReferenceOrPointerLikeType(QT: Callee->getReturnType())) {
237 // Propagate origins through GSL pointer arithmetic and dereference
238 // operators.
239 switch (Callee->getOverloadedOperator()) {
240 case OO_Arrow:
241 case OO_Star:
242 case OO_Plus:
243 case OO_Minus:
244 case OO_PlusPlus:
245 case OO_MinusMinus:
246 return true;
247 default:
248 break;
249 }
250 if (Callee->getIdentifier() &&
251 (IteratorMembers.contains(key: Callee->getName()) ||
252 InnerPointerGetters.contains(key: Callee->getName())))
253 return true;
254 }
255
256 if (!isInStlNamespace(D: Callee->getParent()))
257 return false;
258
259 if (isPointerLikeType(QT: Callee->getReturnType())) {
260 if (!Callee->getIdentifier())
261 // e.g., std::optional<T>::operator->() returns T*.
262 return RunningUnderLifetimeSafety
263 ? IsGslOwnerImplicitObject &&
264 Callee->getOverloadedOperator() ==
265 OverloadedOperatorKind::OO_Arrow
266 : false;
267 return IteratorMembers.contains(key: Callee->getName()) ||
268 InnerPointerGetters.contains(key: Callee->getName()) ||
269 ContainerFindFns.contains(key: Callee->getName());
270 }
271 if (Callee->getReturnType()->isReferenceType()) {
272 if (!Callee->getIdentifier()) {
273 auto OO = Callee->getOverloadedOperator();
274 if (!IsGslOwnerImplicitObject)
275 return false;
276 return OO == OverloadedOperatorKind::OO_Subscript ||
277 OO == OverloadedOperatorKind::OO_Star;
278 }
279 return llvm::StringSwitch<bool>(Callee->getName())
280 .Cases(CaseStrings: {"front", "back", "at", "top", "value"}, Value: true)
281 .Default(Value: false);
282 }
283 return false;
284}
285
286bool shouldTrackFirstArgument(const FunctionDecl *FD) {
287 if (!FD->getIdentifier() || FD->getNumParams() < 1)
288 return false;
289 if (!FD->isInStdNamespace())
290 return false;
291 // Track std:: algorithm functions that return an iterator whose lifetime is
292 // bound to the first argument.
293 if (FD->getNumParams() >= 2 && FD->isInStdNamespace() &&
294 isGslPointerType(QT: FD->getReturnType())) {
295 if (llvm::StringSwitch<bool>(FD->getName())
296 .Cases(
297 CaseStrings: {
298 "find",
299 "find_if",
300 "find_if_not",
301 "find_first_of",
302 "adjacent_find",
303 "search",
304 "find_end",
305 "lower_bound",
306 "upper_bound",
307 "partition_point",
308 },
309 Value: true)
310 .Default(Value: false))
311 return true;
312 }
313 const auto *RD = FD->getParamDecl(i: 0)->getType()->getPointeeCXXRecordDecl();
314 if (!RD || !RD->isInStdNamespace())
315 return false;
316 if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
317 return false;
318
319 if (FD->getNumParams() != 1)
320 return false;
321
322 if (FD->getReturnType()->isPointerType() ||
323 isGslPointerType(QT: FD->getReturnType())) {
324 return llvm::StringSwitch<bool>(FD->getName())
325 .Cases(CaseStrings: {"begin", "rbegin", "cbegin", "crbegin"}, Value: true)
326 .Cases(CaseStrings: {"end", "rend", "cend", "crend"}, Value: true)
327 .Case(S: "data", Value: true)
328 .Default(Value: false);
329 }
330 if (FD->getReturnType()->isReferenceType()) {
331 return llvm::StringSwitch<bool>(FD->getName())
332 .Cases(CaseStrings: {"get", "any_cast"}, Value: true)
333 .Default(Value: false);
334 }
335 return false;
336}
337
338bool shouldTrackSecondArgument(const FunctionDecl *FD) {
339 if (FD->getNumParams() < 2)
340 return false;
341 const auto *RD = FD->getParamDecl(i: 1)->getType()->getAsCXXRecordDecl();
342 if (!RD)
343 return false;
344 // For free-standing `+`/`-` operators annotated with `gsl::Pointer`, track
345 // the second parameter when its type matches the return type.
346 return RD->hasAttr<PointerAttr>() &&
347 (FD->getOverloadedOperator() == OO_Plus ||
348 FD->getOverloadedOperator() == OO_Minus) &&
349 ASTContext::hasSameUnqualifiedType(T1: FD->getParamDecl(i: 1)->getType(),
350 T2: FD->getReturnType()) &&
351 !isa<CXXMethodDecl>(Val: FD);
352}
353
354template <typename T> static bool isRecordWithAttr(const CXXRecordDecl *RD) {
355 if (!RD)
356 return false;
357 // Generally, if a primary template class declaration is annotated with an
358 // attribute, all its specializations generated from template instantiations
359 // should inherit the attribute.
360 //
361 // However, since lifetime analysis occurs during parsing, we may encounter
362 // cases where a full definition of the specialization is not required. In
363 // such cases, the specialization declaration remains incomplete and lacks the
364 // attribute. Therefore, we fall back to checking the primary template class.
365 //
366 // Note: it is possible for a specialization declaration to have an attribute
367 // even if the primary template does not.
368 //
369 // FIXME: What if the primary template and explicit specialization
370 // declarations have conflicting attributes? We should consider diagnosing
371 // this scenario.
372 bool Result = RD->hasAttr<T>();
373
374 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: RD))
375 Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr<T>();
376
377 return Result;
378}
379
380template <typename T> static bool isRecordWithAttr(QualType Type) {
381 return isRecordWithAttr<T>(Type->getAsCXXRecordDecl());
382}
383
384bool isGslPointerType(QualType QT) { return isRecordWithAttr<PointerAttr>(Type: QT); }
385bool isGslOwnerType(QualType QT) { return isRecordWithAttr<OwnerAttr>(Type: QT); }
386bool isGslOwnerType(const CXXRecordDecl *RD) {
387 return isRecordWithAttr<OwnerAttr>(RD);
388}
389
390static StringRef getName(const CXXRecordDecl &RD) {
391 if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: &RD))
392 return CTSD->getSpecializedTemplate()->getName();
393 if (RD.getIdentifier())
394 return RD.getName();
395 return "";
396}
397
398static StringRef getName(const FunctionDecl &FD) {
399 if (FD.getIdentifier())
400 return FD.getName();
401 return "";
402}
403
404static bool isStdUniquePtr(const CXXRecordDecl &RD) {
405 return RD.isInStdNamespace() && getName(RD) == "unique_ptr";
406}
407
408bool isUniquePtrRelease(const CXXMethodDecl &MD) {
409 return MD.getIdentifier() && MD.getName() == "release" &&
410 MD.getNumParams() == 0 && isStdUniquePtr(RD: *MD.getParent());
411}
412
413bool isInvalidationMethod(const CXXMethodDecl &MD) {
414 const CXXRecordDecl *RD = MD.getParent();
415 if (!isInStlNamespace(D: RD))
416 return false;
417
418 // `pop_back` is excluded: it only invalidates references to the removed
419 // element, not to other elements.
420 static const llvm::StringSet<> Vector = {// Insertion
421 "insert", "emplace", "emplace_back",
422 "push_back", "insert_range",
423 "append_range",
424 // Removal
425 "erase", "clear",
426 // Memory management
427 "reserve", "resize", "shrink_to_fit",
428 // Assignment
429 "assign", "assign_range"};
430
431 // `pop_*` methods are excluded: they only invalidate references to the
432 // removed element, not to other elements.
433 static const llvm::StringSet<> Deque = {// Insertion
434 "insert", "emplace", "insert_range",
435 // Removal
436 "erase", "clear",
437 // Memory management
438 "resize", "shrink_to_fit",
439 // Assignment
440 "assign", "assign_range"};
441
442 static const llvm::StringSet<> String = {
443 // Insertion
444 "insert", "push_back", "append", "replace", "replace_with_range",
445 "insert_range", "append_range",
446 // Removal
447 "pop_back", "erase", "clear",
448 // Memory management
449 "reserve", "resize", "resize_and_overwrite", "shrink_to_fit",
450 // Assignment
451 "swap", "assign", "assign_range"};
452
453 // FIXME: Add queue and stack and check for underlying container
454 // (e.g. no invalidation for std::list).
455 static const llvm::StringSet<> PriorityQueue = {// Insertion
456 "push", "emplace",
457 "push_range",
458 // Removal
459 "pop"};
460
461 // `erase` and `extract` are excluded: they only affect the removed element,
462 // not to other elements.
463 static const llvm::StringSet<> NodeBased = {// Removal
464 "clear"};
465
466 // For `flat_*` container adaptors, `try_emplace` and `insert_or_assign`
467 // only exist on `flat_map`. Listing them here is harmless since the methods
468 // won't be found on other types.
469 static const llvm::StringSet<> Flat = {// Insertion
470 "insert", "emplace", "emplace_hint",
471 "try_emplace", "insert_or_assign",
472 "insert_range", "merge",
473 // Removal
474 "extract", "erase", "clear",
475 // Assignment
476 "replace"};
477
478 static const llvm::StringSet<> UniquePtr = {// Reallocation
479 "reset"};
480
481 const StringRef RecordName = getName(RD: *RD);
482 // TODO: Consider caching this lookup by CXXMethodDecl pointer if this
483 // StringSwitch becomes a performance bottleneck.
484 const llvm::StringSet<> *InvalidatingMethods =
485 llvm::StringSwitch<const llvm::StringSet<> *>(RecordName)
486 .Case(S: "vector", Value: &Vector)
487 .Case(S: "basic_string", Value: &String)
488 .Case(S: "deque", Value: &Deque)
489 .Case(S: "priority_queue", Value: &PriorityQueue)
490 .Cases(CaseStrings: {"set", "multiset", "map", "multimap", "unordered_set",
491 "unordered_multiset", "unordered_map", "unordered_multimap"},
492 Value: &NodeBased)
493 .Cases(CaseStrings: {"flat_map", "flat_set", "flat_multimap", "flat_multiset"},
494 Value: &Flat)
495 .Case(S: "unique_ptr", Value: &UniquePtr)
496 .Default(Value: nullptr);
497
498 if (!InvalidatingMethods)
499 return false;
500
501 // Handle Operators via OverloadedOperatorKind
502 OverloadedOperatorKind OO = MD.getOverloadedOperator();
503 if (OO != OO_None) {
504 switch (OO) {
505 case OO_Equal: // operator= : Always invalidates (Assignment)
506 case OO_PlusEqual: // operator+= : Append (String/Vector)
507 return true;
508 case OO_Subscript: // operator[] : Invalidation only for
509 // `flat_map` (Insert-or-access).
510 // `map` and `unordered_map` are excluded.
511 return RecordName == "flat_map";
512 default:
513 return false;
514 }
515 }
516
517 if (!MD.getIdentifier())
518 return false;
519
520 return InvalidatingMethods->contains(key: MD.getName());
521}
522
523bool destructsFirstArg(const FunctionDecl &FD) {
524 if (isa<CXXDestructorDecl>(Val: FD))
525 return true;
526 return isInStlNamespace(D: &FD) && getName(FD) == "destroy_at";
527}
528
529bool isStdCallableWrapperType(const CXXRecordDecl *RD) {
530 if (!RD || !isInStlNamespace(D: RD))
531 return false;
532 StringRef Name = getName(RD: *RD);
533 return Name == "function" || Name == "move_only_function";
534}
535
536bool isStdReferenceCast(const FunctionDecl *FD) {
537 if (!FD)
538 return false;
539 switch (FD->getBuiltinID()) {
540 case Builtin::BImove:
541 case Builtin::BImove_if_noexcept:
542 case Builtin::BIforward:
543 case Builtin::BIforward_like:
544 case Builtin::BIas_const:
545 return true;
546 default:
547 return false;
548 }
549}
550
551} // namespace clang::lifetimes
552