clang-tools 19.0.0git
ExceptionAnalyzer.cpp
Go to the documentation of this file.
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
14 const Type *ExceptionType) {
15 assert(ExceptionType != nullptr && "Only valid types are accepted");
16 Behaviour = State::Throwing;
17 ThrownExceptions.insert(ExceptionType);
18}
19
21 const Throwables &Exceptions) {
22 if (Exceptions.empty())
23 return;
24 Behaviour = State::Throwing;
25 ThrownExceptions.insert(Exceptions.begin(), Exceptions.end());
26}
27
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.
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->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 [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(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(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 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>(From);
174 const auto *ToMember = cast<MemberPointerType>(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>(P1)->getSize() ==
225 cast<ConstantArrayType>(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, 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
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(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(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(ExceptionTy);
383 }
384 // A function pointer conversion ...
385 else if (isFunctionPointerConvertible(ExceptionCanTy, HandlerCanTy)) {
386 TypesToDelete.push_back(ExceptionTy);
387 }
388 // A a qualification conversion ...
389 else if (isQualificationConvertiblePointer(ExceptionCanTy, HandlerCanTy,
390 Context.getLangOpts())) {
391 TypesToDelete.push_back(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(ExceptionTy);
400 }
401 }
402
403 for (const Type *T : TypesToDelete)
404 ThrownExceptions.erase(T);
405
406 reevaluateBehaviour();
407 return !TypesToDelete.empty();
408}
409
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(T);
423 }
424 }
425 }
426 for (const Type *T : TypesToDelete)
427 ThrownExceptions.erase(T);
428
429 reevaluateBehaviour();
430 return *this;
431}
432
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(Func) || (!CallStack.empty() && !canThrow(Func)))
454
455 if (const Stmt *Body = Func->getBody()) {
456 CallStack.insert(Func);
457 ExceptionInfo Result = throwsException(Body, Caught, CallStack);
458
459 // For a constructor, we also have to check the initializers.
460 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Func)) {
461 for (const CXXCtorInitializer *Init : Ctor->inits()) {
462 ExceptionInfo Excs =
463 throwsException(Init->getInit(), Caught, CallStack);
464 Result.merge(Excs);
465 }
466 }
467
468 CallStack.erase(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) {
486 if (!St)
487 return Results;
488
489 if (const auto *Throw = dyn_cast<CXXThrowExpr>(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 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(Caught);
504 } else if (const auto *Try = dyn_cast<CXXTryStmt>(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);
509
510 // Everything is catched through 'catch(...)'.
511 if (!Catch->getExceptionDecl()) {
512 ExceptionInfo Rethrown = throwsException(
513 Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
514 Results.merge(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 CaughtType, Catch->getExceptionDecl()->getASTContext())) {
531 ExceptionInfo::Throwables CaughtExceptions;
532 CaughtExceptions.insert(CaughtType);
533 ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(),
534 CaughtExceptions, CallStack);
535 Results.merge(Rethrown);
536 }
537 }
538 }
539 Results.merge(Uncaught);
540 } else if (const auto *Call = dyn_cast<CallExpr>(St)) {
541 if (const FunctionDecl *Func = Call->getDirectCallee()) {
542 ExceptionInfo Excs = throwsException(Func, Caught, CallStack);
543 Results.merge(Excs);
544 }
545 } else if (const auto *Construct = dyn_cast<CXXConstructExpr>(St)) {
546 ExceptionInfo Excs =
547 throwsException(Construct->getConstructor(), Caught, CallStack);
548 Results.merge(Excs);
549 } else if (const auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(St)) {
550 ExceptionInfo Excs =
551 throwsException(DefaultInit->getExpr(), Caught, CallStack);
552 Results.merge(Excs);
553 } else if (const auto *Coro = dyn_cast<CoroutineBodyStmt>(St)) {
554 for (const Stmt *Child : Coro->childrenExclBody()) {
555 if (Child != Coro->getExceptionHandler()) {
556 ExceptionInfo Excs = throwsException(Child, Caught, CallStack);
557 Results.merge(Excs);
558 }
559 }
560 ExceptionInfo Excs = throwsException(Coro->getBody(), Caught, CallStack);
561 Results.merge(throwsException(Coro->getExceptionHandler(),
562 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(Child, Caught, CallStack);
573 Results.merge(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(Func);
585 if (CacheEntry == FunctionCache.end()) {
586 llvm::SmallSet<const FunctionDecl *, 32> CallStack;
587 ExceptionList =
588 throwsException(Func, 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(Func, 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(Stmt, 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(IgnoredExceptions, IgnoreBadAlloc);
619
620 return ExceptionList;
621}
622
623ExceptionAnalyzer::ExceptionInfo
624ExceptionAnalyzer::analyze(const FunctionDecl *Func) {
625 return analyzeDispatch(Func);
626}
627
629 return analyzeDispatch(Stmt);
630}
631
632} // namespace clang::tidy::utils
std::vector< CodeCompletionResult > Results
NodeType Type
Bundle the gathered information about an entity like a function regarding it's exception behaviour.
bool filterByCatch(const Type *HandlerTy, const ASTContext &Context)
This method is useful in case 'catch' clauses are analyzed as it is possible to catch multiple except...
void clear()
Clear the state to 'NonThrowing' to make the corresponding entity neutral.
void registerException(const Type *ExceptionType)
Register a single exception type as recognized potential exception to be thrown.
ExceptionInfo & filterIgnoredExceptions(const llvm::StringSet<> &IgnoredTypes, bool IgnoreBadAlloc)
Filter the set of thrown exception type against a set of ignored types that shall not be considered i...
void registerExceptions(const Throwables &Exceptions)
Registers a SmallVector of exception types as recognized potential exceptions to be thrown.
ExceptionInfo & merge(const ExceptionInfo &Other)
Updates the local state according to the other state.
@ Throwing
The function can definitely throw given an AST.
@ Unknown
This can happen for extern functions without available definition.
@ NotThrowing
This function can not throw, given an AST.
ExceptionInfo analyze(const FunctionDecl *Func)
static bool canThrow(const FunctionDecl *Func)