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 // 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
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
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
440 Behaviour = State::NotThrowing;
441 ContainsUnknown = false;
442 ThrownExceptions.clear();
443}
444
445void 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
455ExceptionAnalyzer::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)))
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.
489ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
490 const Stmt *St, const ExceptionInfo::Throwables &Caught,
491 llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
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
586ExceptionAnalyzer::ExceptionInfo
587ExceptionAnalyzer::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
608ExceptionAnalyzer::ExceptionInfo
609ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) {
610 llvm::SmallSet<const FunctionDecl *, 32> CallStack;
611 return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack);
612}
613
614template <typename T>
615ExceptionAnalyzer::ExceptionInfo
616ExceptionAnalyzer::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
630ExceptionAnalyzer::ExceptionInfo
631ExceptionAnalyzer::analyze(const FunctionDecl *Func) {
632 return analyzeDispatch(Func);
633}
634
636 return analyzeDispatch(Stmt);
637}
638
639} // 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)