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