1 | //===--- ExceptionSpecAnalyzer.cpp - clang-tidy ---------------------------===// |
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 | |
9 | #include "ExceptionSpecAnalyzer.h" |
10 | |
11 | #include "clang/AST/Expr.h" |
12 | |
13 | namespace clang::tidy::utils { |
14 | |
15 | ExceptionSpecAnalyzer::State |
16 | ExceptionSpecAnalyzer::analyze(const FunctionDecl *FuncDecl) { |
17 | // Check if function exist in cache or add temporary value to cache to protect |
18 | // against endless recursion. |
19 | const auto [CacheEntry, NotFound] = |
20 | FunctionCache.try_emplace(Key: FuncDecl, Args: State::NotThrowing); |
21 | if (NotFound) { |
22 | ExceptionSpecAnalyzer::State State = analyzeImpl(FuncDecl); |
23 | // Update result with calculated value |
24 | FunctionCache[FuncDecl] = State; |
25 | return State; |
26 | } |
27 | |
28 | return CacheEntry->getSecond(); |
29 | } |
30 | |
31 | ExceptionSpecAnalyzer::State |
32 | ExceptionSpecAnalyzer::analyzeUnresolvedOrDefaulted( |
33 | const CXXMethodDecl *MethodDecl, const FunctionProtoType *FuncProto) { |
34 | if (!FuncProto || !MethodDecl) |
35 | return State::Unknown; |
36 | |
37 | const DefaultableMemberKind Kind = getDefaultableMemberKind(MethodDecl); |
38 | |
39 | if (Kind == DefaultableMemberKind::None) |
40 | return State::Unknown; |
41 | |
42 | return analyzeRecord(RecordDecl: MethodDecl->getParent(), Kind, SkipMethods: SkipMethods::Yes); |
43 | } |
44 | |
45 | ExceptionSpecAnalyzer::State |
46 | ExceptionSpecAnalyzer::analyzeFieldDecl(const FieldDecl *FDecl, |
47 | DefaultableMemberKind Kind) { |
48 | if (!FDecl) |
49 | return State::Unknown; |
50 | |
51 | if (const CXXRecordDecl *RecDecl = |
52 | FDecl->getType()->getUnqualifiedDesugaredType()->getAsCXXRecordDecl()) |
53 | return analyzeRecord(RecordDecl: RecDecl, Kind); |
54 | |
55 | // Trivial types do not throw |
56 | if (FDecl->getType().isTrivialType(FDecl->getASTContext())) |
57 | return State::NotThrowing; |
58 | |
59 | return State::Unknown; |
60 | } |
61 | |
62 | ExceptionSpecAnalyzer::State |
63 | ExceptionSpecAnalyzer::analyzeBase(const CXXBaseSpecifier &Base, |
64 | DefaultableMemberKind Kind) { |
65 | const auto *RecType = Base.getType()->getAs<RecordType>(); |
66 | if (!RecType) |
67 | return State::Unknown; |
68 | |
69 | const auto *BaseClass = cast<CXXRecordDecl>(Val: RecType->getDecl()); |
70 | |
71 | return analyzeRecord(RecordDecl: BaseClass, Kind); |
72 | } |
73 | |
74 | ExceptionSpecAnalyzer::State |
75 | ExceptionSpecAnalyzer::analyzeRecord(const CXXRecordDecl *RecordDecl, |
76 | DefaultableMemberKind Kind, |
77 | SkipMethods SkipMethods) { |
78 | if (!RecordDecl) |
79 | return State::Unknown; |
80 | |
81 | // Trivial implies noexcept |
82 | if (hasTrivialMemberKind(RecDecl: RecordDecl, Kind)) |
83 | return State::NotThrowing; |
84 | |
85 | if (SkipMethods == SkipMethods::No) |
86 | for (const auto *MethodDecl : RecordDecl->methods()) |
87 | if (getDefaultableMemberKind(MethodDecl) == Kind) |
88 | return analyze(MethodDecl); |
89 | |
90 | for (const auto &BaseSpec : RecordDecl->bases()) { |
91 | State Result = analyzeBase(Base: BaseSpec, Kind); |
92 | if (Result == State::Throwing || Result == State::Unknown) |
93 | return Result; |
94 | } |
95 | |
96 | for (const auto &BaseSpec : RecordDecl->vbases()) { |
97 | State Result = analyzeBase(Base: BaseSpec, Kind); |
98 | if (Result == State::Throwing || Result == State::Unknown) |
99 | return Result; |
100 | } |
101 | |
102 | for (const auto *FDecl : RecordDecl->fields()) |
103 | if (!FDecl->isInvalidDecl() && !FDecl->isUnnamedBitField()) { |
104 | State Result = analyzeFieldDecl(FDecl, Kind); |
105 | if (Result == State::Throwing || Result == State::Unknown) |
106 | return Result; |
107 | } |
108 | |
109 | return State::NotThrowing; |
110 | } |
111 | |
112 | ExceptionSpecAnalyzer::State |
113 | ExceptionSpecAnalyzer::analyzeImpl(const FunctionDecl *FuncDecl) { |
114 | const auto *FuncProto = FuncDecl->getType()->getAs<FunctionProtoType>(); |
115 | if (!FuncProto) |
116 | return State::Unknown; |
117 | |
118 | const ExceptionSpecificationType EST = FuncProto->getExceptionSpecType(); |
119 | |
120 | if (EST == EST_Unevaluated || (EST == EST_None && FuncDecl->isDefaulted())) |
121 | return analyzeUnresolvedOrDefaulted(MethodDecl: cast<CXXMethodDecl>(Val: FuncDecl), |
122 | FuncProto: FuncProto); |
123 | |
124 | return analyzeFunctionEST(FuncDecl, FuncProto: FuncProto); |
125 | } |
126 | |
127 | ExceptionSpecAnalyzer::State |
128 | ExceptionSpecAnalyzer::analyzeFunctionEST(const FunctionDecl *FuncDecl, |
129 | const FunctionProtoType *FuncProto) { |
130 | if (!FuncDecl || !FuncProto) |
131 | return State::Unknown; |
132 | |
133 | if (isUnresolvedExceptionSpec(ESpecType: FuncProto->getExceptionSpecType())) |
134 | return State::Unknown; |
135 | |
136 | // A non defaulted destructor without the noexcept specifier is still noexcept |
137 | if (isa<CXXDestructorDecl>(Val: FuncDecl) && |
138 | FuncDecl->getExceptionSpecType() == EST_None) |
139 | return State::NotThrowing; |
140 | |
141 | switch (FuncProto->canThrow()) { |
142 | case CT_Cannot: |
143 | return State::NotThrowing; |
144 | case CT_Dependent: { |
145 | const Expr *NoexceptExpr = FuncProto->getNoexceptExpr(); |
146 | if (!NoexceptExpr) |
147 | return State::NotThrowing; |
148 | |
149 | // We can't resolve value dependence so just return unknown |
150 | if (NoexceptExpr->isValueDependent()) |
151 | return State::Unknown; |
152 | |
153 | // Try to evaluate the expression to a boolean value |
154 | bool Result = false; |
155 | if (NoexceptExpr->EvaluateAsBooleanCondition( |
156 | Result, Ctx: FuncDecl->getASTContext(), InConstantContext: true)) |
157 | return Result ? State::NotThrowing : State::Throwing; |
158 | |
159 | // The noexcept expression is not value dependent but we can't evaluate it |
160 | // as a boolean condition so we have no idea if its throwing or not |
161 | return State::Unknown; |
162 | } |
163 | default: |
164 | return State::Throwing; |
165 | }; |
166 | } |
167 | |
168 | bool ExceptionSpecAnalyzer::hasTrivialMemberKind(const CXXRecordDecl *RecDecl, |
169 | DefaultableMemberKind Kind) { |
170 | if (!RecDecl) |
171 | return false; |
172 | |
173 | switch (Kind) { |
174 | case DefaultableMemberKind::DefaultConstructor: |
175 | return RecDecl->hasTrivialDefaultConstructor(); |
176 | case DefaultableMemberKind::CopyConstructor: |
177 | return RecDecl->hasTrivialCopyConstructor(); |
178 | case DefaultableMemberKind::MoveConstructor: |
179 | return RecDecl->hasTrivialMoveConstructor(); |
180 | case DefaultableMemberKind::CopyAssignment: |
181 | return RecDecl->hasTrivialCopyAssignment(); |
182 | case DefaultableMemberKind::MoveAssignment: |
183 | return RecDecl->hasTrivialMoveAssignment(); |
184 | case DefaultableMemberKind::Destructor: |
185 | return RecDecl->hasTrivialDestructor(); |
186 | |
187 | default: |
188 | return false; |
189 | } |
190 | } |
191 | |
192 | bool ExceptionSpecAnalyzer::isConstructor(DefaultableMemberKind Kind) { |
193 | switch (Kind) { |
194 | case DefaultableMemberKind::DefaultConstructor: |
195 | case DefaultableMemberKind::CopyConstructor: |
196 | case DefaultableMemberKind::MoveConstructor: |
197 | return true; |
198 | |
199 | default: |
200 | return false; |
201 | } |
202 | } |
203 | |
204 | bool ExceptionSpecAnalyzer::isSpecialMember(DefaultableMemberKind Kind) { |
205 | switch (Kind) { |
206 | case DefaultableMemberKind::DefaultConstructor: |
207 | case DefaultableMemberKind::CopyConstructor: |
208 | case DefaultableMemberKind::MoveConstructor: |
209 | case DefaultableMemberKind::CopyAssignment: |
210 | case DefaultableMemberKind::MoveAssignment: |
211 | case DefaultableMemberKind::Destructor: |
212 | return true; |
213 | default: |
214 | return false; |
215 | } |
216 | } |
217 | |
218 | bool ExceptionSpecAnalyzer::isComparison(DefaultableMemberKind Kind) { |
219 | switch (Kind) { |
220 | case DefaultableMemberKind::CompareEqual: |
221 | case DefaultableMemberKind::CompareNotEqual: |
222 | case DefaultableMemberKind::CompareRelational: |
223 | case DefaultableMemberKind::CompareThreeWay: |
224 | return true; |
225 | default: |
226 | return false; |
227 | } |
228 | } |
229 | |
230 | ExceptionSpecAnalyzer::DefaultableMemberKind |
231 | ExceptionSpecAnalyzer::getDefaultableMemberKind(const FunctionDecl *FuncDecl) { |
232 | if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(Val: FuncDecl)) { |
233 | if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Val: FuncDecl)) { |
234 | if (Ctor->isDefaultConstructor()) |
235 | return DefaultableMemberKind::DefaultConstructor; |
236 | |
237 | if (Ctor->isCopyConstructor()) |
238 | return DefaultableMemberKind::CopyConstructor; |
239 | |
240 | if (Ctor->isMoveConstructor()) |
241 | return DefaultableMemberKind::MoveConstructor; |
242 | } |
243 | |
244 | if (MethodDecl->isCopyAssignmentOperator()) |
245 | return DefaultableMemberKind::CopyAssignment; |
246 | |
247 | if (MethodDecl->isMoveAssignmentOperator()) |
248 | return DefaultableMemberKind::MoveAssignment; |
249 | |
250 | if (isa<CXXDestructorDecl>(Val: FuncDecl)) |
251 | return DefaultableMemberKind::Destructor; |
252 | } |
253 | |
254 | const LangOptions &LangOpts = FuncDecl->getLangOpts(); |
255 | |
256 | switch (FuncDecl->getDeclName().getCXXOverloadedOperator()) { |
257 | case OO_EqualEqual: |
258 | return DefaultableMemberKind::CompareEqual; |
259 | |
260 | case OO_ExclaimEqual: |
261 | return DefaultableMemberKind::CompareNotEqual; |
262 | |
263 | case OO_Spaceship: |
264 | // No point allowing this if <=> doesn't exist in the current language mode. |
265 | if (!LangOpts.CPlusPlus20) |
266 | break; |
267 | return DefaultableMemberKind::CompareThreeWay; |
268 | |
269 | case OO_Less: |
270 | case OO_LessEqual: |
271 | case OO_Greater: |
272 | case OO_GreaterEqual: |
273 | // No point allowing this if <=> doesn't exist in the current language mode. |
274 | if (!LangOpts.CPlusPlus20) |
275 | break; |
276 | return DefaultableMemberKind::CompareRelational; |
277 | |
278 | default: |
279 | break; |
280 | } |
281 | |
282 | // Not a defaultable member kind |
283 | return DefaultableMemberKind::None; |
284 | } |
285 | |
286 | } // namespace clang::tidy::utils |
287 | |