Skip to content

fix: correct the workaround gate for symmetric transfer on MSVC - #437

Open
mvandeberg wants to merge 1 commit into
cppalliance:developfrom
mvandeberg:fix/msvc-symmetric-transfer-gate
Open

mvandeberg wants to merge 1 commit into
cppalliance:developfrom
mvandeberg:fix/msvc-symmetric-transfer-gate

Conversation

@mvandeberg

Copy link
Copy Markdown
Contributor

detail::symmetric_transfer applied the workaround to each compiler that defines _MSC_VER. This was too wide. MSVC has two different defects, and the new gate keeps the workaround for each one:

#if (BOOST_CAPY_WORKAROUND(_MSC_VER, < 1950) || \
     defined(_M_ARM64) || defined(_M_ARM64EC)) && \
    !defined(__clang__)

The first defect occurs on MSVC 19.34 to 19.44, on all architectures. The caller puts the hidden return slot of await_suspend on the coroutine frame, at __coro_frame_ptr$ + 0xC0. When await_suspend destroys that frame, the runtime then reads released memory. MSVC 19.50 puts the slot on the stack, thus the workaround retires there.

The second defect occurs on MSVC 19.51 for ARM64, in release builds only. A try region that spans the suspend point loses its handler. When the coroutine resumes from a different call stack, a throw in that region does not go to the adjacent catch. The exception goes to unhandled_exception of the promise. A catch(...) also does not get it, thus the runtime does not find the region at all. Debug builds are correct, and so are x64 builds at the same toolset. The gate includes _M_ARM64EC because that target also emits ARM64 code.

The gate excludes Clang. clang-cl and clang++ define _MSC_VER for ABI compatibility, but they emit a correct tail-call.

Two tests cover the defects:

  • test/unit/detail/await_suspend_helper.cpp destroys the coroutine frame in await_suspend and then transfers. The test unmaps each frame on destruction, thus every subsequent access gives a fault. A poison pattern is not sufficient, because symmetric_transfer moves the write to the frame to a point after the destruction.

  • test/unit/task.cpp awaits a handle-returning awaitable in a try block, resumes the parked handle from a different call stack, and then throws. The test watches for an escape through the error handler of run_async. A failure thus gives an assertion and not a terminate, which keeps the other results.

Refs #378

`detail::symmetric_transfer` applied the workaround to each compiler
that defines `_MSC_VER`. This was too wide. MSVC has two different
defects, and the new gate keeps the workaround for each one:

    #if (BOOST_CAPY_WORKAROUND(_MSC_VER, < 1950) || \
         defined(_M_ARM64) || defined(_M_ARM64EC)) && \
        !defined(__clang__)

The first defect occurs on MSVC 19.34 to 19.44, on all architectures.
The caller puts the hidden return slot of `await_suspend` on the
coroutine frame, at `__coro_frame_ptr$ + 0xC0`. When `await_suspend`
destroys that frame, the runtime then reads released memory. MSVC 19.50
puts the slot on the stack, thus the workaround retires there.

The second defect occurs on MSVC 19.51 for ARM64, in release builds
only. A `try` region that spans the suspend point loses its handler.
When the coroutine resumes from a different call stack, a `throw` in
that region does not go to the adjacent `catch`. The exception goes to
`unhandled_exception` of the promise. A `catch(...)` also does not get
it, thus the runtime does not find the region at all. Debug builds are
correct, and so are x64 builds at the same toolset. The gate includes
`_M_ARM64EC` because that target also emits ARM64 code.

The gate excludes Clang. `clang-cl` and `clang++` define `_MSC_VER` for
ABI compatibility, but they emit a correct tail-call.

Two tests cover the defects:

- `test/unit/detail/await_suspend_helper.cpp` destroys the coroutine
  frame in `await_suspend` and then transfers. The test unmaps each
  frame on destruction, thus every subsequent access gives a fault. A
  poison pattern is not sufficient, because `symmetric_transfer` moves
  the write to the frame to a point after the destruction.

- `test/unit/task.cpp` awaits a handle-returning awaitable in a `try`
  block, resumes the parked handle from a different call stack, and
  then throws. The test watches for an escape through the error handler
  of `run_async`. A failure thus gives an assertion and not a
  terminate, which keeps the other results.

Refs cppalliance#378
@cppalliance-bot

Copy link
Copy Markdown

An automated preview of the documentation is available at https://437.capy.prtest3.cppalliance.org/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-09-29 22:30:20 UTC

@cppalliance-bot

Copy link
Copy Markdown

GCOVR code coverage report https://437.capy.prtest3.cppalliance.org/gcovr/index.html
LCOV code coverage report https://437.capy.prtest3.cppalliance.org/genhtml/index.html
Coverage Diff Report https://437.capy.prtest3.cppalliance.org/diff-report/index.html

Build time: 2026-09-29 22:42:48 UTC

@codecov

codecov Bot commented Sep 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.09%. Comparing base (a372a6b) to head (506d5e1).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           develop     #437   +/-   ##
========================================
  Coverage    98.09%   98.09%           
========================================
  Files          130      130           
  Lines         6289     6289           
========================================
  Hits          6169     6169           
  Misses         120      120           
Flag Coverage Δ
linux 98.08% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
include/boost/capy/detail/await_suspend_helper.hpp 100.00% <ø> (ø)

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a372a6b...506d5e1. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants