1 //===--- ExceptionSpecAnalyzer.cpp - clang-tidy ---------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ExceptionSpecAnalyzer.h"
11 #include "clang/AST/Expr.h"
13 namespace clang::tidy::utils
{
15 ExceptionSpecAnalyzer::State
16 ExceptionSpecAnalyzer::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
);
22 // Cache the result of the analysis.
23 FunctionCache
.try_emplace(FuncDecl
, State
);
27 return CacheEntry
->getSecond();
30 ExceptionSpecAnalyzer::State
31 ExceptionSpecAnalyzer::analyzeUnresolvedOrDefaulted(
32 const CXXMethodDecl
*MethodDecl
, const FunctionProtoType
*FuncProto
) {
33 if (!FuncProto
|| !MethodDecl
)
34 return State::Unknown
;
36 const DefaultableMemberKind Kind
= getDefaultableMemberKind(MethodDecl
);
38 if (Kind
== DefaultableMemberKind::None
)
39 return State::Unknown
;
41 return analyzeRecord(MethodDecl
->getParent(), Kind
, SkipMethods::Yes
);
44 ExceptionSpecAnalyzer::State
45 ExceptionSpecAnalyzer::analyzeFieldDecl(const FieldDecl
*FDecl
,
46 DefaultableMemberKind Kind
) {
48 return State::Unknown
;
50 if (const CXXRecordDecl
*RecDecl
=
51 FDecl
->getType()->getUnqualifiedDesugaredType()->getAsCXXRecordDecl())
52 return analyzeRecord(RecDecl
, Kind
);
54 // Trivial types do not throw
55 if (FDecl
->getType().isTrivialType(FDecl
->getASTContext()))
56 return State::NotThrowing
;
58 return State::Unknown
;
61 ExceptionSpecAnalyzer::State
62 ExceptionSpecAnalyzer::analyzeBase(const CXXBaseSpecifier
&Base
,
63 DefaultableMemberKind Kind
) {
64 const auto *RecType
= Base
.getType()->getAs
<RecordType
>();
66 return State::Unknown
;
68 const auto *BaseClass
= cast
<CXXRecordDecl
>(RecType
->getDecl());
70 return analyzeRecord(BaseClass
, Kind
);
73 ExceptionSpecAnalyzer::State
74 ExceptionSpecAnalyzer::analyzeRecord(const CXXRecordDecl
*RecordDecl
,
75 DefaultableMemberKind Kind
,
76 SkipMethods SkipMethods
) {
78 return State::Unknown
;
80 // Trivial implies noexcept
81 if (hasTrivialMemberKind(RecordDecl
, Kind
))
82 return State::NotThrowing
;
84 if (SkipMethods
== SkipMethods::No
)
85 for (const auto *MethodDecl
: RecordDecl
->methods())
86 if (getDefaultableMemberKind(MethodDecl
) == Kind
)
87 return analyze(MethodDecl
);
89 for (const auto &BaseSpec
: RecordDecl
->bases()) {
90 State Result
= analyzeBase(BaseSpec
, Kind
);
91 if (Result
== State::Throwing
|| Result
== State::Unknown
)
95 for (const auto &BaseSpec
: RecordDecl
->vbases()) {
96 State Result
= analyzeBase(BaseSpec
, Kind
);
97 if (Result
== State::Throwing
|| Result
== State::Unknown
)
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
)
108 return State::NotThrowing
;
111 ExceptionSpecAnalyzer::State
112 ExceptionSpecAnalyzer::analyzeImpl(const FunctionDecl
*FuncDecl
) {
113 const auto *FuncProto
= FuncDecl
->getType()->getAs
<FunctionProtoType
>();
115 return State::Unknown
;
117 const ExceptionSpecificationType EST
= FuncProto
->getExceptionSpecType();
119 if (EST
== EST_Unevaluated
|| (EST
== EST_None
&& FuncDecl
->isDefaulted()))
120 return analyzeUnresolvedOrDefaulted(cast
<CXXMethodDecl
>(FuncDecl
),
123 return analyzeFunctionEST(FuncDecl
, FuncProto
);
126 ExceptionSpecAnalyzer::State
127 ExceptionSpecAnalyzer::analyzeFunctionEST(const FunctionDecl
*FuncDecl
,
128 const FunctionProtoType
*FuncProto
) {
129 if (!FuncDecl
|| !FuncProto
)
130 return State::Unknown
;
132 if (isUnresolvedExceptionSpec(FuncProto
->getExceptionSpecType()))
133 return State::Unknown
;
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
;
140 switch (FuncProto
->canThrow()) {
142 return State::NotThrowing
;
144 const Expr
*NoexceptExpr
= FuncProto
->getNoexceptExpr();
146 return State::NotThrowing
;
148 // We can't resolve value dependence so just return unknown
149 if (NoexceptExpr
->isValueDependent())
150 return State::Unknown
;
152 // Try to evaluate the expression to a boolean value
154 if (NoexceptExpr
->EvaluateAsBooleanCondition(
155 Result
, FuncDecl
->getASTContext(), true))
156 return Result
? State::NotThrowing
: State::Throwing
;
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
;
163 return State::Throwing
;
167 bool ExceptionSpecAnalyzer::hasTrivialMemberKind(const CXXRecordDecl
*RecDecl
,
168 DefaultableMemberKind 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();
191 bool ExceptionSpecAnalyzer::isConstructor(DefaultableMemberKind Kind
) {
193 case DefaultableMemberKind::DefaultConstructor
:
194 case DefaultableMemberKind::CopyConstructor
:
195 case DefaultableMemberKind::MoveConstructor
:
203 bool ExceptionSpecAnalyzer::isSpecialMember(DefaultableMemberKind 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
:
217 bool ExceptionSpecAnalyzer::isComparison(DefaultableMemberKind Kind
) {
219 case DefaultableMemberKind::CompareEqual
:
220 case DefaultableMemberKind::CompareNotEqual
:
221 case DefaultableMemberKind::CompareRelational
:
222 case DefaultableMemberKind::CompareThreeWay
:
229 ExceptionSpecAnalyzer::DefaultableMemberKind
230 ExceptionSpecAnalyzer::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
;
236 if (Ctor
->isCopyConstructor())
237 return DefaultableMemberKind::CopyConstructor
;
239 if (Ctor
->isMoveConstructor())
240 return DefaultableMemberKind::MoveConstructor
;
243 if (MethodDecl
->isCopyAssignmentOperator())
244 return DefaultableMemberKind::CopyAssignment
;
246 if (MethodDecl
->isMoveAssignmentOperator())
247 return DefaultableMemberKind::MoveAssignment
;
249 if (isa
<CXXDestructorDecl
>(FuncDecl
))
250 return DefaultableMemberKind::Destructor
;
253 const LangOptions
&LangOpts
= FuncDecl
->getLangOpts();
255 switch (FuncDecl
->getDeclName().getCXXOverloadedOperator()) {
257 return DefaultableMemberKind::CompareEqual
;
259 case OO_ExclaimEqual
:
260 return DefaultableMemberKind::CompareNotEqual
;
263 // No point allowing this if <=> doesn't exist in the current language mode.
264 if (!LangOpts
.CPlusPlus20
)
266 return DefaultableMemberKind::CompareThreeWay
;
271 case OO_GreaterEqual
:
272 // No point allowing this if <=> doesn't exist in the current language mode.
273 if (!LangOpts
.CPlusPlus20
)
275 return DefaultableMemberKind::CompareRelational
;
281 // Not a defaultable member kind
282 return DefaultableMemberKind::None
;
285 } // namespace clang::tidy::utils