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 "llvm/ADT/StringSet.h"
17
18namespace clang::lifetimes {
19
20const FunctionDecl *
21getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
22 return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
23}
24
25const CXXMethodDecl *
26getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
27 const FunctionDecl *FD = CMD;
28 return cast_if_present<CXXMethodDecl>(
29 Val: getDeclWithMergedLifetimeBoundAttrs(FD));
30}
31
32bool isNormalAssignmentOperator(const FunctionDecl *FD) {
33 OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
34 bool IsAssignment = OO == OO_Equal || isCompoundAssignmentOperator(Kind: OO);
35 if (!IsAssignment)
36 return false;
37 QualType RetT = FD->getReturnType();
38 if (!RetT->isLValueReferenceType())
39 return false;
40 ASTContext &Ctx = FD->getASTContext();
41 QualType LHST;
42 auto *MD = dyn_cast<CXXMethodDecl>(Val: FD);
43 if (MD && MD->isCXXInstanceMember())
44 LHST = Ctx.getLValueReferenceType(T: MD->getFunctionObjectParameterType());
45 else
46 LHST = FD->getParamDecl(i: 0)->getType();
47 return Ctx.hasSameType(T1: RetT, T2: LHST);
48}
49
50bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
51 CMD = getDeclWithMergedLifetimeBoundAttrs(CMD);
52 return CMD && isNormalAssignmentOperator(FD: CMD) && CMD->param_size() == 1 &&
53 CMD->getParamDecl(i: 0)->hasAttr<clang::LifetimeBoundAttr>();
54}
55
56/// Check if a function has a lifetimebound attribute on its function type
57/// (which represents the implicit 'this' parameter for methods).
58/// Returns the attribute if found, nullptr otherwise.
59static const LifetimeBoundAttr *
60getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) {
61 // Walk through the type layers looking for a lifetimebound attribute.
62 TypeLoc TL = TSI.getTypeLoc();
63 while (true) {
64 auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
65 if (!ATL)
66 break;
67 if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
68 return LBAttr;
69 TL = ATL.getModifiedLoc();
70 }
71 return nullptr;
72}
73
74bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
75 FD = getDeclWithMergedLifetimeBoundAttrs(FD);
76 // Attribute merging doesn't work well with attributes on function types (like
77 // 'this' param). We need to check all redeclarations.
78 auto CheckRedecls = [](const FunctionDecl *F) {
79 return llvm::any_of(Range: F->redecls(), P: [](const FunctionDecl *Redecl) {
80 const TypeSourceInfo *TSI = Redecl->getTypeSourceInfo();
81 return TSI && getLifetimeBoundAttrFromFunctionType(TSI: *TSI);
82 });
83 };
84
85 if (CheckRedecls(FD))
86 return true;
87 if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern();
88 Pattern && CheckRedecls(Pattern))
89 return true;
90 return isNormalAssignmentOperator(FD);
91}
92
93bool isInStlNamespace(const Decl *D) {
94 const DeclContext *DC = D->getDeclContext();
95 if (!DC)
96 return false;
97 if (const auto *ND = dyn_cast<NamespaceDecl>(Val: DC))
98 if (const IdentifierInfo *II = ND->getIdentifier()) {
99 StringRef Name = II->getName();
100 if (Name.size() >= 2 && Name.front() == '_' &&
101 (Name[1] == '_' || isUppercase(c: Name[1])))
102 return true;
103 }
104 return DC->isStdNamespace();
105}
106
107bool isPointerLikeType(QualType QT) {
108 return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
109}
110
111static bool isReferenceOrPointerLikeType(QualType QT) {
112 return QT->isReferenceType() || isPointerLikeType(QT);
113}
114
115bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
116 bool RunningUnderLifetimeSafety) {
117 if (!Callee)
118 return false;
119 if (auto *Conv = dyn_cast<CXXConversionDecl>(Val: Callee))
120 if (isGslPointerType(QT: Conv->getConversionType()) &&
121 Callee->getParent()->hasAttr<OwnerAttr>())
122 return true;
123 if (!isGslPointerType(QT: Callee->getFunctionObjectParameterType()) &&
124 !isGslOwnerType(QT: Callee->getFunctionObjectParameterType()))
125 return false;
126
127 // Begin and end iterators.
128 static const llvm::StringSet<> IteratorMembers = {
129 "begin", "end", "rbegin", "rend", "cbegin", "cend", "crbegin", "crend"};
130 static const llvm::StringSet<> InnerPointerGetters = {
131 // Inner pointer getters.
132 "c_str", "data", "get"};
133 static const llvm::StringSet<> ContainerFindFns = {
134 // Map and set types.
135 "find", "equal_range", "lower_bound", "upper_bound"};
136 // Track dereference operator and transparent functions like begin(), get(),
137 // etc. for all GSL pointers. Only do so for lifetime safety analysis and not
138 // for Sema's statement-local analysis as it starts to have false-positives.
139 if (RunningUnderLifetimeSafety &&
140 isGslPointerType(QT: Callee->getFunctionObjectParameterType()) &&
141 isReferenceOrPointerLikeType(QT: Callee->getReturnType())) {
142 if (Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Star ||
143 Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Arrow)
144 return true;
145 if (Callee->getIdentifier() &&
146 (IteratorMembers.contains(key: Callee->getName()) ||
147 InnerPointerGetters.contains(key: Callee->getName())))
148 return true;
149 }
150
151 if (!isInStlNamespace(D: Callee->getParent()))
152 return false;
153
154 if (isPointerLikeType(QT: Callee->getReturnType())) {
155 if (!Callee->getIdentifier())
156 return false;
157 return IteratorMembers.contains(key: Callee->getName()) ||
158 InnerPointerGetters.contains(key: Callee->getName()) ||
159 ContainerFindFns.contains(key: Callee->getName());
160 }
161 if (Callee->getReturnType()->isReferenceType()) {
162 if (!Callee->getIdentifier()) {
163 auto OO = Callee->getOverloadedOperator();
164 if (!Callee->getParent()->hasAttr<OwnerAttr>())
165 return false;
166 return OO == OverloadedOperatorKind::OO_Subscript ||
167 OO == OverloadedOperatorKind::OO_Star;
168 }
169 return llvm::StringSwitch<bool>(Callee->getName())
170 .Cases(CaseStrings: {"front", "back", "at", "top", "value"}, Value: true)
171 .Default(Value: false);
172 }
173 return false;
174}
175
176bool shouldTrackFirstArgument(const FunctionDecl *FD) {
177 if (!FD->getIdentifier() || FD->getNumParams() < 1)
178 return false;
179 if (!FD->isInStdNamespace())
180 return false;
181 // Track std:: algorithm functions that return an iterator whose lifetime is
182 // bound to the first argument.
183 if (FD->getNumParams() >= 2 && FD->isInStdNamespace() &&
184 isGslPointerType(QT: FD->getReturnType())) {
185 if (llvm::StringSwitch<bool>(FD->getName())
186 .Cases(
187 CaseStrings: {
188 "find",
189 "find_if",
190 "find_if_not",
191 "find_first_of",
192 "adjacent_find",
193 "search",
194 "find_end",
195 "lower_bound",
196 "upper_bound",
197 "partition_point",
198 },
199 Value: true)
200 .Default(Value: false))
201 return true;
202 }
203 const auto *RD = FD->getParamDecl(i: 0)->getType()->getPointeeCXXRecordDecl();
204 if (!RD || !RD->isInStdNamespace())
205 return false;
206 if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
207 return false;
208
209 if (FD->getNumParams() != 1)
210 return false;
211
212 if (FD->getReturnType()->isPointerType() ||
213 isGslPointerType(QT: FD->getReturnType())) {
214 return llvm::StringSwitch<bool>(FD->getName())
215 .Cases(CaseStrings: {"begin", "rbegin", "cbegin", "crbegin"}, Value: true)
216 .Cases(CaseStrings: {"end", "rend", "cend", "crend"}, Value: true)
217 .Case(S: "data", Value: true)
218 .Default(Value: false);
219 }
220 if (FD->getReturnType()->isReferenceType()) {
221 return llvm::StringSwitch<bool>(FD->getName())
222 .Cases(CaseStrings: {"get", "any_cast"}, Value: true)
223 .Default(Value: false);
224 }
225 return false;
226}
227
228template <typename T> static bool isRecordWithAttr(QualType Type) {
229 auto *RD = Type->getAsCXXRecordDecl();
230 if (!RD)
231 return false;
232 // Generally, if a primary template class declaration is annotated with an
233 // attribute, all its specializations generated from template instantiations
234 // should inherit the attribute.
235 //
236 // However, since lifetime analysis occurs during parsing, we may encounter
237 // cases where a full definition of the specialization is not required. In
238 // such cases, the specialization declaration remains incomplete and lacks the
239 // attribute. Therefore, we fall back to checking the primary template class.
240 //
241 // Note: it is possible for a specialization declaration to have an attribute
242 // even if the primary template does not.
243 //
244 // FIXME: What if the primary template and explicit specialization
245 // declarations have conflicting attributes? We should consider diagnosing
246 // this scenario.
247 bool Result = RD->hasAttr<T>();
248
249 if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: RD))
250 Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr<T>();
251
252 return Result;
253}
254
255bool isGslPointerType(QualType QT) { return isRecordWithAttr<PointerAttr>(Type: QT); }
256bool isGslOwnerType(QualType QT) { return isRecordWithAttr<OwnerAttr>(Type: QT); }
257
258static StringRef getName(const CXXRecordDecl &RD) {
259 if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(Val: &RD))
260 return CTSD->getSpecializedTemplate()->getName();
261 if (RD.getIdentifier())
262 return RD.getName();
263 return "";
264}
265
266static bool isStdUniquePtr(const CXXRecordDecl &RD) {
267 return RD.isInStdNamespace() && getName(RD) == "unique_ptr";
268}
269
270bool isUniquePtrRelease(const CXXMethodDecl &MD) {
271 return MD.getIdentifier() && MD.getName() == "release" &&
272 MD.getNumParams() == 0 && isStdUniquePtr(RD: *MD.getParent());
273}
274
275bool isContainerInvalidationMethod(const CXXMethodDecl &MD) {
276 const CXXRecordDecl *RD = MD.getParent();
277 if (!isInStlNamespace(D: RD))
278 return false;
279
280 StringRef ContainerName = getName(RD: *RD);
281 static const llvm::StringSet<> Containers = {
282 // Sequence
283 "vector", "basic_string", "deque",
284 // Adaptors
285 // FIXME: Add queue and stack and check for underlying container (e.g. no
286 // invalidation for std::list).
287 "priority_queue",
288 // Associative
289 "set", "multiset", "map", "multimap",
290 // Unordered Associative
291 "unordered_set", "unordered_multiset", "unordered_map",
292 "unordered_multimap",
293 // C++23 Flat
294 "flat_map", "flat_set", "flat_multimap", "flat_multiset"};
295
296 if (!Containers.contains(key: ContainerName))
297 return false;
298
299 // Handle Operators via OverloadedOperatorKind
300 OverloadedOperatorKind OO = MD.getOverloadedOperator();
301 if (OO != OO_None) {
302 switch (OO) {
303 case OO_Equal: // operator= : Always invalidates (Assignment)
304 case OO_PlusEqual: // operator+= : Append (String/Vector)
305 return true;
306 case OO_Subscript: // operator[] : Invalidation only for Maps
307 // (Insert-or-access)
308 {
309 static const llvm::StringSet<> MapContainers = {"map", "unordered_map",
310 "flat_map"};
311 return MapContainers.contains(key: ContainerName);
312 }
313 default:
314 return false;
315 }
316 }
317
318 if (!MD.getIdentifier())
319 return false;
320
321 StringRef MethodName = MD.getName();
322
323 // Special handling for 'erase':
324 // It invalidates the whole container (effectively) for contiguous/flat
325 // storage, but is safe for other iterators in node-based containers.
326 if (MethodName == "erase") {
327 static const llvm::StringSet<> NodeBasedContainers = {"map",
328 "set",
329 "multimap",
330 "multiset",
331 "unordered_map",
332 "unordered_set",
333 "unordered_multimap",
334 "unordered_multiset"};
335
336 // 'erase' invalidates for non node-based containers (vector, deque, string,
337 // flat_map).
338 return !NodeBasedContainers.contains(key: ContainerName);
339 }
340
341 static const llvm::StringSet<> InvalidatingMembers = {
342 // Basic Insertion/Emplacement
343 "push_front", "push_back", "emplace_front", "emplace_back", "insert",
344 "emplace", "push",
345 // Basic Removal/Clearing
346 "pop_front", "pop_back", "pop", "clear",
347 // Memory Management
348 "reserve", "resize", "shrink_to_fit",
349 // Assignment (Named)
350 "assign", "swap",
351 // String Specifics
352 "append", "replace",
353 // Modern C++ (C++17/23)
354 "extract", "try_emplace", "insert_range", "append_range", "assign_range"};
355
356 return InvalidatingMembers.contains(key: MethodName);
357}
358} // namespace clang::lifetimes
359