diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp index c3e9a13bb1ba2..f1f1cc3b294d1 100644 --- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp +++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp @@ -316,6 +316,34 @@ bool isQualificationConvertiblePointer(QualType From, QualType To, } } // namespace +static bool canThrow(const FunctionDecl *Func) { + const auto *FunProto = Func->getType()->getAs(); + if (!FunProto) + return true; + + switch (FunProto->canThrow()) { + case CT_Cannot: + return false; + case CT_Dependent: { + const Expr *NoexceptExpr = FunProto->getNoexceptExpr(); + if (!NoexceptExpr) + return true; // no noexept - can throw + + if (NoexceptExpr->isValueDependent()) + return true; // depend on template - some instance can throw + + bool Result = false; + if (!NoexceptExpr->EvaluateAsBooleanCondition(Result, Func->getASTContext(), + /*InConstantContext=*/true)) + return true; // complex X condition in noexcept(X), cannot validate, + // assume that may throw + return !Result; // noexcept(false) - can throw + } + default: + return true; + }; +} + bool ExceptionAnalyzer::ExceptionInfo::filterByCatch( const Type *HandlerTy, const ASTContext &Context) { llvm::SmallVector TypesToDelete; @@ -421,7 +449,7 @@ void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() { ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException( const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught, llvm::SmallSet &CallStack) { - if (CallStack.count(Func)) + if (!Func || CallStack.count(Func) || (!CallStack.empty() && !canThrow(Func))) return ExceptionInfo::createNonThrowing(); if (const Stmt *Body = Func->getBody()) { diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 820a76c66d986..abe815e7cba75 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -275,7 +275,8 @@ Changes in existing checks - Improved :doc:`bugprone-exception-escape ` to not emit warnings for - forward declarations of functions. + forward declarations of functions and additionally modified it to skip + ``noexcept`` functions during call stack analysis. - Fixed :doc:`bugprone-exception-escape` for coroutines where previously a warning would be emitted with coroutines 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 index f575a185897c2..39cf5476eb6ea 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape.cpp @@ -152,7 +152,7 @@ void throw_catch_multi_ptr_4() noexcept { } // FIXME: In this case 'a' is convertible to the handler and should be caught -// but in reality it's thrown. Note that clang doesn't report a warning for +// but in reality it's thrown. Note that clang doesn't report a warning for // this either. void throw_catch_multi_ptr_5() noexcept { try { @@ -249,7 +249,7 @@ void throw_derived_catch_base_alias() noexcept { void throw_derived_catch_base_ptr_c() noexcept { try { derived d; - throw &d; + throw &d; } catch(const base *) { } } @@ -259,7 +259,7 @@ void throw_derived_catch_base_ptr() noexcept { try { derived d; const derived *p = &d; - throw p; + throw p; } catch(base *) { } } @@ -282,7 +282,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 try { B b; - throw b; + throw b; } catch(A) { } } @@ -291,7 +291,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 try { B b; - throw &b; + throw &b; } catch(A *) { } } @@ -300,7 +300,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 try { C c; - throw c; + throw c; } catch(A) { } } @@ -309,7 +309,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 try { C c; - throw &c; + throw &c; } catch(A *) { } } @@ -318,7 +318,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 try { E e; - throw e; + throw e; } catch(A) { } } @@ -327,7 +327,7 @@ 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 try { E e; - throw e; + throw e; } catch(A) { } } @@ -703,3 +703,22 @@ int main() { throw 1; return 0; } + +// The following function all incorrectly throw exceptions, *but* calling them +// should not yield a warning because they are marked as noexcept. + +void test_basic_no_throw() noexcept { throw 42; } +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'test_basic_no_throw' which should not throw exceptions + +void test_basic_throw() noexcept(false) { throw 42; } + +void only_calls_non_throwing() noexcept { + test_basic_no_throw(); +} + +void calls_non_and_throwing() noexcept { +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_non_and_throwing' which should not throw exceptions + test_basic_no_throw(); + test_basic_throw(); +} +