clang-tools 20.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 // 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
154bool 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.
194bool 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
322static bool canThrow(const FunctionDecl *Func) {
323 const auto *FunProto = Func->getType()->getAs<FunctionProtoType>();
324 if (!FunProto)
325 return true;
326
327 switch (FunProto->canThrow()) {
328 case CT_Cannot:
329 return false;
330 case CT_Dependent: {
331 const Expr *NoexceptExpr = FunProto->getNoexceptExpr();
332 if (!NoexceptExpr)
333 return true; // no noexept - can throw
334
335 if (NoexceptExpr->isValueDependent())
336 return true; // depend on template - some instance can throw
337
338 bool Result = false;
339 if (!NoexceptExpr->EvaluateAsBooleanCondition(Result, Func->getASTContext(),
340 /*InConstantContext=*/true))
341 return true; // complex X condition in noexcept(X), cannot validate,
342 // assume that may throw
343 return !Result; // noexcept(false) - can throw
344 }
345 default:
346 return true;
347 };
348}
349
351 const Type *HandlerTy, const ASTContext &Context) {
352 llvm::SmallVector<const Type *, 8> TypesToDelete;
353 for (const Type *ExceptionTy : ThrownExceptions) {
354 CanQualType ExceptionCanTy = ExceptionTy->getCanonicalTypeUnqualified();
355 CanQualType HandlerCanTy = HandlerTy->getCanonicalTypeUnqualified();
356
357 // The handler is of type cv T or cv T& and E and T are the same type
358 // (ignoring the top-level cv-qualifiers) ...
359 if (ExceptionCanTy == HandlerCanTy) {
360 TypesToDelete.push_back(ExceptionTy);
361 }
362
363 // The handler is of type cv T or cv T& and T is an unambiguous public base
364 // class of E ...
365 else if (isUnambiguousPublicBaseClass(ExceptionCanTy->getTypePtr(),
366 HandlerCanTy->getTypePtr())) {
367 TypesToDelete.push_back(ExceptionTy);
368 }
369
370 if (HandlerCanTy->getTypeClass() == Type::RValueReference ||
371 (HandlerCanTy->getTypeClass() == Type::LValueReference &&
372 !HandlerCanTy->getTypePtr()->getPointeeType().isConstQualified()))
373 continue;
374 // The handler is of type cv T or const T& where T is a pointer or
375 // pointer-to-member type and E is a pointer or pointer-to-member type that
376 // can be converted to T by one or more of ...
377 if (isPointerOrPointerToMember(HandlerCanTy->getTypePtr()) &&
378 isPointerOrPointerToMember(ExceptionCanTy->getTypePtr())) {
379 // A standard pointer conversion not involving conversions to pointers to
380 // private or protected or ambiguous classes ...
381 if (isStandardPointerConvertible(ExceptionCanTy, HandlerCanTy)) {
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)