clang-tools  17.0.0git
ExceptionAnalyzer.cpp
Go to the documentation of this file.
1 //===--- ExceptionAnalyzer.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 "ExceptionAnalyzer.h"
10 
11 namespace clang::tidy::utils {
12 
14  const Type *ExceptionType) {
15  assert(ExceptionType != nullptr && "Only valid types are accepted");
16  Behaviour = State::Throwing;
17  ThrownExceptions.insert(ExceptionType);
18 }
19 
21  const Throwables &Exceptions) {
22  if (Exceptions.size() == 0)
23  return;
24  Behaviour = State::Throwing;
25  ThrownExceptions.insert(Exceptions.begin(), Exceptions.end());
26 }
27 
29  const ExceptionAnalyzer::ExceptionInfo &Other) {
30  // Only the following two cases require an update to the local
31  // 'Behaviour'. If the local entity is already throwing there will be no
32  // change and if the other entity is throwing the merged entity will throw
33  // as well.
34  // If one of both entities is 'Unknown' and the other one does not throw
35  // the merged entity is 'Unknown' as well.
36  if (Other.Behaviour == State::Throwing)
37  Behaviour = State::Throwing;
38  else if (Other.Behaviour == State::Unknown && Behaviour == State::NotThrowing)
39  Behaviour = State::Unknown;
40 
41  ContainsUnknown = ContainsUnknown || Other.ContainsUnknown;
42  ThrownExceptions.insert(Other.ThrownExceptions.begin(),
43  Other.ThrownExceptions.end());
44  return *this;
45 }
46 
47 static bool isBaseOf(const Type *DerivedType, const Type *BaseType) {
48  const auto *DerivedClass = DerivedType->getAsCXXRecordDecl();
49  const auto *BaseClass = BaseType->getAsCXXRecordDecl();
50  if (!DerivedClass || !BaseClass)
51  return false;
52 
53  return !DerivedClass->forallBases(
54  [BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; });
55 }
56 
58  llvm::SmallVector<const Type *, 8> TypesToDelete;
59  for (const Type *T : ThrownExceptions) {
60  if (T == BaseClass || isBaseOf(T, BaseClass))
61  TypesToDelete.push_back(T);
62  }
63 
64  for (const Type *T : TypesToDelete)
65  ThrownExceptions.erase(T);
66 
67  reevaluateBehaviour();
68  return TypesToDelete.size() > 0;
69 }
70 
73  const llvm::StringSet<> &IgnoredTypes, bool IgnoreBadAlloc) {
74  llvm::SmallVector<const Type *, 8> TypesToDelete;
75  // Note: Using a 'SmallSet' with 'llvm::remove_if()' is not possible.
76  // Therefore this slightly hacky implementation is required.
77  for (const Type *T : ThrownExceptions) {
78  if (const auto *TD = T->getAsTagDecl()) {
79  if (TD->getDeclName().isIdentifier()) {
80  if ((IgnoreBadAlloc &&
81  (TD->getName() == "bad_alloc" && TD->isInStdNamespace())) ||
82  (IgnoredTypes.count(TD->getName()) > 0))
83  TypesToDelete.push_back(T);
84  }
85  }
86  }
87  for (const Type *T : TypesToDelete)
88  ThrownExceptions.erase(T);
89 
90  reevaluateBehaviour();
91  return *this;
92 }
93 
95  Behaviour = State::NotThrowing;
96  ContainsUnknown = false;
97  ThrownExceptions.clear();
98 }
99 
100 void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
101  if (ThrownExceptions.size() == 0)
102  if (ContainsUnknown)
103  Behaviour = State::Unknown;
104  else
105  Behaviour = State::NotThrowing;
106  else
107  Behaviour = State::Throwing;
108 }
109 
110 ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
111  const FunctionDecl *Func,
112  llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
113  if (CallStack.count(Func))
115 
116  if (const Stmt *Body = Func->getBody()) {
117  CallStack.insert(Func);
118  ExceptionInfo Result =
119  throwsException(Body, ExceptionInfo::Throwables(), CallStack);
120 
121  // For a constructor, we also have to check the initializers.
122  if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Func)) {
123  for (const CXXCtorInitializer *Init : Ctor->inits()) {
124  ExceptionInfo Excs = throwsException(
125  Init->getInit(), ExceptionInfo::Throwables(), CallStack);
126  Result.merge(Excs);
127  }
128  }
129 
130  CallStack.erase(Func);
131  return Result;
132  }
133 
134  auto Result = ExceptionInfo::createUnknown();
135  if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
136  for (const QualType &Ex : FPT->exceptions())
137  Result.registerException(Ex.getTypePtr());
138  }
139  return Result;
140 }
141 
142 /// Analyzes a single statement on it's throwing behaviour. This is in principle
143 /// possible except some 'Unknown' functions are called.
144 ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
145  const Stmt *St, const ExceptionInfo::Throwables &Caught,
146  llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
148  if (!St)
149  return Results;
150 
151  if (const auto *Throw = dyn_cast<CXXThrowExpr>(St)) {
152  if (const auto *ThrownExpr = Throw->getSubExpr()) {
153  const auto *ThrownType =
154  ThrownExpr->getType()->getUnqualifiedDesugaredType();
155  if (ThrownType->isReferenceType())
156  ThrownType = ThrownType->castAs<ReferenceType>()
157  ->getPointeeType()
158  ->getUnqualifiedDesugaredType();
159  Results.registerException(
160  ThrownExpr->getType()->getUnqualifiedDesugaredType());
161  } else
162  // A rethrow of a caught exception happens which makes it possible
163  // to throw all exception that are caught in the 'catch' clause of
164  // the parent try-catch block.
165  Results.registerExceptions(Caught);
166  } else if (const auto *Try = dyn_cast<CXXTryStmt>(St)) {
167  ExceptionInfo Uncaught =
168  throwsException(Try->getTryBlock(), Caught, CallStack);
169  for (unsigned I = 0; I < Try->getNumHandlers(); ++I) {
170  const CXXCatchStmt *Catch = Try->getHandler(I);
171 
172  // Everything is catched through 'catch(...)'.
173  if (!Catch->getExceptionDecl()) {
174  ExceptionInfo Rethrown = throwsException(
175  Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
176  Results.merge(Rethrown);
177  Uncaught.clear();
178  } else {
179  const auto *CaughtType =
180  Catch->getCaughtType()->getUnqualifiedDesugaredType();
181  if (CaughtType->isReferenceType()) {
182  CaughtType = CaughtType->castAs<ReferenceType>()
183  ->getPointeeType()
184  ->getUnqualifiedDesugaredType();
185  }
186 
187  // If the caught exception will catch multiple previously potential
188  // thrown types (because it's sensitive to inheritance) the throwing
189  // situation changes. First of all filter the exception types and
190  // analyze if the baseclass-exception is rethrown.
191  if (Uncaught.filterByCatch(CaughtType)) {
192  ExceptionInfo::Throwables CaughtExceptions;
193  CaughtExceptions.insert(CaughtType);
194  ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(),
195  CaughtExceptions, CallStack);
196  Results.merge(Rethrown);
197  }
198  }
199  }
200  Results.merge(Uncaught);
201  } else if (const auto *Call = dyn_cast<CallExpr>(St)) {
202  if (const FunctionDecl *Func = Call->getDirectCallee()) {
203  ExceptionInfo Excs = throwsException(Func, CallStack);
204  Results.merge(Excs);
205  }
206  } else if (const auto *Construct = dyn_cast<CXXConstructExpr>(St)) {
207  ExceptionInfo Excs =
208  throwsException(Construct->getConstructor(), CallStack);
209  Results.merge(Excs);
210  } else if (const auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(St)) {
211  ExceptionInfo Excs =
212  throwsException(DefaultInit->getExpr(), Caught, CallStack);
213  Results.merge(Excs);
214  } else {
215  for (const Stmt *Child : St->children()) {
216  ExceptionInfo Excs = throwsException(Child, Caught, CallStack);
217  Results.merge(Excs);
218  }
219  }
220  return Results;
221 }
222 
223 ExceptionAnalyzer::ExceptionInfo
224 ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) {
225  ExceptionInfo ExceptionList;
226 
227  // Check if the function has already been analyzed and reuse that result.
228  if (FunctionCache.count(Func) == 0) {
229  llvm::SmallSet<const FunctionDecl *, 32> CallStack;
230  ExceptionList = throwsException(Func, CallStack);
231 
232  // Cache the result of the analysis. This is done prior to filtering
233  // because it is best to keep as much information as possible.
234  // The results here might be relevant to different analysis passes
235  // with different needs as well.
236  FunctionCache.insert(std::make_pair(Func, ExceptionList));
237  } else
238  ExceptionList = FunctionCache[Func];
239 
240  return ExceptionList;
241 }
242 
243 ExceptionAnalyzer::ExceptionInfo
244 ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) {
245  llvm::SmallSet<const FunctionDecl *, 32> CallStack;
246  return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack);
247 }
248 
249 template <typename T>
250 ExceptionAnalyzer::ExceptionInfo
251 ExceptionAnalyzer::analyzeDispatch(const T *Node) {
252  ExceptionInfo ExceptionList = analyzeImpl(Node);
253 
254  if (ExceptionList.getBehaviour() == State::NotThrowing ||
255  ExceptionList.getBehaviour() == State::Unknown)
256  return ExceptionList;
257 
258  // Remove all ignored exceptions from the list of exceptions that can be
259  // thrown.
260  ExceptionList.filterIgnoredExceptions(IgnoredExceptions, IgnoreBadAlloc);
261 
262  return ExceptionList;
263 }
264 
265 ExceptionAnalyzer::ExceptionInfo
266 ExceptionAnalyzer::analyze(const FunctionDecl *Func) {
267  return analyzeDispatch(Func);
268 }
269 
271 ExceptionAnalyzer::analyze(const Stmt *Stmt) {
272  return analyzeDispatch(Stmt);
273 }
274 
275 } // namespace clang::tidy::utils
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::merge
ExceptionInfo & merge(const ExceptionInfo &Other)
Updates the local state according to the other state.
Definition: ExceptionAnalyzer.cpp:28
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::clear
void clear()
Clear the state to 'NonThrowing' to make the corresponding entity neutral.
Definition: ExceptionAnalyzer.cpp:94
clang::tidy::utils::isBaseOf
static bool isBaseOf(const Type *DerivedType, const Type *BaseType)
Definition: ExceptionAnalyzer.cpp:47
Type
NodeType Type
Definition: HTMLGenerator.cpp:75
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::createNonThrowing
static ExceptionInfo createNonThrowing()
Definition: ExceptionAnalyzer.h:44
ExceptionAnalyzer.h
clang::tidy::utils::ExceptionAnalyzer::State::NotThrowing
@ NotThrowing
This function can not throw, given an AST.
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::Throwables
llvm::SmallSet< const Type *, 2 > Throwables
Definition: ExceptionAnalyzer.h:40
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::registerException
void registerException(const Type *ExceptionType)
Register a single exception type as recognized potential exception to be thrown.
Definition: ExceptionAnalyzer.cpp:13
Results
std::vector< CodeCompletionResult > Results
Definition: CodeComplete.cpp:819
clang::tidy::utils
Definition: Aliasing.cpp:14
clang::tidy::utils::ExceptionAnalyzer::State::Unknown
@ Unknown
This can happen for extern functions without available definition.
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::registerExceptions
void registerExceptions(const Throwables &Exceptions)
Registers a SmallVector of exception types as recognized potential exceptions to be thrown.
Definition: ExceptionAnalyzer.cpp:20
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions
ExceptionInfo & filterIgnoredExceptions(const llvm::StringSet<> &IgnoredTypes, bool IgnoreBadAlloc)
Filter the set of thrown exception type against a set of ignored types that shall not be considered i...
Definition: ExceptionAnalyzer.cpp:72
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::createUnknown
static ExceptionInfo createUnknown()
Definition: ExceptionAnalyzer.h:41
clang::tidy::utils::ExceptionAnalyzer::State::Throwing
@ Throwing
The function can definitely throw given an AST.
clang::tidy::utils::ExceptionAnalyzer::analyze
ExceptionInfo analyze(const FunctionDecl *Func)
Definition: ExceptionAnalyzer.cpp:266
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo::filterByCatch
bool filterByCatch(const Type *BaseClass)
This method is useful in case 'catch' clauses are analyzed as it is possible to catch multiple except...
Definition: ExceptionAnalyzer.cpp:57
clang::tidy::utils::ExceptionAnalyzer::ExceptionInfo
Bundle the gathered information about an entity like a function regarding it's exception behaviour.
Definition: ExceptionAnalyzer.h:38