clang 23.0.0git
LifetimeAnnotations.cpp
Go to the documentation of this file.
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//===----------------------------------------------------------------------===//
10#include "clang/AST/Attr.h"
11#include "clang/AST/Decl.h"
12#include "clang/AST/DeclCXX.h"
14#include "clang/AST/Type.h"
15#include "clang/AST/TypeLoc.h"
17#include "llvm/ADT/StringSet.h"
18
19namespace clang::lifetimes {
20
21const FunctionDecl *
23 return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
24}
25
26const CXXMethodDecl *
28 const FunctionDecl *FD = CMD;
29 return cast_if_present<CXXMethodDecl>(
31}
32
35 bool IsAssignment = OO == OO_Equal || isCompoundAssignmentOperator(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>(FD);
44 if (MD && MD->isCXXInstanceMember())
45 LHST = Ctx.getLValueReferenceType(MD->getFunctionObjectParameterType());
46 else
47 LHST = FD->getParamDecl(0)->getType();
48 return Ctx.hasSameType(RetT, LHST);
49}
50
53 return CMD && isNormalAssignmentOperator(CMD) && CMD->param_size() == 1 &&
54 CMD->getParamDecl(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 *
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 *
77 if (const TypeSourceInfo *TSI = FD->getTypeSourceInfo())
78 if (const auto *Attr = getLifetimeBoundAttrFromFunctionType(*TSI))
79 return Attr;
80 return nullptr;
81}
82
83const LifetimeBoundAttr *
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(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
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>(DC))
113 if (const IdentifierInfo *II = ND->getIdentifier()) {
114 StringRef Name = II->getName();
115 if (Name.size() >= 2 && Name.front() == '_' &&
116 (Name[1] == '_' || isUppercase(Name[1])))
117 return true;
118 }
119 }
120 return false;
121}
122
124 return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
125}
126
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(Callee->getFunctionObjectParameterType()) ||
140 (RunningUnderLifetimeSafety &&
141 isGslOwnerType(ImplicitObjectArgument.getBestDynamicClassType()));
142 if (auto *Conv = dyn_cast<CXXConversionDecl>(Callee))
143 if (isGslPointerType(Conv->getConversionType()) && IsGslOwnerImplicitObject)
144 return true;
145 if (!isGslPointerType(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(Callee->getFunctionObjectParameterType()) &&
163 isReferenceOrPointerLikeType(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(Callee->getName()) ||
179 InnerPointerGetters.contains(Callee->getName())))
180 return true;
181 }
182
183 if (!isInStlNamespace(Callee->getParent()))
184 return false;
185
186 if (isPointerLikeType(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(Callee->getName()) ||
195 InnerPointerGetters.contains(Callee->getName()) ||
196 ContainerFindFns.contains(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({"front", "back", "at", "top", "value"}, true)
208 .Default(false);
209 }
210 return false;
211}
212
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() &&
222 if (llvm::StringSwitch<bool>(FD->getName())
223 .Cases(
224 {
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 true)
237 .Default(false))
238 return true;
239 }
240 const auto *RD = FD->getParamDecl(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() ||
251 return llvm::StringSwitch<bool>(FD->getName())
252 .Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
253 .Cases({"end", "rend", "cend", "crend"}, true)
254 .Case("data", true)
255 .Default(false);
256 }
257 if (FD->getReturnType()->isReferenceType()) {
258 return llvm::StringSwitch<bool>(FD->getName())
259 .Cases({"get", "any_cast"}, true)
260 .Default(false);
261 }
262 return false;
263}
264
266 if (FD->getNumParams() < 2)
267 return false;
268 const auto *RD = FD->getParamDecl(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) &&
277 FD->getReturnType()) &&
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>(RD))
302 Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr<T>();
303
304 return Result;
305}
306
307template <typename T> static bool isRecordWithAttr(QualType Type) {
309}
310
315}
316
317static StringRef getName(const CXXRecordDecl &RD) {
318 if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(&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
336 return MD.getIdentifier() && MD.getName() == "release" &&
337 MD.getNumParams() == 0 && isStdUniquePtr(*MD.getParent());
338}
339
341 const CXXRecordDecl *RD = MD.getParent();
342 if (!isInStlNamespace(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);
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("vector", &Vector)
414 .Case("basic_string", &String)
415 .Case("deque", &Deque)
416 .Case("priority_queue", &PriorityQueue)
417 .Cases({"set", "multiset", "map", "multimap", "unordered_set",
418 "unordered_multiset", "unordered_map", "unordered_multimap"},
419 &NodeBased)
420 .Cases({"flat_map", "flat_set", "flat_multimap", "flat_multiset"},
421 &Flat)
422 .Case("unique_ptr", &UniquePtr)
423 .Default(nullptr);
424
425 if (!InvalidatingMethods)
426 return false;
427
428 // Handle Operators via OverloadedOperatorKind
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(MD.getName());
448}
449
452 return true;
453 return isInStlNamespace(&FD) && getName(FD) == "destroy_at";
454}
455
457 if (!RD || !isInStlNamespace(RD))
458 return false;
459 StringRef Name = getName(*RD);
460 return Name == "function" || Name == "move_only_function";
461}
462
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
Defines the clang::ASTContext interface.
Defines the C++ Decl subclasses, other than those for templates (found in DeclTemplate....
Defines the C++ template declaration subclasses.
Defines an enumeration for C++ overloaded operators.
Defines the clang::TypeLoc interface and its subclasses.
C Language Family Type Representation.
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition ASTContext.h:226
QualType getLValueReferenceType(QualType T, bool SpelledAsLValue=true) const
Return the uniqued reference to the type for an lvalue reference to the specified type.
static bool hasSameType(QualType T1, QualType T2)
Determine whether the given types T1 and T2 are equivalent.
static bool hasSameUnqualifiedType(QualType T1, QualType T2)
Determine whether the given types are equivalent after cvr-qualifiers have been removed.
Attr - This represents one attribute.
Definition Attr.h:46
Type source information for an attributed type.
Definition TypeLoc.h:1008
Represents a static or instance method of a struct/union/class.
Definition DeclCXX.h:2132
const CXXRecordDecl * getParent() const
Return the parent of this method declaration, which is the class in which this method is defined.
Definition DeclCXX.h:2271
Represents a C++ struct/union/class.
Definition DeclCXX.h:258
DeclContext - This is used only as base class of specific decl types that can act as declaration cont...
Definition DeclBase.h:1462
DeclContext * getParent()
getParent - Returns the containing DeclContext.
Definition DeclBase.h:2122
Decl - This represents one declaration (or definition), e.g.
Definition DeclBase.h:86
bool isInStdNamespace() const
Definition DeclBase.cpp:450
ASTContext & getASTContext() const LLVM_READONLY
Definition DeclBase.cpp:547
DeclContext * getDeclContext()
Definition DeclBase.h:456
bool hasAttr() const
Definition DeclBase.h:585
OverloadedOperatorKind getCXXOverloadedOperator() const
If this name is the name of an overloadable operator in C++ (e.g., operator+), retrieve the kind of o...
TypeSourceInfo * getTypeSourceInfo() const
Definition Decl.h:809
This represents one expression.
Definition Expr.h:112
const CXXRecordDecl * getBestDynamicClassType() const
For an expression of class type or pointer to class type, return the most derived class decl the expr...
Definition Expr.cpp:69
Represents a function declaration or definition.
Definition Decl.h:2018
const ParmVarDecl * getParamDecl(unsigned i) const
Definition Decl.h:2815
unsigned getBuiltinID(bool ConsiderWrapperFunctions=false) const
Returns a value indicating whether this function corresponds to a builtin function.
Definition Decl.cpp:3736
QualType getReturnType() const
Definition Decl.h:2863
FunctionDecl * getTemplateInstantiationPattern(bool ForDefinition=true) const
Retrieve the function declaration from which this function could be instantiated, if it is an instant...
Definition Decl.cpp:4238
FunctionDecl * getMostRecentDecl()
Returns the most recent (re)declaration of this declaration.
redecl_range redecls() const
Returns an iterator range for all the redeclarations of the same decl.
OverloadedOperatorKind getOverloadedOperator() const
getOverloadedOperator - Which C++ overloaded operator this function represents, if any.
Definition Decl.cpp:4104
unsigned getNumParams() const
Return the number of parameters this function must have based on its FunctionType.
Definition Decl.cpp:3800
size_t param_size() const
Definition Decl.h:2808
One of these records is kept for each identifier that is lexed.
IdentifierInfo * getIdentifier() const
Get the identifier that names this declaration, if there is one.
Definition Decl.h:295
StringRef getName() const
Get the name of identifier for this declaration as a StringRef.
Definition Decl.h:301
DeclarationName getDeclName() const
Get the actual, stored name of the declaration, which may be a special name.
Definition Decl.h:340
A (possibly-)qualified type.
Definition TypeBase.h:937
Base wrapper for a particular "section" of type source info.
Definition TypeLoc.h:59
T getAsAdjusted() const
Convert to the specified TypeLoc type, returning a null TypeLoc if this TypeLoc is not of the desired...
Definition TypeLoc.h:2735
A container of type source information.
Definition TypeBase.h:8416
TypeLoc getTypeLoc() const
Return the TypeLoc wrapper for the type source info.
Definition TypeLoc.h:267
The base class of the type hierarchy.
Definition TypeBase.h:1875
CXXRecordDecl * getAsCXXRecordDecl() const
Retrieves the CXXRecordDecl that this type refers to, either because the type is a RecordType or beca...
Definition Type.h:26
bool isPointerType() const
Definition TypeBase.h:8682
bool isReferenceType() const
Definition TypeBase.h:8706
const CXXRecordDecl * getPointeeCXXRecordDecl() const
If this is a pointer or reference to a RecordType, return the CXXRecordDecl that the type refers to.
Definition Type.cpp:1958
bool isLValueReferenceType() const
Definition TypeBase.h:8710
bool isNullPtrType() const
Definition TypeBase.h:9085
QualType getType() const
Definition Decl.h:723
const LifetimeBoundAttr * getDirectImplicitObjectLifetimeBoundAttr(const FunctionDecl *FD)
bool isGslPointerType(QualType QT)
bool isStdCallableWrapperType(const CXXRecordDecl *RD)
bool shouldTrackFirstArgument(const FunctionDecl *FD)
static StringRef getName(const CXXRecordDecl &RD)
bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD)
bool shouldTrackImplicitObjectArg(const Expr &ImplicitObjectArgument, const CXXMethodDecl *Callee, bool RunningUnderLifetimeSafety)
static const LifetimeBoundAttr * getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI)
Check if a function has a lifetimebound attribute on its function type (which represents the implicit...
bool isPointerLikeType(QualType QT)
bool isNormalAssignmentOperator(const FunctionDecl *FD)
bool isUniquePtrRelease(const CXXMethodDecl &MD)
static bool isRecordWithAttr(const CXXRecordDecl *RD)
static bool isReferenceOrPointerLikeType(QualType QT)
bool isStdReferenceCast(const FunctionDecl *FD)
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD)
const LifetimeBoundAttr * getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD)
const FunctionDecl * getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD)
bool isInvalidationMethod(const CXXMethodDecl &MD)
bool destructsFirstArg(const FunctionDecl &FD)
bool isGslOwnerType(QualType QT)
bool isInStlNamespace(const Decl *D)
bool shouldTrackSecondArgument(const FunctionDecl *FD)
static bool isStdUniquePtr(const CXXRecordDecl &RD)
OverloadedOperatorKind
Enumeration specifying the different kinds of C++ overloaded operators.
@ OO_None
Not an overloaded operator.
bool isa(CodeGen::Address addr)
Definition Address.h:330
bool isCompoundAssignmentOperator(OverloadedOperatorKind Kind)
Determine if this is a compound assignment operator.
LLVM_READONLY bool isUppercase(unsigned char c)
Return true if this character is an uppercase ASCII letter: [A-Z].
Definition CharInfo.h:126
@ Vector
'vector' clause, allowed on 'loop', Combined, and 'routine' directives.
@ Result
The result type of a method or function.
Definition TypeBase.h:905