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