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
11namespace clang::tidy::utils {
12
13void ExceptionAnalyzer::ExceptionInfo::registerException(
14 const Type *ExceptionType) {
15 assert(ExceptionType != nullptr && "Only valid types are accepted");
16 Behaviour = State::Throwing;
17 ThrownExceptions.insert(Ptr: ExceptionType);
18}
19
20void ExceptionAnalyzer::ExceptionInfo::registerExceptions(
21 const Throwables &Exceptions) {
22 if (Exceptions.empty())
23 return;
24 Behaviour = State::Throwing;
25 ThrownExceptions.insert(I: Exceptions.begin(), E: Exceptions.end());
26}
27
28ExceptionAnalyzer::ExceptionInfo &ExceptionAnalyzer::ExceptionInfo::merge(
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(I: Other.ThrownExceptions.begin(),
43 E: Other.ThrownExceptions.end());
44 return *this;
45}
46
47// FIXME: This could be ported to clang later.
48namespace {
49
50bool isUnambiguousPublicBaseClass(const Type *DerivedType,
51 const Type *BaseType) {
52 const auto *DerivedClass =
53 DerivedType->getCanonicalTypeUnqualified()->getAsCXXRecordDecl();
54 const auto *BaseClass =
55 BaseType->getCanonicalTypeUnqualified()->getAsCXXRecordDecl();
56 if (!DerivedClass || !BaseClass)
57 return false;
58
59 CXXBasePaths Paths;
60 Paths.setOrigin(DerivedClass);
61
62 bool IsPublicBaseClass = false;
63 DerivedClass->lookupInBases(
64 [&BaseClass, &IsPublicBaseClass](const CXXBaseSpecifier *BS,
65 CXXBasePath &) {
66 if (BS->getType()
67 ->getCanonicalTypeUnqualified()
68 ->getAsCXXRecordDecl() == BaseClass &&
69 BS->getAccessSpecifier() == AS_public) {
70 IsPublicBaseClass = true;
71 return true;
72 }
73
74 return false;
75 },
76 Paths);
77
78 return !Paths.isAmbiguous(BaseType: BaseType->getCanonicalTypeUnqualified()) &&
79 IsPublicBaseClass;
80}
81
82inline bool isPointerOrPointerToMember(const Type *T) {
83 return T->isPointerType() || T->isMemberPointerType();
84}
85
86std::optional<QualType> getPointeeOrArrayElementQualType(QualType T) {
87 if (T->isAnyPointerType() || T->isMemberPointerType())
88 return T->getPointeeType();
89
90 if (T->isArrayType())
91 return T->getAsArrayTypeUnsafe()->getElementType();
92
93 return std::nullopt;
94}
95
96bool isBaseOf(const Type *DerivedType, const Type *BaseType) {
97 const auto *DerivedClass = DerivedType->getAsCXXRecordDecl();
98 const auto *BaseClass = BaseType->getAsCXXRecordDecl();
99 if (!DerivedClass || !BaseClass)
100 return false;
101
102 return !DerivedClass->forallBases(
103 BaseMatches: [BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; });
104}
105
106// Check if T1 is more or Equally qualified than T2.
107bool moreOrEquallyQualified(QualType T1, QualType T2) {
108 return T1.getQualifiers().isStrictSupersetOf(Other: T2.getQualifiers()) ||
109 T1.getQualifiers() == T2.getQualifiers();
110}
111
112bool isStandardPointerConvertible(QualType From, QualType To) {
113 assert((From->isPointerType() || From->isMemberPointerType()) &&
114 (To->isPointerType() || To->isMemberPointerType()) &&
115 "Pointer conversion should be performed on pointer types only.");
116
117 if (!moreOrEquallyQualified(T1: To->getPointeeType(), T2: From->getPointeeType()))
118 return false;
119
120 // (1)
121 // A null pointer constant can be converted to a pointer type ...
122 // The conversion of a null pointer constant to a pointer to cv-qualified type
123 // is a single conversion, and not the sequence of a pointer conversion
124 // followed by a qualification conversion. A null pointer constant of integral
125 // type can be converted to a prvalue of type std::nullptr_t
126 if (To->isPointerType() && From->isNullPtrType())
127 return true;
128
129 // (2)
130 // A prvalue of type “pointer to cv T”, where T is an object type, can be
131 // converted to a prvalue of type “pointer to cv void”.
132 if (To->isVoidPointerType() && From->isObjectPointerType())
133 return true;
134
135 // (3)
136 // A prvalue of type “pointer to cv D”, where D is a complete class type, can
137 // be converted to a prvalue of type “pointer to cv B”, where B is a base
138 // class of D. If B is an inaccessible or ambiguous base class of D, a program
139 // that necessitates this conversion is ill-formed.
140 if (const auto *RD = From->getPointeeCXXRecordDecl()) {
141 if (RD->isCompleteDefinition() &&
142 isBaseOf(DerivedType: From->getPointeeType().getTypePtr(),
143 BaseType: To->getPointeeType().getTypePtr())) {
144 return true;
145 }
146 }
147
148 return false;
149}
150
151bool isFunctionPointerConvertible(QualType From, QualType To) {
152 if (!From->isFunctionPointerType() && !From->isFunctionType() &&
153 !From->isMemberFunctionPointerType())
154 return false;
155
156 if (!To->isFunctionPointerType() && !To->isMemberFunctionPointerType())
157 return false;
158
159 if (To->isFunctionPointerType()) {
160 if (From->isFunctionPointerType())
161 return To->getPointeeType() == From->getPointeeType();
162
163 if (From->isFunctionType())
164 return To->getPointeeType() == From;
165
166 return false;
167 }
168
169 if (To->isMemberFunctionPointerType()) {
170 if (!From->isMemberFunctionPointerType())
171 return false;
172
173 const auto *FromMember = cast<MemberPointerType>(Val&: From);
174 const auto *ToMember = cast<MemberPointerType>(Val&: To);
175
176 // Note: converting Derived::* to Base::* is a different kind of conversion,
177 // called Pointer-to-member conversion.
178 return FromMember->getClass() == ToMember->getClass() &&
179 FromMember->getPointeeType() == ToMember->getPointeeType();
180 }
181
182 return false;
183}
184
185// Checks if From is qualification convertible to To based on the current
186// LangOpts. If From is any array, we perform the array to pointer conversion
187// first. The function only performs checks based on C++ rules, which can differ
188// from the C rules.
189//
190// The function should only be called in C++ mode.
191bool isQualificationConvertiblePointer(QualType From, QualType To,
192 LangOptions LangOpts) {
193
194 // [N4659 7.5 (1)]
195 // A cv-decomposition of a type T is a sequence of cv_i and P_i such that T is
196 // cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U” for n > 0,
197 // where each cv_i is a set of cv-qualifiers, and each P_i is “pointer to”,
198 // “pointer to member of class C_i of type”, “array of N_i”, or
199 // “array of unknown bound of”.
200 //
201 // If P_i designates an array, the cv-qualifiers cv_i+1 on the element type
202 // are also taken as the cv-qualifiers cvi of the array.
203 //
204 // The n-tuple of cv-qualifiers after the first one in the longest
205 // cv-decomposition of T, that is, cv_1, cv_2, ... , cv_n, is called the
206 // cv-qualification signature of T.
207
208 auto isValidP_i = [](QualType P) {
209 return P->isPointerType() || P->isMemberPointerType() ||
210 P->isConstantArrayType() || P->isIncompleteArrayType();
211 };
212
213 auto isSameP_i = [](QualType P1, QualType P2) {
214 if (P1->isPointerType())
215 return P2->isPointerType();
216
217 if (P1->isMemberPointerType())
218 return P2->isMemberPointerType() &&
219 P1->getAs<MemberPointerType>()->getClass() ==
220 P2->getAs<MemberPointerType>()->getClass();
221
222 if (P1->isConstantArrayType())
223 return P2->isConstantArrayType() &&
224 cast<ConstantArrayType>(Val&: P1)->getSize() ==
225 cast<ConstantArrayType>(Val&: P2)->getSize();
226
227 if (P1->isIncompleteArrayType())
228 return P2->isIncompleteArrayType();
229
230 return false;
231 };
232
233 // (2)
234 // Two types From and To are similar if they have cv-decompositions with the
235 // same n such that corresponding P_i components are the same [(added by
236 // N4849 7.3.5) or one is “array of N_i” and the other is “array of unknown
237 // bound of”], and the types denoted by U are the same.
238 //
239 // (3)
240 // A prvalue expression of type From can be converted to type To if the
241 // following conditions are satisfied:
242 // - From and To are similar
243 // - For every i > 0, if const is in cv_i of From then const is in cv_i of
244 // To, and similarly for volatile.
245 // - [(derived from addition by N4849 7.3.5) If P_i of From is “array of
246 // unknown bound of”, P_i of To is “array of unknown bound of”.]
247 // - If the cv_i of From and cv_i of To are different, then const is in every
248 // cv_k of To for 0 < k < i.
249
250 int I = 0;
251 bool ConstUntilI = true;
252 auto SatisfiesCVRules = [&I, &ConstUntilI](const QualType &From,
253 const QualType &To) {
254 if (I > 1) {
255 if (From.getQualifiers() != To.getQualifiers() && !ConstUntilI)
256 return false;
257 }
258
259 if (I > 0) {
260 if (From.isConstQualified() && !To.isConstQualified())
261 return false;
262
263 if (From.isVolatileQualified() && !To.isVolatileQualified())
264 return false;
265
266 ConstUntilI = To.isConstQualified();
267 }
268
269 return true;
270 };
271
272 while (isValidP_i(From) && isValidP_i(To)) {
273 // Remove every sugar.
274 From = From.getCanonicalType();
275 To = To.getCanonicalType();
276
277 if (!SatisfiesCVRules(From, To))
278 return false;
279
280 if (!isSameP_i(From, To)) {
281 if (LangOpts.CPlusPlus20) {
282 if (From->isConstantArrayType() && !To->isIncompleteArrayType())
283 return false;
284
285 if (From->isIncompleteArrayType() && !To->isIncompleteArrayType())
286 return false;
287
288 } else {
289 return false;
290 }
291 }
292
293 ++I;
294 std::optional<QualType> FromPointeeOrElem =
295 getPointeeOrArrayElementQualType(From);
296 std::optional<QualType> ToPointeeOrElem =
297 getPointeeOrArrayElementQualType(To);
298
299 assert(FromPointeeOrElem &&
300 "From pointer or array has no pointee or element!");
301 assert(ToPointeeOrElem && "To pointer or array has no pointee or element!");
302
303 From = *FromPointeeOrElem;
304 To = *ToPointeeOrElem;
305 }
306
307 // In this case the length (n) of From and To are not the same.
308 if (isValidP_i(From) || isValidP_i(To))
309 return false;
310
311 // We hit U.
312 if (!SatisfiesCVRules(From, To))
313 return false;
314
315 return From.getTypePtr() == To.getTypePtr();
316}
317} // namespace
318
319static bool canThrow(const FunctionDecl *Func) {
320 const auto *FunProto = Func->getType()->getAs<FunctionProtoType>();
321 if (!FunProto)
322 return true;
323
324 switch (FunProto->canThrow()) {
325 case CT_Cannot:
326 return false;
327 case CT_Dependent: {
328 const Expr *NoexceptExpr = FunProto->getNoexceptExpr();
329 if (!NoexceptExpr)
330 return true; // no noexept - can throw
331
332 if (NoexceptExpr->isValueDependent())
333 return true; // depend on template - some instance can throw
334
335 bool Result = false;
336 if (!NoexceptExpr->EvaluateAsBooleanCondition(Result, Ctx: Func->getASTContext(),
337 /*InConstantContext=*/true))
338 return true; // complex X condition in noexcept(X), cannot validate,
339 // assume that may throw
340 return !Result; // noexcept(false) - can throw
341 }
342 default:
343 return true;
344 };
345}
346
347bool ExceptionAnalyzer::ExceptionInfo::filterByCatch(
348 const Type *HandlerTy, const ASTContext &Context) {
349 llvm::SmallVector<const Type *, 8> TypesToDelete;
350 for (const Type *ExceptionTy : ThrownExceptions) {
351 CanQualType ExceptionCanTy = ExceptionTy->getCanonicalTypeUnqualified();
352 CanQualType HandlerCanTy = HandlerTy->getCanonicalTypeUnqualified();
353
354 // The handler is of type cv T or cv T& and E and T are the same type
355 // (ignoring the top-level cv-qualifiers) ...
356 if (ExceptionCanTy == HandlerCanTy) {
357 TypesToDelete.push_back(Elt: ExceptionTy);
358 }
359
360 // The handler is of type cv T or cv T& and T is an unambiguous public base
361 // class of E ...
362 else if (isUnambiguousPublicBaseClass(ExceptionCanTy->getTypePtr(),
363 HandlerCanTy->getTypePtr())) {
364 TypesToDelete.push_back(Elt: ExceptionTy);
365 }
366
367 if (HandlerCanTy->getTypeClass() == Type::RValueReference ||
368 (HandlerCanTy->getTypeClass() == Type::LValueReference &&
369 !HandlerCanTy->getTypePtr()->getPointeeType().isConstQualified()))
370 continue;
371 // The handler is of type cv T or const T& where T is a pointer or
372 // pointer-to-member type and E is a pointer or pointer-to-member type that
373 // can be converted to T by one or more of ...
374 if (isPointerOrPointerToMember(HandlerCanTy->getTypePtr()) &&
375 isPointerOrPointerToMember(ExceptionCanTy->getTypePtr())) {
376 // A standard pointer conversion not involving conversions to pointers to
377 // private or protected or ambiguous classes ...
378 if (isStandardPointerConvertible(ExceptionCanTy, HandlerCanTy) &&
379 isUnambiguousPublicBaseClass(
380 ExceptionCanTy->getTypePtr()->getPointeeType().getTypePtr(),
381 HandlerCanTy->getTypePtr()->getPointeeType().getTypePtr())) {
382 TypesToDelete.push_back(Elt: ExceptionTy);
383 }
384 // A function pointer conversion ...
385 else if (isFunctionPointerConvertible(From: ExceptionCanTy, To: HandlerCanTy)) {
386 TypesToDelete.push_back(Elt: ExceptionTy);
387 }
388 // A a qualification conversion ...
389 else if (isQualificationConvertiblePointer(From: ExceptionCanTy, To: HandlerCanTy,
390 LangOpts: Context.getLangOpts())) {
391 TypesToDelete.push_back(Elt: ExceptionTy);
392 }
393 }
394
395 // The handler is of type cv T or const T& where T is a pointer or
396 // pointer-to-member type and E is std::nullptr_t.
397 else if (isPointerOrPointerToMember(HandlerCanTy->getTypePtr()) &&
398 ExceptionCanTy->isNullPtrType()) {
399 TypesToDelete.push_back(Elt: ExceptionTy);
400 }
401 }
402
403 for (const Type *T : TypesToDelete)
404 ThrownExceptions.erase(Ptr: T);
405
406 reevaluateBehaviour();
407 return !TypesToDelete.empty();
408}
409
410ExceptionAnalyzer::ExceptionInfo &
411ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
412 const llvm::StringSet<> &IgnoredTypes, bool IgnoreBadAlloc) {
413 llvm::SmallVector<const Type *, 8> TypesToDelete;
414 // Note: Using a 'SmallSet' with 'llvm::remove_if()' is not possible.
415 // Therefore this slightly hacky implementation is required.
416 for (const Type *T : ThrownExceptions) {
417 if (const auto *TD = T->getAsTagDecl()) {
418 if (TD->getDeclName().isIdentifier()) {
419 if ((IgnoreBadAlloc &&
420 (TD->getName() == "bad_alloc" && TD->isInStdNamespace())) ||
421 (IgnoredTypes.count(TD->getName()) > 0))
422 TypesToDelete.push_back(Elt: T);
423 }
424 }
425 }
426 for (const Type *T : TypesToDelete)
427 ThrownExceptions.erase(Ptr: T);
428
429 reevaluateBehaviour();
430 return *this;
431}
432
433void ExceptionAnalyzer::ExceptionInfo::clear() {
434 Behaviour = State::NotThrowing;
435 ContainsUnknown = false;
436 ThrownExceptions.clear();
437}
438
439void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
440 if (ThrownExceptions.empty())
441 if (ContainsUnknown)
442 Behaviour = State::Unknown;
443 else
444 Behaviour = State::NotThrowing;
445 else
446 Behaviour = State::Throwing;
447}
448
449ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
450 const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught,
451 llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
452 if (!Func || CallStack.count(Ptr: Func) || (!CallStack.empty() && !canThrow(Func)))
453 return ExceptionInfo::createNonThrowing();
454
455 if (const Stmt *Body = Func->getBody()) {
456 CallStack.insert(Ptr: Func);
457 ExceptionInfo Result = throwsException(St: Body, Caught, CallStack);
458
459 // For a constructor, we also have to check the initializers.
460 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Val: Func)) {
461 for (const CXXCtorInitializer *Init : Ctor->inits()) {
462 ExceptionInfo Excs =
463 throwsException(Init->getInit(), Caught, CallStack);
464 Result.merge(Other: Excs);
465 }
466 }
467
468 CallStack.erase(Ptr: Func);
469 return Result;
470 }
471
472 auto Result = ExceptionInfo::createUnknown();
473 if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
474 for (const QualType &Ex : FPT->exceptions())
475 Result.registerException(Ex.getTypePtr());
476 }
477 return Result;
478}
479
480/// Analyzes a single statement on it's throwing behaviour. This is in principle
481/// possible except some 'Unknown' functions are called.
482ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
483 const Stmt *St, const ExceptionInfo::Throwables &Caught,
484 llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
485 auto Results = ExceptionInfo::createNonThrowing();
486 if (!St)
487 return Results;
488
489 if (const auto *Throw = dyn_cast<CXXThrowExpr>(Val: St)) {
490 if (const auto *ThrownExpr = Throw->getSubExpr()) {
491 const auto *ThrownType =
492 ThrownExpr->getType()->getUnqualifiedDesugaredType();
493 if (ThrownType->isReferenceType())
494 ThrownType = ThrownType->castAs<ReferenceType>()
495 ->getPointeeType()
496 ->getUnqualifiedDesugaredType();
497 Results.registerException(
498 ExceptionType: ThrownExpr->getType()->getUnqualifiedDesugaredType());
499 } else
500 // A rethrow of a caught exception happens which makes it possible
501 // to throw all exception that are caught in the 'catch' clause of
502 // the parent try-catch block.
503 Results.registerExceptions(Exceptions: Caught);
504 } else if (const auto *Try = dyn_cast<CXXTryStmt>(Val: St)) {
505 ExceptionInfo Uncaught =
506 throwsException(Try->getTryBlock(), Caught, CallStack);
507 for (unsigned I = 0; I < Try->getNumHandlers(); ++I) {
508 const CXXCatchStmt *Catch = Try->getHandler(i: I);
509
510 // Everything is catched through 'catch(...)'.
511 if (!Catch->getExceptionDecl()) {
512 ExceptionInfo Rethrown = throwsException(
513 St: Catch->getHandlerBlock(), Caught: Uncaught.getExceptionTypes(), CallStack);
514 Results.merge(Other: Rethrown);
515 Uncaught.clear();
516 } else {
517 const auto *CaughtType =
518 Catch->getCaughtType()->getUnqualifiedDesugaredType();
519 if (CaughtType->isReferenceType()) {
520 CaughtType = CaughtType->castAs<ReferenceType>()
521 ->getPointeeType()
522 ->getUnqualifiedDesugaredType();
523 }
524
525 // If the caught exception will catch multiple previously potential
526 // thrown types (because it's sensitive to inheritance) the throwing
527 // situation changes. First of all filter the exception types and
528 // analyze if the baseclass-exception is rethrown.
529 if (Uncaught.filterByCatch(
530 HandlerTy: CaughtType, Context: Catch->getExceptionDecl()->getASTContext())) {
531 ExceptionInfo::Throwables CaughtExceptions;
532 CaughtExceptions.insert(Ptr: CaughtType);
533 ExceptionInfo Rethrown = throwsException(St: Catch->getHandlerBlock(),
534 Caught: CaughtExceptions, CallStack);
535 Results.merge(Other: Rethrown);
536 }
537 }
538 }
539 Results.merge(Other: Uncaught);
540 } else if (const auto *Call = dyn_cast<CallExpr>(Val: St)) {
541 if (const FunctionDecl *Func = Call->getDirectCallee()) {
542 ExceptionInfo Excs = throwsException(Func, Caught, CallStack);
543 Results.merge(Other: Excs);
544 }
545 } else if (const auto *Construct = dyn_cast<CXXConstructExpr>(Val: St)) {
546 ExceptionInfo Excs =
547 throwsException(Construct->getConstructor(), Caught, CallStack);
548 Results.merge(Other: Excs);
549 } else if (const auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(Val: St)) {
550 ExceptionInfo Excs =
551 throwsException(DefaultInit->getExpr(), Caught, CallStack);
552 Results.merge(Other: Excs);
553 } else if (const auto *Coro = dyn_cast<CoroutineBodyStmt>(Val: St)) {
554 for (const Stmt *Child : Coro->childrenExclBody()) {
555 if (Child != Coro->getExceptionHandler()) {
556 ExceptionInfo Excs = throwsException(St: Child, Caught, CallStack);
557 Results.merge(Other: Excs);
558 }
559 }
560 ExceptionInfo Excs = throwsException(Coro->getBody(), Caught, CallStack);
561 Results.merge(Other: throwsException(St: Coro->getExceptionHandler(),
562 Caught: Excs.getExceptionTypes(), CallStack));
563 for (const Type *Throwable : Excs.getExceptionTypes()) {
564 if (const auto ThrowableRec = Throwable->getAsCXXRecordDecl()) {
565 ExceptionInfo DestructorExcs =
566 throwsException(ThrowableRec->getDestructor(), Caught, CallStack);
567 Results.merge(DestructorExcs);
568 }
569 }
570 } else {
571 for (const Stmt *Child : St->children()) {
572 ExceptionInfo Excs = throwsException(St: Child, Caught, CallStack);
573 Results.merge(Other: Excs);
574 }
575 }
576 return Results;
577}
578
579ExceptionAnalyzer::ExceptionInfo
580ExceptionAnalyzer::analyzeImpl(const FunctionDecl *Func) {
581 ExceptionInfo ExceptionList;
582
583 // Check if the function has already been analyzed and reuse that result.
584 const auto CacheEntry = FunctionCache.find(Val: Func);
585 if (CacheEntry == FunctionCache.end()) {
586 llvm::SmallSet<const FunctionDecl *, 32> CallStack;
587 ExceptionList =
588 throwsException(Func, Caught: ExceptionInfo::Throwables(), CallStack);
589
590 // Cache the result of the analysis. This is done prior to filtering
591 // because it is best to keep as much information as possible.
592 // The results here might be relevant to different analysis passes
593 // with different needs as well.
594 FunctionCache.try_emplace(Key: Func, Args&: ExceptionList);
595 } else
596 ExceptionList = CacheEntry->getSecond();
597
598 return ExceptionList;
599}
600
601ExceptionAnalyzer::ExceptionInfo
602ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) {
603 llvm::SmallSet<const FunctionDecl *, 32> CallStack;
604 return throwsException(St: Stmt, Caught: ExceptionInfo::Throwables(), CallStack);
605}
606
607template <typename T>
608ExceptionAnalyzer::ExceptionInfo
609ExceptionAnalyzer::analyzeDispatch(const T *Node) {
610 ExceptionInfo ExceptionList = analyzeImpl(Node);
611
612 if (ExceptionList.getBehaviour() == State::NotThrowing ||
613 ExceptionList.getBehaviour() == State::Unknown)
614 return ExceptionList;
615
616 // Remove all ignored exceptions from the list of exceptions that can be
617 // thrown.
618 ExceptionList.filterIgnoredExceptions(IgnoredTypes: IgnoredExceptions, IgnoreBadAlloc);
619
620 return ExceptionList;
621}
622
623ExceptionAnalyzer::ExceptionInfo
624ExceptionAnalyzer::analyze(const FunctionDecl *Func) {
625 return analyzeDispatch(Node: Func);
626}
627
628ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::analyze(const Stmt *Stmt) {
629 return analyzeDispatch(Node: Stmt);
630}
631
632} // namespace clang::tidy::utils
633

source code of clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp