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