diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp --- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -76,13 +76,22 @@ if (!MatchedDecl) return; - if (Tracer.analyze(MatchedDecl).getBehaviour() == - utils::ExceptionAnalyzer::State::Throwing) - // FIXME: We should provide more information about the exact location where - // the exception is thrown, maybe the full path the exception escapes - diag(MatchedDecl->getLocation(), "an exception may be thrown in function " - "%0 which should not throw exceptions") - << MatchedDecl; + const utils::ExceptionAnalyzer::ExceptionInfo AnalyzeResult = + Tracer.analyze(MatchedDecl); + if (AnalyzeResult.getBehaviour() != utils::ExceptionAnalyzer::State::Throwing) + return; + + diag(MatchedDecl->getLocation(), "an exception may be thrown in function " + "%0 which should not throw exceptions") + << MatchedDecl; + + for (auto [ThrowType, ThrowLoc] : AnalyzeResult.getExceptionTypes()) + diag(ThrowLoc, "may throw %0 here", DiagnosticIDs::Note) + << QualType(ThrowType, 0U); + + if (AnalyzeResult.containsUnknownElements()) + diag(MatchedDecl->getLocation(), "may throw unknown exceptions here", + DiagnosticIDs::Note); } } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h --- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h +++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h @@ -13,6 +13,8 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringSet.h" +#include +#include namespace clang::tidy::utils { @@ -35,9 +37,11 @@ /// does not include *ALL* possible types as there is the possibility that /// an 'Unknown' function is called that might throw a previously unknown /// exception at runtime. + class ExceptionInfo { public: - using Throwables = llvm::SmallSet; + using Throwables = std::map; + static ExceptionInfo createUnknown() { return ExceptionInfo(State::Unknown); } @@ -60,11 +64,12 @@ /// Register a single exception type as recognized potential exception to be /// thrown. - void registerException(const Type *ExceptionType); + void registerException(const Type *ExceptionType, const SourceLocation Loc); /// Registers a `SmallVector` of exception types as recognized potential /// exceptions to be thrown. - void registerExceptions(const Throwables &Exceptions); + void registerExceptions(const Throwables &Exceptions, + const SourceLocation Loc); /// Updates the local state according to the other state. That means if /// for example a function contains multiple statements the 'ExceptionInfo' @@ -78,7 +83,8 @@ /// possible to catch multiple exception types by one 'catch' if they /// are a subclass of the 'catch'ed exception type. /// Returns 'true' if some exceptions were filtered, otherwise 'false'. - bool filterByCatch(const Type *BaseClass, const ASTContext &Context); + bool filterByCatch(const Type *BaseClass, const ASTContext &Context, + Throwables &CaughtExceptions); /// Filter the set of thrown exception type against a set of ignored /// types that shall not be considered in the exception analysis. diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp --- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp +++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp @@ -11,18 +11,19 @@ namespace clang::tidy::utils { void ExceptionAnalyzer::ExceptionInfo::registerException( - const Type *ExceptionType) { + const Type *ExceptionType, const SourceLocation Loc) { assert(ExceptionType != nullptr && "Only valid types are accepted"); Behaviour = State::Throwing; - ThrownExceptions.insert(ExceptionType); + ThrownExceptions.emplace(ExceptionType, Loc); } void ExceptionAnalyzer::ExceptionInfo::registerExceptions( - const Throwables &Exceptions) { - if (Exceptions.size() == 0) + const Throwables &Exceptions, const SourceLocation Loc) { + if (Exceptions.empty()) return; Behaviour = State::Throwing; - ThrownExceptions.insert(Exceptions.begin(), Exceptions.end()); + for (const auto [ThrowType, ThrowLoc] : Exceptions) + ThrownExceptions.emplace(ThrowType, ThrowLoc.isInvalid() ? Loc : ThrowLoc); } ExceptionAnalyzer::ExceptionInfo &ExceptionAnalyzer::ExceptionInfo::merge( @@ -345,29 +346,29 @@ } bool ExceptionAnalyzer::ExceptionInfo::filterByCatch( - const Type *HandlerTy, const ASTContext &Context) { - llvm::SmallVector TypesToDelete; - for (const Type *ExceptionTy : ThrownExceptions) { + const Type *HandlerTy, const ASTContext &Context, + ExceptionInfo::Throwables &CaughtExceptions) { + + auto ShouldRemoveFnt = [HandlerTy, &Context](const Type *ExceptionTy) { CanQualType ExceptionCanTy = ExceptionTy->getCanonicalTypeUnqualified(); CanQualType HandlerCanTy = HandlerTy->getCanonicalTypeUnqualified(); // The handler is of type cv T or cv T& and E and T are the same type // (ignoring the top-level cv-qualifiers) ... - if (ExceptionCanTy == HandlerCanTy) { - TypesToDelete.push_back(ExceptionTy); - } + if (ExceptionCanTy == HandlerCanTy) + return true; // The handler is of type cv T or cv T& and T is an unambiguous public base // class of E ... - else if (isUnambiguousPublicBaseClass(ExceptionCanTy->getTypePtr(), - HandlerCanTy->getTypePtr())) { - TypesToDelete.push_back(ExceptionTy); - } + if (isUnambiguousPublicBaseClass(ExceptionCanTy->getTypePtr(), + HandlerCanTy->getTypePtr())) + return true; if (HandlerCanTy->getTypeClass() == Type::RValueReference || (HandlerCanTy->getTypeClass() == Type::LValueReference && !HandlerCanTy->getTypePtr()->getPointeeType().isConstQualified())) - continue; + return false; + // The handler is of type cv T or const T& where T is a pointer or // pointer-to-member type and E is a pointer or pointer-to-member type that // can be converted to T by one or more of ... @@ -378,53 +379,59 @@ if (isStandardPointerConvertible(ExceptionCanTy, HandlerCanTy) && isUnambiguousPublicBaseClass( ExceptionCanTy->getTypePtr()->getPointeeType().getTypePtr(), - HandlerCanTy->getTypePtr()->getPointeeType().getTypePtr())) { - TypesToDelete.push_back(ExceptionTy); - } + HandlerCanTy->getTypePtr()->getPointeeType().getTypePtr())) + return true; // A function pointer conversion ... - else if (isFunctionPointerConvertible(ExceptionCanTy, HandlerCanTy)) { - TypesToDelete.push_back(ExceptionTy); - } + if (isFunctionPointerConvertible(ExceptionCanTy, HandlerCanTy)) + return true; // A a qualification conversion ... - else if (isQualificationConvertiblePointer(ExceptionCanTy, HandlerCanTy, - Context.getLangOpts())) { - TypesToDelete.push_back(ExceptionTy); - } + if (isQualificationConvertiblePointer(ExceptionCanTy, HandlerCanTy, + Context.getLangOpts())) + return true; + return false; } // The handler is of type cv T or const T& where T is a pointer or // pointer-to-member type and E is std::nullptr_t. - else if (isPointerOrPointerToMember(HandlerCanTy->getTypePtr()) && - ExceptionCanTy->isNullPtrType()) { - TypesToDelete.push_back(ExceptionTy); + return isPointerOrPointerToMember(HandlerCanTy->getTypePtr()) && + ExceptionCanTy->isNullPtrType(); + }; + + bool Result = false; + for (Throwables::iterator It = ThrownExceptions.begin(); + It != ThrownExceptions.end();) { + if (!ShouldRemoveFnt(It->first)) { + ++It; + continue; } - } - for (const Type *T : TypesToDelete) - ThrownExceptions.erase(T); + CaughtExceptions.emplace(*It); + It = ThrownExceptions.erase(It); + Result = true; + } reevaluateBehaviour(); - return TypesToDelete.size() > 0; + return Result; } ExceptionAnalyzer::ExceptionInfo & ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions( const llvm::StringSet<> &IgnoredTypes, bool IgnoreBadAlloc) { - llvm::SmallVector TypesToDelete; - // Note: Using a 'SmallSet' with 'llvm::remove_if()' is not possible. - // Therefore this slightly hacky implementation is required. - for (const Type *T : ThrownExceptions) { - if (const auto *TD = T->getAsTagDecl()) { + + for (Throwables::iterator It = ThrownExceptions.begin(); + It != ThrownExceptions.end();) { + if (const auto *TD = It->first->getAsTagDecl()) { if (TD->getDeclName().isIdentifier()) { if ((IgnoreBadAlloc && (TD->getName() == "bad_alloc" && TD->isInStdNamespace())) || - (IgnoredTypes.count(TD->getName()) > 0)) - TypesToDelete.push_back(T); + (IgnoredTypes.count(TD->getName()) > 0)) { + It = ThrownExceptions.erase(It); + continue; + } } } + ++It; } - for (const Type *T : TypesToDelete) - ThrownExceptions.erase(T); reevaluateBehaviour(); return *this; @@ -472,7 +479,8 @@ auto Result = ExceptionInfo::createUnknown(); if (const auto *FPT = Func->getType()->getAs()) { for (const QualType &Ex : FPT->exceptions()) - Result.registerException(Ex.getTypePtr()); + Result.registerException(Ex.getTypePtr(), + Func->getExceptionSpecSourceRange().getBegin()); } return Result; } @@ -495,12 +503,13 @@ ->getPointeeType() ->getUnqualifiedDesugaredType(); Results.registerException( - ThrownExpr->getType()->getUnqualifiedDesugaredType()); + ThrownExpr->getType()->getUnqualifiedDesugaredType(), + Throw->getThrowLoc()); } else // A rethrow of a caught exception happens which makes it possible // to throw all exception that are caught in the 'catch' clause of // the parent try-catch block. - Results.registerExceptions(Caught); + Results.registerExceptions(Caught, Throw->getThrowLoc()); } else if (const auto *Try = dyn_cast(St)) { ExceptionInfo Uncaught = throwsException(Try->getTryBlock(), Caught, CallStack); @@ -526,10 +535,11 @@ // thrown types (because it's sensitive to inheritance) the throwing // situation changes. First of all filter the exception types and // analyze if the baseclass-exception is rethrown. - if (Uncaught.filterByCatch( - CaughtType, Catch->getExceptionDecl()->getASTContext())) { - ExceptionInfo::Throwables CaughtExceptions; - CaughtExceptions.insert(CaughtType); + ExceptionInfo::Throwables CaughtExceptions; + if (Uncaught.filterByCatch(CaughtType, + Catch->getExceptionDecl()->getASTContext(), + CaughtExceptions)) { + CaughtExceptions.emplace(CaughtType, SourceLocation()); ExceptionInfo Rethrown = throwsException(Catch->getHandlerBlock(), CaughtExceptions, CallStack); Results.merge(Rethrown); @@ -560,7 +570,7 @@ ExceptionInfo Excs = throwsException(Coro->getBody(), Caught, CallStack); Results.merge(throwsException(Coro->getExceptionHandler(), Excs.getExceptionTypes(), CallStack)); - for (const Type *Throwable : Excs.getExceptionTypes()) { + for (auto [Throwable, ThrowLoc] : Excs.getExceptionTypes()) { if (const auto ThrowableRec = Throwable->getAsCXXRecordDecl()) { ExceptionInfo DestructorExcs = throwsException(ThrowableRec->getDestructor(), Caught, CallStack); diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -169,6 +169,11 @@ Changes in existing checks ^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Improved :doc:`bugprone-exception-escape + ` check to not emit warnings for + forward declarations of functions. Emit additional diagnostic information + about uncaught exceptions. + - Fixed bug in :doc:`bugprone-reserved-identifier `, so that it does not warn on macros starting with underscore and lowercase letter. @@ -187,7 +192,7 @@ ignore delegate constructors. - Improved :doc `cppcoreguidelines-pro-bounds-array-to-pointer-decay - ` check + ` check to ignore predefined expression (e.g., ``__func__``, ...). - Improved :doc:`cppcoreguidelines-pro-type-member-init diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-rethrow.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-rethrow.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-rethrow.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-rethrow.cpp @@ -15,6 +15,7 @@ int throwsAndCallsRethrower() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'throwsAndCallsRethrower' which should not throw exceptions +// CHECK-MESSAGES: :[[@LINE+2]]:9: note: may throw 'int' here try { throw 1; } catch(...) { @@ -24,6 +25,7 @@ int throwsAndCallsCallsRethrower() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'throwsAndCallsCallsRethrower' which should not throw exceptions +// CHECK-MESSAGES: :[[@LINE+2]]:9: note: may throw 'int' here try { throw 1; } catch(...) { @@ -34,3 +36,18 @@ void rethrowerNoexcept() noexcept { throw; } + +void throwInt() { + throw 5; +} + +void rethrowInt() noexcept { +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'rethrowInt' which should not throw exceptions [bugprone-exception-escape] +// CHECK-MESSAGES: :[[@LINE-5]]:3: note: may throw 'int' here + try { + throwInt(); + } catch(int) { + throw; + } catch(...) { + } +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-throw.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-throw.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-throw.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-throw.cpp @@ -2,6 +2,7 @@ void throwing_throw_nothing() throw() { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throwing_throw_nothing' which should not throw exceptions +// CHECK-MESSAGES: :[[@LINE+1]]:3: note: may throw 'int' here throw 1; } @@ -13,11 +14,14 @@ void indirect_implicit() throw() { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'indirect_implicit' which should not throw exceptions +// CHECK-MESSAGES: :[[@LINE-5]]:3: note: may throw 'int' here implicit_int_thrower(); } void indirect_explicit() throw() { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'indirect_explicit' which should not throw exceptions +// CHECK-MESSAGES: :[[@LINE-14]]:29: note: may throw 'int' here +// CHECK-MESSAGES: :[[@LINE-3]]:6: note: may throw unknown exceptions here explicit_int_thrower(); } @@ -28,4 +32,5 @@ struct sub_throws : super_throws { sub_throws() throw() : super_throws() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function 'sub_throws' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-6]]:31: note: may throw 'int' here }; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp @@ -9,6 +9,7 @@ struct throwing_destructor { ~throwing_destructor() { // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function '~throwing_destructor' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:5: note: may throw 'int' here throw 1; } }; @@ -16,6 +17,7 @@ struct throwing_move_constructor { throwing_move_constructor(throwing_move_constructor&&) { // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function 'throwing_move_constructor' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:5: note: may throw 'int' here throw 1; } }; @@ -23,12 +25,14 @@ struct throwing_move_assignment { throwing_move_assignment& operator=(throwing_move_assignment&&) { // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: an exception may be thrown in function 'operator=' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:5: note: may throw 'int' here throw 1; } }; void throwing_noexcept() noexcept { - // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throwing_noexcept' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throwing_noexcept' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:3: note: may throw 'int' here throw 1; } @@ -42,6 +46,7 @@ void throw_and_catch_some(int n) noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_and_catch_some' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'double' here try { if (n) throw 1; throw 1.1; @@ -70,6 +75,7 @@ void throw_and_rethrow() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_and_rethrow' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+2]]:5: note: may throw 'int' here try { throw 1; } catch(int &) { @@ -79,6 +85,7 @@ void throw_catch_throw() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_catch_throw' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'int' here try { throw 1; } catch(int &) { @@ -88,6 +95,7 @@ void throw_catch_rethrow_the_rest(int n) noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_catch_rethrow_the_rest' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'double' here try { if (n) throw 1; throw 1.1; @@ -120,6 +128,7 @@ void throw_catch_multi_ptr_1() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_catch_multi_ptr_1' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'char **' here try { char **p = 0; throw p; @@ -165,6 +174,7 @@ void throw_c_catch_pointer() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_c_catch_pointer' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'const int *' here try { int a = 1; const int *p = &a; @@ -174,6 +184,7 @@ void throw_c_catch_pointer_v() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_c_catch_pointer_v' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'const int *' here try { int a = 1; const int *p = &a; @@ -183,6 +194,7 @@ void throw_v_catch_pointer() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_v_catch_pointer' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'volatile int *' here try { int a = 1; volatile int *p = &a; @@ -192,6 +204,7 @@ void throw_v_catch_pointer_c() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_v_catch_pointer_c' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'volatile int *' here try { int a = 1; volatile int *p = &a; @@ -201,6 +214,7 @@ void throw_cv_catch_pointer_c() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_cv_catch_pointer_c' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'const volatile int *' here try { int a = 1; const volatile int *p = &a; @@ -210,6 +224,7 @@ void throw_cv_catch_pointer_v() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_cv_catch_pointer_v' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'const volatile int *' here try { int a = 1; const volatile int *p = &a; @@ -256,6 +271,7 @@ void throw_derived_catch_base_ptr() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_ptr' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'const derived *' here try { derived d; const derived *p = &d; @@ -280,6 +296,7 @@ void throw_derived_catch_base_private() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_private' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'B' here try { B b; throw b; @@ -289,6 +306,7 @@ void throw_derived_catch_base_private_ptr() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_private_ptr' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'B *' here try { B b; throw &b; @@ -298,6 +316,7 @@ void throw_derived_catch_base_protected() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_protected' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'C' here try { C c; throw c; @@ -307,6 +326,7 @@ void throw_derived_catch_base_protected_ptr() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_protected_ptr' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'C *' here try { C c; throw &c; @@ -316,6 +336,7 @@ void throw_derived_catch_base_ambiguous() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_ambiguous' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'E' here try { E e; throw e; @@ -325,10 +346,11 @@ void throw_derived_catch_base_ambiguous_ptr() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derived_catch_base_ambiguous_ptr' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'E *' here try { E e; - throw e; - } catch(A) { + throw &e; + } catch(A *) { } } @@ -344,6 +366,7 @@ void throw_alias_catch_original_warn() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_alias_catch_original_warn' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+5]]:5: note: may throw 'float' here using alias = float; try { @@ -365,6 +388,7 @@ void throw_original_catch_alias_warn() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_original_catch_alias_warn' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+5]]:5: note: may throw 'char **' here using alias = int; try { @@ -428,6 +452,7 @@ void throw_basefn_catch_derivedfn() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_basefn_catch_derivedfn' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+2]]:5: note: may throw 'void (baseMember::*)()' here try { throw &baseMember::foo; } catch(void(derivedMember::*)()) { @@ -443,6 +468,7 @@ void throw_basem_catch_basem_throw() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_basem_catch_basem_throw' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'int *baseMember::**' here try { auto ptr = &baseMember::iptr; throw &ptr; @@ -460,6 +486,7 @@ void throw_basem_catch_derivedm() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_basem_catch_derivedm' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'int *baseMember::**' here try { auto ptr = &baseMember::iptr; throw &ptr; @@ -469,6 +496,7 @@ void throw_derivedm_catch_basem() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_derivedm_catch_basem' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'int *derivedMember::**' here try { int *derivedMember::* ptr = &derivedMember::iptr; throw &ptr; @@ -478,6 +506,7 @@ void throw_original_catch_alias_2_warn() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throw_original_catch_alias_2_warn' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+5]]:5: note: may throw 'char **' here using alias = const int *const; try { @@ -501,6 +530,7 @@ void bad_try_nested_try(int n) noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'bad_try_nested_try' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+2]]:12: note: may throw 'int' here try { if (n) throw 1; try { @@ -537,6 +567,7 @@ void bad_catch_nested_try() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'bad_catch_nested_try' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+5]]:7: note: may throw 'double' here try { throw 1; } catch(int &) { @@ -558,11 +589,13 @@ void indirect_implicit() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'indirect_implicit' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-9]]:3: note: may throw 'int' here implicit_int_thrower(); } void indirect_explicit() noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'indirect_explicit' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-11]]:3: note: may throw 'int' here explicit_int_thrower(); } @@ -583,6 +616,7 @@ void swap(int&, int&) { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'swap' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:3: note: may throw 'int' here throw 1; } @@ -601,11 +635,13 @@ void enabled1() { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'enabled1' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:3: note: may throw 'int' here throw 1; } void enabled2() { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'enabled2' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-5]]:3: note: may throw 'int' here enabled1(); } @@ -636,6 +672,7 @@ void this_counts(int n) noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'this_counts' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:10: note: may throw 'int' here if (n) throw 1; throw ignored1(); } @@ -646,6 +683,7 @@ int directly_recursive(int n) noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'directly_recursive' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-5]]:3: note: may throw 'int' here if (n == 0) thrower(n); return directly_recursive(n); @@ -659,6 +697,7 @@ int indirectly_recursive(int n) noexcept { // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'indirectly_recursive' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-19]]:3: note: may throw 'int' here if (n == 0) thrower(n); return recursion_helper(n); @@ -671,6 +710,7 @@ struct sub_throws : super_throws { sub_throws() noexcept : super_throws() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function 'sub_throws' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-6]]:36: note: may throw 'int' here }; struct init_member_throws { @@ -678,6 +718,7 @@ init_member_throws() noexcept : s() {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function 'init_member_throws' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-14]]:36: note: may throw 'int' here }; struct implicit_init_member_throws { @@ -685,6 +726,7 @@ implicit_init_member_throws() noexcept {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function 'implicit_init_member_throws' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-22]]:36: note: may throw 'int' here }; struct init { @@ -696,10 +738,12 @@ in_class_init_throws() noexcept {} // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function 'in_class_init_throws' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE-8]]:45: note: may throw 'int' here }; int main() { // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'main' which should not throw exceptions + // CHECK-MESSAGES: :[[@LINE+1]]:3: note: may throw 'int' here throw 1; return 0; } @@ -746,3 +790,26 @@ }; }} + +struct Exception1 {}; +struct Exception2 {}; + +void throwException1Or2(bool value) noexcept { +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throwException1Or2' which should not throw exceptions +// CHECK-MESSAGES: :[[@LINE+3]]:5: note: may throw 'Exception1' here +// CHECK-MESSAGES: :[[@LINE+4]]:5: note: may throw 'Exception2' here + if (value) + throw Exception1(); + else + throw Exception2(); +} + +void throwSomething() noexcept(false); + +void throwKnownAndUnknownException() noexcept { +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'throwKnownAndUnknownException' which should not throw exceptions +// CHECK-MESSAGES: :[[@LINE+3]]:3: note: may throw 'Exception1' here +// CHECK-MESSAGES: :[[@LINE-3]]:6: note: may throw unknown exceptions here + throwSomething(); + throw Exception1(); +}