xref: /llvm-project/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp (revision 3d56ea05b6c746a7144f643bef2ebd599f605b8b)
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
analyze(const FunctionDecl * FuncDecl)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);
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 
30 ExceptionSpecAnalyzer::State
analyzeUnresolvedOrDefaulted(const CXXMethodDecl * MethodDecl,const FunctionProtoType * FuncProto)31 ExceptionSpecAnalyzer::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 
44 ExceptionSpecAnalyzer::State
analyzeFieldDecl(const FieldDecl * FDecl,DefaultableMemberKind Kind)45 ExceptionSpecAnalyzer::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 
61 ExceptionSpecAnalyzer::State
analyzeBase(const CXXBaseSpecifier & Base,DefaultableMemberKind Kind)62 ExceptionSpecAnalyzer::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 
73 ExceptionSpecAnalyzer::State
analyzeRecord(const CXXRecordDecl * RecordDecl,DefaultableMemberKind Kind,SkipMethods SkipMethods)74 ExceptionSpecAnalyzer::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 
111 ExceptionSpecAnalyzer::State
analyzeImpl(const FunctionDecl * FuncDecl)112 ExceptionSpecAnalyzer::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 
126 ExceptionSpecAnalyzer::State
analyzeFunctionEST(const FunctionDecl * FuncDecl,const FunctionProtoType * FuncProto)127 ExceptionSpecAnalyzer::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 
hasTrivialMemberKind(const CXXRecordDecl * RecDecl,DefaultableMemberKind Kind)167 bool 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 
isConstructor(DefaultableMemberKind Kind)191 bool 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 
isSpecialMember(DefaultableMemberKind Kind)203 bool 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 
isComparison(DefaultableMemberKind Kind)217 bool 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 
229 ExceptionSpecAnalyzer::DefaultableMemberKind
getDefaultableMemberKind(const FunctionDecl * FuncDecl)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;
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
286