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