denizevrenci created this revision. denizevrenci added a reviewer: njames93. Herald added subscribers: PiotrZSL, carlosgalvezp, ChuanqiXu, xazax.hun. Herald added a project: All. denizevrenci requested review of this revision. Herald added a project: clang-tools-extra. Herald added a subscriber: cfe-commits.
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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D147417 Files: clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-coro.cpp
Index: clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-coro.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-coro.cpp @@ -0,0 +1,121 @@ +// RUN: %check_clang_tidy -std=c++20 %s bugprone-exception-escape %t -- \ +// RUN: -- -fexceptions + +namespace std { + +template <class Ret, typename... T> struct coroutine_traits { + using promise_type = typename Ret::promise_type; +}; + +template <class Promise = void> 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<void> { + template <class PromiseType> + coroutine_handle(coroutine_handle<PromiseType>) 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 <typename T> struct promise; + +template <typename T> struct task { + using promise_type = promise<T>; + + explicit task(promise_type &p) { + throw 1; + p.return_val = this; + } + + T value; +}; + +template <typename T> struct promise { + task<T> get_return_object() { return task{*this}; } + + std::suspend_never initial_suspend() const noexcept { return {}; } + + std::suspend_never final_suspend() const noexcept { return {}; } + + template <typename U> void return_value(U &&val) { + return_val->value = static_cast<U &&>(val); + } + + void unhandled_exception() { throw 1; } + + task<T> *return_val; +}; + +task<int> a_ShouldNotDiag(const int a, const int b) { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:11: warning: an exception may be thrown in + // function 'a_ShouldNotDiag' which should not throw exceptions + if (b == 0) + throw b; + + co_return a / b; +} + +task<int> b_ShouldNotDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:11: warning: an exception may be thrown in + // function 'b_ShouldNotDiag' which should not throw exceptions + if (b == 0) + throw b; + + co_return a / b; +} + +int c_ShouldDiag(const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: an exception may be thrown in + // function 'c_ShouldDiag' which should not throw exceptions + if (b == 0) + throw b; + + return a / b; +} + +const auto d_ShouldNotDiag = [](const int a, const int b) -> task<int> { + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:30: warning: an exception may be thrown in + // function 'operator()' which should not throw exceptions + if (b == 0) + throw b; + + co_return a / b; +}; + +const auto e_ShouldNotDiag = [](const int a, + const int b) noexcept -> task<int> { + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:30: warning: an exception may be thrown in + // function 'operator()' which should not throw exceptions + if (b == 0) + throw b; + + co_return a / b; +}; + +const auto f_ShouldDiag = [](const int a, const int b) noexcept { + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: an exception may be thrown in + // function 'operator()' which should not throw exceptions + if (b == 0) + throw b; + + return a / b; +}; + +// CHECK-MESSAGES Index: clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp =================================================================== --- clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp +++ clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -51,12 +51,15 @@ } void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) { + auto IsCoroMatcher = + hasDescendant(expr(anyOf(coyieldExpr(), coreturnStmt(), coawaitExpr()))); Finder->addMatcher( functionDecl(anyOf(isNoThrow(), cxxDestructorDecl(), cxxConstructorDecl(isMoveConstructor()), cxxMethodDecl(isMoveAssignmentOperator()), hasName("main"), hasName("swap"), - isEnabled(FunctionsThatShouldNotThrow))) + isEnabled(FunctionsThatShouldNotThrow)), + unless(IsCoroMatcher)) .bind("thrower"), this); }
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits