LCOV - code coverage report
Current view: top level - capy/detail - await_suspend_helper.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 66.7 % 6 4 2
Test Date: 2026-03-04 22:59:25 Functions: 16.1 % 31 5 26

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3                 : // Copyright (c) 2026 Steve Gerbino
       4                 : //
       5                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7                 : //
       8                 : // Official repository: https://github.com/cppalliance/capy
       9                 : //
      10                 : 
      11                 : #ifndef BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
      12                 : #define BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
      13                 : 
      14                 : #include <coroutine>
      15                 : #include <boost/capy/ex/io_env.hpp>
      16                 : 
      17                 : #include <type_traits>
      18                 : 
      19                 : namespace boost {
      20                 : namespace capy {
      21                 : namespace detail {
      22                 : 
      23                 : /** Perform symmetric transfer, working around an MSVC codegen bug.
      24                 : 
      25                 :     MSVC stores the `std::coroutine_handle<>` returned from
      26                 :     `await_suspend` in a hidden `__$ReturnUdt$` variable located
      27                 :     on the coroutine frame. When another thread resumes or destroys
      28                 :     the frame between the store and the read-back for the
      29                 :     symmetric-transfer tail-call, the read hits freed memory.
      30                 : 
      31                 :     This occurs in two scenarios:
      32                 : 
      33                 :     @li `await_suspend` calls `h.destroy()` then returns a handle
      34                 :         (e.g. `when_all_runner` and `when_any_runner` final_suspend).
      35                 :         The return value is written to the now-destroyed frame.
      36                 : 
      37                 :     @li `await_suspend` hands the continuation to another thread
      38                 :         via `executor::dispatch()`, which may resume the parent.
      39                 :         The parent can destroy this frame before the runtime reads
      40                 :         `__$ReturnUdt$` (e.g. `dispatch_trampoline` final_suspend).
      41                 : 
      42                 :     On MSVC this function calls `h.resume()` on the current stack
      43                 :     and returns `void`, causing unconditional suspension. The
      44                 :     trade-off is O(n) stack growth instead of O(1) tail-calls.
      45                 : 
      46                 :     On other compilers the handle is returned directly for proper
      47                 :     symmetric transfer.
      48                 : 
      49                 :     Callers must use `auto` return type on their `await_suspend`
      50                 :     so the return type adapts per platform.
      51                 : 
      52                 :     @param h The coroutine handle to transfer to.
      53                 : */
      54                 : #ifdef _MSC_VER
      55                 : inline void symmetric_transfer(std::coroutine_handle<> h) noexcept
      56                 : {
      57                 :     h.resume();
      58                 : }
      59                 : #else
      60                 : inline std::coroutine_handle<>
      61 HIT        2878 : symmetric_transfer(std::coroutine_handle<> h) noexcept
      62                 : {
      63            2878 :     return h;
      64                 : }
      65                 : #endif
      66                 : 
      67                 : // Helper to normalize await_suspend return types to std::coroutine_handle<>
      68                 : template<typename Awaitable>
      69               7 : std::coroutine_handle<> call_await_suspend(
      70                 :     Awaitable* a,
      71                 :     std::coroutine_handle<> h,
      72                 :     io_env const* env)
      73                 : {
      74                 :     using R = decltype(a->await_suspend(h, env));
      75                 :     if constexpr (std::is_void_v<R>)
      76                 :     {
      77 MIS           0 :         a->await_suspend(h, env);
      78               0 :         return std::noop_coroutine();
      79                 :     }
      80                 :     else if constexpr (std::is_same_v<R, bool>)
      81                 :     {
      82                 :         if(a->await_suspend(h, env))
      83                 :             return std::noop_coroutine();
      84                 :         return h;
      85                 :     }
      86                 :     else
      87                 :     {
      88 HIT           7 :         return a->await_suspend(h, env);
      89                 :     }
      90                 : }
      91                 : 
      92                 : } // namespace detail
      93                 : } // namespace capy
      94                 : } // namespace boost
      95                 : 
      96                 : #endif
        

Generated by: LCOV version 2.3