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
258} // namespace clang::lifetimes
259