[clang-tidy] Do not emit bugprone-exception-escape warnings from coroutines
authorDeniz Evrenci <denizevrenci@gmail.com>
Tue, 30 May 2023 16:48:28 +0000 (16:48 +0000)
committerPiotr Zegar <me@piotrzegar.pl>
Tue, 30 May 2023 17:48:11 +0000 (17:48 +0000)
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

clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
clang-tools-extra/docs/ReleaseNotes.rst
clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-coro.cpp [new file with mode: 0644]
clang/include/clang/AST/StmtCXX.h

index c862303..690e771 100644 (file)
@@ -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<CoroutineBodyStmt>(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);
index 1eb8c5b..b336cd2 100644 (file)
@@ -395,6 +395,10 @@ Changes in existing checks
   <clang-tidy/checks/performance/no-automatic-move>`: warn on ``const &&``
   constructors.
 
+- Fixed :doc:`bugprone-exception-escape<clang-tidy/checks/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 (file)
index 0000000..9caafe7
--- /dev/null
@@ -0,0 +1,711 @@
+// 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 Task, typename T, bool ThrowInPromiseConstructor,
+          bool ThrowInInitialSuspend, bool ThrowInGetReturnObject,
+          bool ThrowInUnhandledException>
+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<Task, T, ThrowInPromiseConstructor, ThrowInInitialSuspend,
+              ThrowInGetReturnObject, ThrowInUnhandledException>;
+
+  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 <bool ThrowInTaskConstructor, bool ThrowInPromiseConstructor,
+          bool ThrowInInitialSuspend, bool ThrowInGetReturnObject,
+          bool ThrowInUnhandledException>
+struct Task<void, ThrowInTaskConstructor, ThrowInPromiseConstructor,
+            ThrowInInitialSuspend, ThrowInGetReturnObject,
+            ThrowInUnhandledException> {
+  using promise_type =
+      Promise<Task, void, ThrowInPromiseConstructor, ThrowInInitialSuspend,
+              ThrowInGetReturnObject, ThrowInUnhandledException>;
+
+  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 <typename Task, typename T, bool ThrowInPromiseConstructor,
+          bool ThrowInInitialSuspend, bool ThrowInGetReturnObject,
+          bool ThrowInUnhandledException>
+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 <typename U> void return_value(U &&val) {
+    return_val->value = static_cast<U &&>(val);
+  }
+
+  template <typename U> std::suspend_never yield_value(U &&val) {
+    return_val->value = static_cast<U &&>(val);
+    return {};
+  }
+
+  void unhandled_exception() {
+    if constexpr (ThrowInUnhandledException) {
+      throw 1;
+    }
+  }
+
+  Task *return_val;
+};
+
+template <typename Task, bool ThrowInPromiseConstructor,
+          bool ThrowInInitialSuspend, bool ThrowInGetReturnObject,
+          bool ThrowInUnhandledException>
+struct Promise<Task, void, ThrowInPromiseConstructor, ThrowInInitialSuspend,
+               ThrowInGetReturnObject, ThrowInUnhandledException> {
+  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<int> returnOne() { co_return 1; }
+
+namespace function {
+
+namespace coreturn {
+
+Task<int> a_ShouldNotDiag(const int a, const int b) {
+  if (b == 0)
+    throw b;
+
+  co_return a / b;
+}
+
+Task<int> b_ShouldNotDiag(const int a, const int b) noexcept {
+  if (b == 0)
+    throw b;
+
+  co_return a / b;
+}
+
+Task<int> c_ShouldNotDiag(const int a, const int b) {
+  if (b == 0)
+    throw Evil{};
+
+  co_return a / b;
+}
+
+Task<int> 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<int, true> d_ShouldNotDiag(const int a, const int b) {
+  co_return a / b;
+}
+
+Task<int, true> 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<int, false, true> e_ShouldNotDiag(const int a, const int b) {
+  co_return a / b;
+}
+
+Task<int, false, true> 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<int, false, false, true> f_ShouldNotDiag(const int a, const int b) {
+  co_return a / b;
+}
+
+Task<int, false, false, true> 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<int, false, false, false, true> g_ShouldNotDiag(const int a, const int b) {
+  co_return a / b;
+}
+
+Task<int, false, false, false, true> 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<int, false, false, false, false, true> h_ShouldNotDiag(const int a,
+                                                            const int b) {
+  co_return a / b;
+}
+
+Task<int, false, false, false, false, true> 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<int> a_ShouldNotDiag(const int a, const int b) {
+  if (b == 0)
+    throw b;
+
+  co_yield a / b;
+}
+
+Task<int> b_ShouldNotDiag(const int a, const int b) noexcept {
+  if (b == 0)
+    throw b;
+
+  co_yield a / b;
+}
+
+Task<int> c_ShouldNotDiag(const int a, const int b) {
+  if (b == 0)
+    throw Evil{};
+
+  co_yield a / b;
+}
+
+Task<int> 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<int, true> d_ShouldNotDiag(const int a, const int b) {
+  co_yield a / b;
+}
+
+Task<int, true> 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<int, false, true> e_ShouldNotDiag(const int a, const int b) {
+  co_yield a / b;
+}
+
+Task<int, false, true> 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<int, false, false, true> f_ShouldNotDiag(const int a, const int b) {
+  co_yield a / b;
+}
+
+Task<int, false, false, true> 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<int, false, false, false, true> g_ShouldNotDiag(const int a, const int b) {
+  co_yield a / b;
+}
+
+Task<int, false, false, false, true> 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<int, false, false, false, false, true> h_ShouldNotDiag(const int a,
+                                                            const int b) {
+  co_yield a / b;
+}
+
+Task<int, false, false, false, false, true> 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<void> a_ShouldNotDiag(const int a, const int b) {
+  if (b == 0)
+    throw b;
+
+  co_await returnOne();
+}
+
+Task<void> b_ShouldNotDiag(const int a, const int b) noexcept {
+  if (b == 0)
+    throw b;
+
+  co_await returnOne();
+}
+
+Task<void> c_ShouldNotDiag(const int a, const int b) {
+  if (b == 0)
+    throw Evil{};
+
+  co_await returnOne();
+}
+
+Task<void> 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<void, true> d_ShouldNotDiag(const int a, const int b) {
+  co_await returnOne();
+}
+
+Task<void, true> 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<void, false, true> e_ShouldNotDiag(const int a, const int b) {
+  co_await returnOne();
+}
+
+Task<void, false, true> 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<void, false, false, true> f_ShouldNotDiag(const int a, const int b) {
+  co_await returnOne();
+}
+
+Task<void, false, false, true> 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<void, false, false, false, true> g_ShouldNotDiag(const int a,
+                                                      const int b) {
+  co_await returnOne();
+}
+
+Task<void, false, false, false, true> 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<void, false, false, false, false, true> h_ShouldNotDiag(const int a,
+                                                             const int b) {
+  co_await returnOne();
+}
+
+Task<void, false, false, false, false, true>
+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<int> {
+  if (b == 0)
+    throw b;
+
+  co_return a / b;
+};
+
+const auto b_ShouldNotDiag = [](const int a,
+                                const int b) noexcept -> Task<int> {
+  if (b == 0)
+    throw b;
+
+  co_return a / b;
+};
+
+const auto c_ShouldNotDiag = [](const int a, const int b) -> Task<int> {
+  if (b == 0)
+    throw Evil{};
+
+  co_return a / b;
+};
+
+const auto c_ShouldDiag = [](const int a, const int b) noexcept -> Task<int> {
+  // 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<int, true> {
+  co_return a / b;
+};
+
+const auto d_ShouldDiag = [](const int a,
+                             const int b) noexcept -> Task<int, true> {
+  // 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<int, false, true> {
+  co_return a / b;
+};
+
+const auto e_ShouldDiag = [](const int a,
+                             const int b) noexcept -> Task<int, false, true> {
+  // 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<int, false, false, true> {
+  co_return a / b;
+};
+
+const auto f_ShouldDiag =
+    [](const int a, const int b) noexcept -> Task<int, false, false, true> {
+  // 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<int, false, false, false, true> {
+  co_return a / b;
+};
+
+const auto g_ShouldDiag =
+    [](const int a,
+       const int b) noexcept -> Task<int, false, false, false, true> {
+  // 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<int, false, false, false, false, true> {
+  co_return a / b;
+};
+
+const auto h_ShouldDiag =
+    [](const int a,
+       const int b) noexcept -> Task<int, false, false, false, false, true> {
+  // 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<int> {
+  if (b == 0)
+    throw b;
+
+  co_yield a / b;
+};
+
+const auto b_ShouldNotDiag = [](const int a,
+                                const int b) noexcept -> Task<int> {
+  if (b == 0)
+    throw b;
+
+  co_yield a / b;
+};
+
+const auto c_ShouldNotDiag = [](const int a, const int b) -> Task<int> {
+  if (b == 0)
+    throw Evil{};
+
+  co_yield a / b;
+};
+
+const auto c_ShouldDiag = [](const int a, const int b) noexcept -> Task<int> {
+  // 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<int, true> {
+  co_yield a / b;
+};
+
+const auto d_ShouldDiag = [](const int a,
+                             const int b) noexcept -> Task<int, true> {
+  // 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<int, false, true> {
+  co_yield a / b;
+};
+
+const auto e_ShouldDiag = [](const int a,
+                             const int b) noexcept -> Task<int, false, true> {
+  // 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<int, false, false, true> {
+  co_yield a / b;
+};
+
+const auto f_ShouldDiag =
+    [](const int a, const int b) noexcept -> Task<int, false, false, true> {
+  // 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<int, false, false, false, true> {
+  co_yield a / b;
+};
+
+const auto g_ShouldDiag =
+    [](const int a,
+       const int b) noexcept -> Task<int, false, false, false, true> {
+  // 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<int, false, false, false, false, true> {
+  co_yield a / b;
+};
+
+const auto h_ShouldDiag =
+    [](const int a,
+       const int b) noexcept -> Task<int, false, false, false, false, true> {
+  // 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<void> {
+  if (b == 0)
+    throw b;
+
+  co_await returnOne();
+};
+
+const auto b_ShouldNotDiag = [](const int a,
+                                const int b) noexcept -> Task<void> {
+  if (b == 0)
+    throw b;
+
+  co_await returnOne();
+};
+
+const auto c_ShouldNotDiag = [](const int a, const int b) -> Task<void> {
+  if (b == 0)
+    throw Evil{};
+
+  co_await returnOne();
+};
+
+const auto c_ShouldDiag = [](const int a, const int b) noexcept -> Task<void> {
+  // 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<void, true> {
+  co_await returnOne();
+};
+
+const auto d_ShouldDiag = [](const int a,
+                             const int b) noexcept -> Task<void, true> {
+  // 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<void, false, true> {
+  co_await returnOne();
+};
+
+const auto e_ShouldDiag = [](const int a,
+                             const int b) noexcept -> Task<void, false, true> {
+  // 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<void, false, false, true> {
+  co_await returnOne();
+};
+
+const auto f_ShouldDiag =
+    [](const int a, const int b) noexcept -> Task<void, false, false, true> {
+  // 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<void, false, false, false, true> {
+  co_await returnOne();
+};
+
+const auto g_ShouldDiag =
+    [](const int a,
+       const int b) noexcept -> Task<void, false, false, false, true> {
+  // 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<void, false, false, false, false, true> {
+  co_await returnOne();
+};
+
+const auto h_ShouldDiag =
+    [](const int a,
+       const int b) noexcept -> Task<void, false, false, false, false, true> {
+  // 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
index 60fc3f3..8b4ef24 100644 (file)
@@ -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;
   }