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