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 
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.
throwsException(const Stmt * St,const ExceptionInfo::Throwables & Caught,llvm::SmallSet<const FunctionDecl *,32> & CallStack)146 ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
147     const Stmt *St, const ExceptionInfo::Throwables &Caught,
148     llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
149   auto Results = ExceptionInfo::createNonThrowing();
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
analyzeImpl(const FunctionDecl * Func)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
analyzeImpl(const Stmt * Stmt)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
analyzeDispatch(const T * Node)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
analyze(const FunctionDecl * Func)268 ExceptionAnalyzer::analyze(const FunctionDecl *Func) {
269   return analyzeDispatch(Func);
270 }
271 
272 ExceptionAnalyzer::ExceptionInfo
analyze(const Stmt * Stmt)273 ExceptionAnalyzer::analyze(const Stmt *Stmt) {
274   return analyzeDispatch(Stmt);
275 }
276 
277 } // namespace utils
278 } // namespace tidy
279 
280 } // namespace clang
281