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