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
registerException(const Type * ExceptionType)15 void ExceptionAnalyzer::ExceptionInfo::registerException(
16 const Type *ExceptionType) {
17 assert(ExceptionType != nullptr && "Only valid types are accepted");
18 Behaviour = State::Throwing;
19 ThrownExceptions.insert(ExceptionType);
20 }
21
registerExceptions(const Throwables & Exceptions)22 void ExceptionAnalyzer::ExceptionInfo::registerExceptions(
23 const Throwables &Exceptions) {
24 if (Exceptions.size() == 0)
25 return;
26 Behaviour = State::Throwing;
27 ThrownExceptions.insert(Exceptions.begin(), Exceptions.end());
28 }
29
merge(const ExceptionAnalyzer::ExceptionInfo & Other)30 ExceptionAnalyzer::ExceptionInfo &ExceptionAnalyzer::ExceptionInfo::merge(
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
isBaseOf(const Type * DerivedType,const Type * BaseType)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
filterByCatch(const Type * BaseClass)59 bool ExceptionAnalyzer::ExceptionInfo::filterByCatch(const Type *BaseClass) {
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
73 ExceptionAnalyzer::ExceptionInfo &
filterIgnoredExceptions(const llvm::StringSet<> & IgnoredTypes,bool IgnoreBadAlloc)74 ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
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
clear()96 void ExceptionAnalyzer::ExceptionInfo::clear() {
97 Behaviour = State::NotThrowing;
98 ContainsUnknown = false;
99 ThrownExceptions.clear();
100 }
101
reevaluateBehaviour()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
throwsException(const FunctionDecl * Func,llvm::SmallSet<const FunctionDecl *,32> & CallStack)112 ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
113 const FunctionDecl *Func,
114 llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
115 if (CallStack.count(Func))
116 return ExceptionInfo::createNonThrowing();
117
118 if (const Stmt *Body = Func->getBody()) {
119 CallStack.insert(Func);
120 ExceptionInfo Result =
121 throwsException(Body, ExceptionInfo::Throwables(), CallStack);
122 CallStack.erase(Func);
123 return Result;
124 }
125
126 auto Result = ExceptionInfo::createUnknown();
127 if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
128 for (const QualType &Ex : FPT->exceptions())
129 Result.registerException(Ex.getTypePtr());
130 }
131 return Result;
132 }
133
134 /// Analyzes a single statement on it's throwing behaviour. This is in principle
135 /// possible except some 'Unknown' functions are called.
throwsException(const Stmt * St,const ExceptionInfo::Throwables & Caught,llvm::SmallSet<const FunctionDecl *,32> & CallStack)136 ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
137 const Stmt *St, const ExceptionInfo::Throwables &Caught,
138 llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
139 auto Results = ExceptionInfo::createNonThrowing();
140 if (!St)
141 return Results;
142
143 if (const auto *Throw = dyn_cast<CXXThrowExpr>(St)) {
144 if (const auto *ThrownExpr = Throw->getSubExpr()) {
145 const auto *ThrownType =
146 ThrownExpr->getType()->getUnqualifiedDesugaredType();
147 if (ThrownType->isReferenceType())
148 ThrownType = ThrownType->castAs<ReferenceType>()
149 ->getPointeeType()
150 ->getUnqualifiedDesugaredType();
151 Results.registerException(
152 ThrownExpr->getType()->getUnqualifiedDesugaredType());
153 } else
154 // A rethrow of a caught exception happens which makes it possible
155 // to throw all exception that are caught in the 'catch' clause of
156 // the parent try-catch block.
157 Results.registerExceptions(Caught);
158 } else if (const auto *Try = dyn_cast<CXXTryStmt>(St)) {
159 ExceptionInfo Uncaught =
160 throwsException(Try->getTryBlock(), Caught, CallStack);
161 for (unsigned i = 0; i < Try->getNumHandlers(); ++i) {
162 const CXXCatchStmt *Catch = Try->getHandler(i);
163
164 // Everything is catched through 'catch(...)'.
165 if (!Catch->getExceptionDecl()) {
166 ExceptionInfo Rethrown = throwsException(
167 Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
168 Results.merge(Rethrown);
169 Uncaught.clear();
170 } else {
171 const auto *CaughtType =
172 Catch->getCaughtType()->getUnqualifiedDesugaredType();
173 if (CaughtType->isReferenceType()) {
174 CaughtType = CaughtType->castAs<ReferenceType>()
175 ->getPointeeType()
176 ->getUnqualifiedDesugaredType();
177 }
178
179 // If the caught exception will catch multiple previously potential
180 // thrown types (because it's sensitive to inheritance) the throwing
181 // situation changes. First of all filter the exception types and
182 // analyze if the baseclass-exception is rethrown.
183 if (Uncaught.filterByCatch(CaughtType)) {
184 ExceptionInfo::Throwables CaughtExceptions;
185 CaughtExceptions.insert(CaughtType);
186 ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(),
187 CaughtExceptions, CallStack);
188 Results.merge(Rethrown);
189 }
190 }
191 }
192 Results.merge(Uncaught);
193 } else if (const auto *Call = dyn_cast<CallExpr>(St)) {
194 if (const FunctionDecl *Func = Call->getDirectCallee()) {
195 ExceptionInfo Excs = throwsException(Func, CallStack);
196 Results.merge(Excs);
197 }
198 } else {
199 for (const Stmt *Child : St->children()) {
200 ExceptionInfo Excs = throwsException(Child, Caught, CallStack);
201 Results.merge(Excs);
202 }
203 }
204 return Results;
205 }
206
207 ExceptionAnalyzer::ExceptionInfo
analyzeImpl(const FunctionDecl * Func)208 ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) {
209 ExceptionInfo ExceptionList;
210
211 // Check if the function has already been analyzed and reuse that result.
212 if (FunctionCache.count(Func) == 0) {
213 llvm::SmallSet<const FunctionDecl *, 32> CallStack;
214 ExceptionList = throwsException(Func, CallStack);
215
216 // Cache the result of the analysis. This is done prior to filtering
217 // because it is best to keep as much information as possible.
218 // The results here might be relevant to different analysis passes
219 // with different needs as well.
220 FunctionCache.insert(std::make_pair(Func, ExceptionList));
221 } else
222 ExceptionList = FunctionCache[Func];
223
224 return ExceptionList;
225 }
226
227 ExceptionAnalyzer::ExceptionInfo
analyzeImpl(const Stmt * Stmt)228 ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) {
229 llvm::SmallSet<const FunctionDecl *, 32> CallStack;
230 return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack);
231 }
232
233 template <typename T>
234 ExceptionAnalyzer::ExceptionInfo
analyzeDispatch(const T * Node)235 ExceptionAnalyzer::analyzeDispatch(const T *Node) {
236 ExceptionInfo ExceptionList = analyzeImpl(Node);
237
238 if (ExceptionList.getBehaviour() == State::NotThrowing ||
239 ExceptionList.getBehaviour() == State::Unknown)
240 return ExceptionList;
241
242 // Remove all ignored exceptions from the list of exceptions that can be
243 // thrown.
244 ExceptionList.filterIgnoredExceptions(IgnoredExceptions, IgnoreBadAlloc);
245
246 return ExceptionList;
247 }
248
249 ExceptionAnalyzer::ExceptionInfo
analyze(const FunctionDecl * Func)250 ExceptionAnalyzer::analyze(const FunctionDecl *Func) {
251 return analyzeDispatch(Func);
252 }
253
254 ExceptionAnalyzer::ExceptionInfo
analyze(const Stmt * Stmt)255 ExceptionAnalyzer::analyze(const Stmt *Stmt) {
256 return analyzeDispatch(Stmt);
257 }
258
259 } // namespace utils
260 } // namespace tidy
261
262 } // namespace clang
263