From: Deniz Evrenci Date: Tue, 30 May 2023 16:48:28 +0000 (+0000) Subject: [clang-tidy] Do not emit bugprone-exception-escape warnings from coroutines X-Git-Tag: upstream/17.0.6~6794 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=6219b7c61a942fb8b6d931b4aac021d293cdde4d;p=platform%2Fupstream%2Fllvm.git [clang-tidy] Do not emit bugprone-exception-escape warnings from coroutines All exceptions thrown in coroutine bodies are caught and unhandled_exception member of the coroutine promise type is called. In accordance with the existing rules of diagnostics related to exceptions thrown in functions marked noexcept, even if the promise type's constructor, get_return_object, or unhandled_exception throws, diagnostics should not be emitted. Fixes #61905. Reviewed By: PiotrZSL, ChuanqiXu Differential Revision: https://reviews.llvm.org/D147417 --- diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp index c862303..690e771 100644 --- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp +++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp @@ -523,6 +523,19 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException( 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 index 1eb8c5b..b336cd2 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -395,6 +395,10 @@ Changes in existing checks `: 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 index 0000000..9caafe7 --- /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 index 60fc3f3..8b4ef24 100644 --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -443,6 +443,17 @@ public: 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; }