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

@cppalliance-bot

cppalliance-bot commented Sep 29, 2026 •

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-10-20 19:59:25 UTC

@cppalliance-bot

cppalliance-bot commented Sep 29, 2026 •

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-10-20 20:12:57 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.45%. Comparing base (7eed844) to head (2904dd9).
⚠️ Report is 2 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #437      +/-   ##
===========================================
+ Coverage    98.39%   98.45%   +0.05%     
===========================================
  Files          137      137              
  Lines         7297     7293       -4     
===========================================
  Hits          7180     7180              
+ Misses         117      113       -4     
Flag Coverage Δ
linux 98.40% <ø> (+0.11%) ⬆️

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% <ø> (ø)
include/boost/capy/ex/frame_allocator.hpp 100.00% <ø> (ø)

... and 12 files 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 7eed844...2904dd9. Read the comment docs.

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

@mvandeberg
mvandeberg force-pushed the fix/msvc-symmetric-transfer-gate branch 2 times, most recently from 2dd9f79 to 17fecda Compare October 20, 2026 17:38
detail::symmetric_transfer resumed on the current stack for every
compiler that defines _MSC_VER. Only two MSVC defects need it:

- MSVC 19.34 through 19.44, every target: the returned handle is
  stored on the coroutine frame, so destroying the frame in
  await_suspend and then transferring touches freed memory.
  MSVC 19.50 fixes this.
- MSVC 19.50 and later on ARM64, release builds: a try region that
  spans the suspend point loses its handler when the coroutine is
  resumed from another call stack.

clang-cl and clang++ targeting Windows define _MSC_VER but have
neither defect.

Add BOOST_CAPY_MSVC, defined only on Microsoft's compiler, so MSVC
workarounds exclude Clang without a separate check; the frame
allocator gate uses it too. The two defects combine into
BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND.

Add a regression test for each defect.

Refs cppalliance#378
@mvandeberg
mvandeberg force-pushed the fix/msvc-symmetric-transfer-gate branch from 17fecda to 2904dd9 Compare October 20, 2026 19:52

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