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 @@ -523,6 +523,19 @@ ExceptionInfo Excs = throwsException(DefaultInit->getExpr(), Caught, CallStack); Results.merge(Excs); + } else if (const auto *Coro = dyn_cast(St)) { + for (const Stmt *Child : Coro->childrenExclBody()) { + ExceptionInfo Excs = throwsException(Child, Caught, CallStack); + Results.merge(Excs); + } + ExceptionInfo Excs = throwsException(Coro->getBody(), Caught, CallStack); + for (const Type *Throwable : Excs.getExceptionTypes()) { + if (const auto ThrowableRec = Throwable->getAsCXXRecordDecl()) { + ExceptionInfo DestructorExcs = + throwsException(ThrowableRec->getDestructor(), CallStack); + Results.merge(DestructorExcs); + } + } } else { for (const Stmt *Child : St->children()) { ExceptionInfo Excs = throwsException(Child, 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 @@ -395,6 +395,10 @@ `: warn on ``const &&`` constructors. +- Fixed :doc:`bugprone-exception-escape` + for coroutines where previously a warning would be emitted with coroutines + throwing exceptions in their bodies. + Removed checks ^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-coro.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-coro.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-coro.cpp @@ -0,0 +1,711 @@ +// RUN: %check_clang_tidy -std=c++20 %s bugprone-exception-escape %t -- \ +// RUN: -- -fexceptions + +namespace std { + +template struct coroutine_traits { + using promise_type = typename Ret::promise_type; +}; + +template struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + static coroutine_handle from_promise(Promise &promise); + constexpr void *address() const noexcept; +}; + +template <> struct coroutine_handle { + template + coroutine_handle(coroutine_handle) noexcept; + static coroutine_handle from_address(void *); + constexpr void *address() const noexcept; +}; + +struct suspend_always { + bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +struct suspend_never { + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +} // namespace std + +template +struct Promise; + +template < + typename T, bool ThrowInTaskConstructor = false, + bool ThrowInPromiseConstructor = false, bool ThrowInInitialSuspend = false, + bool ThrowInGetReturnObject = false, bool ThrowInUnhandledException = false> +struct Task { + using promise_type = + Promise; + + explicit Task(promise_type &p) { + if constexpr (ThrowInTaskConstructor) { + throw 1; + } + + p.return_val = this; + } + + bool await_ready() { return true; } + + void await_suspend(std::coroutine_handle<> h) {} + + void await_resume() {} + + T value; +}; + +template +struct Task { + using promise_type = + Promise; + + explicit Task(promise_type &p) { + if constexpr (ThrowInTaskConstructor) { + throw 1; + } + + p.return_val = this; + } + + bool await_ready() { return true; } + + void await_suspend(std::coroutine_handle<> h) {} + + void await_resume() {} +}; + +template +struct Promise { + Promise() { + if constexpr (ThrowInPromiseConstructor) { + throw 1; + } + } + + Task get_return_object() { + if constexpr (ThrowInGetReturnObject) { + throw 1; + } + + return Task{*this}; + } + + std::suspend_never initial_suspend() const { + if constexpr (ThrowInInitialSuspend) { + throw 1; + } + + return {}; + } + + std::suspend_never final_suspend() const noexcept { return {}; } + + template void return_value(U &&val) { + return_val->value = static_cast(val); + } + + template std::suspend_never yield_value(U &&val) { + return_val->value = static_cast(val); + return {}; + } + + void unhandled_exception() { + if constexpr (ThrowInUnhandledException) { + throw 1; + } + } + + Task *return_val; +}; + +template +struct Promise { + Promise() { + if constexpr (ThrowInPromiseConstructor) { + throw 1; + } + } + + Task get_return_object() { + if constexpr (ThrowInGetReturnObject) { + throw 1; + } + + return Task{*this}; + } + + std::suspend_never initial_suspend() const { + if constexpr (ThrowInInitialSuspend) { + throw 1; + } + + return {}; + } + + std::suspend_never final_suspend() const noexcept { return {}; } + + void return_void() {} + + void unhandled_exception() { + if constexpr (ThrowInUnhandledException) { + throw 1; + } + } + + Task *return_val; +}; + +struct Evil { + ~Evil() noexcept(false) { + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: an exception may be thrown in function '~Evil' which should not throw exceptions + throw 42; + } +}; + +Task returnOne() { co_return 1; } + +namespace function { + +namespace coreturn { + +Task a_ShouldNotDiag(const int a, const int b) { + if (b == 0) + throw b; + + co_return a / b; +} + +Task b_ShouldNotDiag(const int a, const int b) noexcept { + if (b == 0) + throw b; + + co_return a / b; +} + +Task c_ShouldNotDiag(const int a, const int b) { + if (b == 0) + throw Evil{}; + + co_return a / b; +} + +Task c_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: an exception may be thrown in function 'c_ShouldDiag' which should not throw exceptions + if (b == 0) + throw Evil{}; + + co_return a / b; +} + +Task d_ShouldNotDiag(const int a, const int b) { + co_return a / b; +} + +Task d_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: an exception may be thrown in function 'd_ShouldDiag' which should not throw exceptions + co_return a / b; +} + +Task e_ShouldNotDiag(const int a, const int b) { + co_return a / b; +} + +Task e_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: an exception may be thrown in function 'e_ShouldDiag' which should not throw exceptions + co_return a / b; +} + +Task f_ShouldNotDiag(const int a, const int b) { + co_return a / b; +} + +Task f_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: an exception may be thrown in function 'f_ShouldDiag' which should not throw exceptions + co_return a / b; +} + +Task g_ShouldNotDiag(const int a, const int b) { + co_return a / b; +} + +Task g_ShouldDiag(const int a, + const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-2]]:38: warning: an exception may be thrown in function 'g_ShouldDiag' which should not throw exceptions + co_return a / b; +} + +Task h_ShouldNotDiag(const int a, + const int b) { + co_return a / b; +} + +Task h_ShouldDiag(const int a, + const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-2]]:45: warning: an exception may be thrown in function 'h_ShouldDiag' which should not throw exceptions + co_return a / b; +} + +} // namespace coreturn + +namespace coyield { + +Task a_ShouldNotDiag(const int a, const int b) { + if (b == 0) + throw b; + + co_yield a / b; +} + +Task b_ShouldNotDiag(const int a, const int b) noexcept { + if (b == 0) + throw b; + + co_yield a / b; +} + +Task c_ShouldNotDiag(const int a, const int b) { + if (b == 0) + throw Evil{}; + + co_yield a / b; +} + +Task c_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: an exception may be thrown in function 'c_ShouldDiag' which should not throw exceptions + if (b == 0) + throw Evil{}; + + co_yield a / b; +} + +Task d_ShouldNotDiag(const int a, const int b) { + co_yield a / b; +} + +Task d_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: an exception may be thrown in function 'd_ShouldDiag' which should not throw exceptions + co_yield a / b; +} + +Task e_ShouldNotDiag(const int a, const int b) { + co_yield a / b; +} + +Task e_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: an exception may be thrown in function 'e_ShouldDiag' which should not throw exceptions + co_yield a / b; +} + +Task f_ShouldNotDiag(const int a, const int b) { + co_yield a / b; +} + +Task f_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: an exception may be thrown in function 'f_ShouldDiag' which should not throw exceptions + co_yield a / b; +} + +Task g_ShouldNotDiag(const int a, const int b) { + co_yield a / b; +} + +Task g_ShouldDiag(const int a, + const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-2]]:38: warning: an exception may be thrown in function 'g_ShouldDiag' which should not throw exceptions + co_yield a / b; +} + +Task h_ShouldNotDiag(const int a, + const int b) { + co_yield a / b; +} + +Task h_ShouldDiag(const int a, + const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-2]]:45: warning: an exception may be thrown in function 'h_ShouldDiag' which should not throw exceptions + co_yield a / b; +} + +} // namespace coyield + +namespace coawait { + +Task a_ShouldNotDiag(const int a, const int b) { + if (b == 0) + throw b; + + co_await returnOne(); +} + +Task b_ShouldNotDiag(const int a, const int b) noexcept { + if (b == 0) + throw b; + + co_await returnOne(); +} + +Task c_ShouldNotDiag(const int a, const int b) { + if (b == 0) + throw Evil{}; + + co_await returnOne(); +} + +Task c_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: an exception may be thrown in function 'c_ShouldDiag' which should not throw exceptions + if (b == 0) + throw Evil{}; + + co_await returnOne(); +} + +Task d_ShouldNotDiag(const int a, const int b) { + co_await returnOne(); +} + +Task d_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: an exception may be thrown in function 'd_ShouldDiag' which should not throw exceptions + co_await returnOne(); +} + +Task e_ShouldNotDiag(const int a, const int b) { + co_await returnOne(); +} + +Task e_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: an exception may be thrown in function 'e_ShouldDiag' which should not throw exceptions + co_await returnOne(); +} + +Task f_ShouldNotDiag(const int a, const int b) { + co_await returnOne(); +} + +Task f_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: an exception may be thrown in function 'f_ShouldDiag' which should not throw exceptions + co_await returnOne(); +} + +Task g_ShouldNotDiag(const int a, + const int b) { + co_await returnOne(); +} + +Task g_ShouldDiag(const int a, + const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-2]]:39: warning: an exception may be thrown in function 'g_ShouldDiag' which should not throw exceptions + co_await returnOne(); +} + +Task h_ShouldNotDiag(const int a, + const int b) { + co_await returnOne(); +} + +Task +h_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: an exception may be thrown in function 'h_ShouldDiag' which should not throw exceptions + co_await returnOne(); +} + +} // namespace coawait + +} // namespace function + +namespace lambda { + +namespace coreturn { + +const auto a_ShouldNotDiag = [](const int a, const int b) -> Task { + if (b == 0) + throw b; + + co_return a / b; +}; + +const auto b_ShouldNotDiag = [](const int a, + const int b) noexcept -> Task { + if (b == 0) + throw b; + + co_return a / b; +}; + +const auto c_ShouldNotDiag = [](const int a, const int b) -> Task { + if (b == 0) + throw Evil{}; + + co_return a / b; +}; + +const auto c_ShouldDiag = [](const int a, const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + if (b == 0) + throw Evil{}; + + co_return a / b; +}; + +const auto d_ShouldNotDiag = [](const int a, const int b) -> Task { + co_return a / b; +}; + +const auto d_ShouldDiag = [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_return a / b; +}; + +const auto e_ShouldNotDiag = [](const int a, + const int b) -> Task { + co_return a / b; +}; + +const auto e_ShouldDiag = [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_return a / b; +}; + +const auto f_ShouldNotDiag = [](const int a, + const int b) -> Task { + co_return a / b; +}; + +const auto f_ShouldDiag = + [](const int a, const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_return a / b; +}; + +const auto g_ShouldNotDiag = + [](const int a, const int b) -> Task { + co_return a / b; +}; + +const auto g_ShouldDiag = + [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_return a / b; +}; + +const auto h_ShouldNotDiag = + [](const int a, + const int b) -> Task { + co_return a / b; +}; + +const auto h_ShouldDiag = + [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_return a / b; +}; + +} // namespace coreturn + +namespace coyield { + +const auto a_ShouldNotDiag = [](const int a, const int b) -> Task { + if (b == 0) + throw b; + + co_yield a / b; +}; + +const auto b_ShouldNotDiag = [](const int a, + const int b) noexcept -> Task { + if (b == 0) + throw b; + + co_yield a / b; +}; + +const auto c_ShouldNotDiag = [](const int a, const int b) -> Task { + if (b == 0) + throw Evil{}; + + co_yield a / b; +}; + +const auto c_ShouldDiag = [](const int a, const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + if (b == 0) + throw Evil{}; + + co_yield a / b; +}; + +const auto d_ShouldNotDiag = [](const int a, const int b) -> Task { + co_yield a / b; +}; + +const auto d_ShouldDiag = [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_yield a / b; +}; + +const auto e_ShouldNotDiag = [](const int a, + const int b) -> Task { + co_yield a / b; +}; + +const auto e_ShouldDiag = [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_yield a / b; +}; + +const auto f_ShouldNotDiag = [](const int a, + const int b) -> Task { + co_yield a / b; +}; + +const auto f_ShouldDiag = + [](const int a, const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_yield a / b; +}; + +const auto g_ShouldNotDiag = + [](const int a, const int b) -> Task { + co_yield a / b; +}; + +const auto g_ShouldDiag = + [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_yield a / b; +}; + +const auto h_ShouldNotDiag = + [](const int a, + const int b) -> Task { + co_yield a / b; +}; + +const auto h_ShouldDiag = + [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_yield a / b; +}; + +} // namespace coyield + +namespace coawait { + +const auto a_ShouldNotDiag = [](const int a, const int b) -> Task { + if (b == 0) + throw b; + + co_await returnOne(); +}; + +const auto b_ShouldNotDiag = [](const int a, + const int b) noexcept -> Task { + if (b == 0) + throw b; + + co_await returnOne(); +}; + +const auto c_ShouldNotDiag = [](const int a, const int b) -> Task { + if (b == 0) + throw Evil{}; + + co_await returnOne(); +}; + +const auto c_ShouldDiag = [](const int a, const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + if (b == 0) + throw Evil{}; + + co_await returnOne(); +}; + +const auto d_ShouldNotDiag = [](const int a, const int b) -> Task { + co_await returnOne(); +}; + +const auto d_ShouldDiag = [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_await returnOne(); +}; + +const auto e_ShouldNotDiag = [](const int a, + const int b) -> Task { + co_await returnOne(); +}; + +const auto e_ShouldDiag = [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_await returnOne(); +}; + +const auto f_ShouldNotDiag = [](const int a, + const int b) -> Task { + co_await returnOne(); +}; + +const auto f_ShouldDiag = + [](const int a, const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_await returnOne(); +}; + +const auto g_ShouldNotDiag = + [](const int a, const int b) -> Task { + co_await returnOne(); +}; + +const auto g_ShouldDiag = + [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_await returnOne(); +}; + +const auto h_ShouldNotDiag = + [](const int a, + const int b) -> Task { + co_await returnOne(); +}; + +const auto h_ShouldDiag = + [](const int a, + const int b) noexcept -> Task { + // CHECK-MESSAGES: :[[@LINE-2]]:5: warning: an exception may be thrown in function 'operator()' which should not throw exceptions + co_await returnOne(); +}; + +} // namespace coawait + +} // namespace lambda diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -443,6 +443,17 @@ NumParams); } + child_range childrenExclBody() { + return child_range(getStoredStmts() + SubStmt::Body + 1, + getStoredStmts() + SubStmt::FirstParamMove + NumParams); + } + + const_child_range childrenExclBody() const { + return const_child_range(getStoredStmts() + SubStmt::Body + 1, + getStoredStmts() + SubStmt::FirstParamMove + + NumParams); + } + static bool classof(const Stmt *T) { return T->getStmtClass() == CoroutineBodyStmtClass; }