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