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

Reply via email to