| Issue |
182584
|
| Summary |
[clang-cl][coroutines][windows] Use-after-free when catching exception from co_await in loop (e.what() segfaults)
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
agarcin
|
## Summary
When `co_await`ing a coroutine that throws, the exception is caught successfully, but reading the caught exception (`e.what()`) crashes with an access violation / segfault in a looped scenario.
- The crash occurs consistently in a `for` loop.
- A single `try { co_await task2; } catch (...) { ... }` (no loop) does **not** crash.
- The crash happens at `e.what()` inside the catch block.
- The crash happens only in **debug** mode.
## Minimal Reproducer
```cpp
#include <Windows.h>
#include <coroutine>
#include <exception>
#include <iostream>
#include <stdexcept>
struct Task2 {
struct promise_type {
std::exception_ptr* exception_;
auto get_return_object() {
return Task2{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { *exception_ = std::current_exception(); }
};
Task2(std::coroutine_handle<promise_type> h)
: handle_(h) {
h.promise().exception_ = &exception_;
}
~Task2() {
std::cout << "Task2 destroyed" << std::endl;
if (handle_) {
handle_.destroy();
}
}
std::coroutine_handle<promise_type> handle_;
std::exception_ptr exception_;
bool await_ready() { return true; }
void await_suspend(std::coroutine_handle<>) {}
void await_resume() {
if (exception_) {
std::rethrow_exception(exception_);
}
}
};
struct Task1 {
struct promise_type {
auto get_return_object() {
return Task1{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
Task1(std::coroutine_handle<promise_type> h)
: handle_(h) {}
~Task1() {
std::cout << "Task1 destroyed" << std::endl;
if (handle_) {
handle_.destroy();
}
}
std::coroutine_handle<promise_type> handle_;
};
Task2 FThrow() {
throw std::runtime_error("TEST_EXCEPTION");
co_return;
}
Task1 Fmain() {
for (int i = 0; i < 10; ++i) {
std::cout << "Main promise iteration " << i << std::endl;
auto task2 = FThrow();
try {
co_await task2;
} catch (std::exception const& e) {
std::cout << "TEST " << e.what() << std::endl; // crash here
}
}
co_return;
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
auto task = Fmain();
return 0;
}
```
## Observed Behavior
Output:
```text
Main promise iteration 0
TEST
```
Then crash when evaluating `e.what()`.
Important observation: `Task2 destroyed` / `Task1 destroyed` are not printed before the crash.
## Expected Behavior
No crash. Expected output should include:
```text
Main promise iteration 0
TEST TEST_EXCEPTION
Task2 destroyed
Main promise iteration 1
...
```
## Environment
- OS: Windows 11
- Language mode: C++20
- Frontend: clang-cl (reproduced across multiple LLVM versions)
- STL: MSVC STL 19.42 (VS 2022)
- Debug config: `/Od /RTC1 /MDd`, page heap enabled
## Build Commands
```bat
clang-cl.exe /nologo -TP -DNOMINMAX /DWIN32 /D_WINDOWS /EHsc /Zi /Ob0 /Od /RTC1 -clang:-std=c++20 -MDd /W4 -clang:-DDEBUG -clang:-Wall -clang:-Wextra -clang:-Wpedantic -clang:-Wcast-align -clang:-Waddress-of-packed-member -clang:-Werror -clang:-ftemplate-backtrace-limit=0 -clang:-O0 -clang:-g -m64 /showIncludes /Fobuild\promise_test.cpp.obj /Fdbuild\ -c -- promise_test.cpp
```
```bat
lld-link.exe /nologo build\promise_test.cpp.obj /out:build\promise_test.exe /implib:build\promise_test.lib /pdb:build\promise_test.pdb /version:0.0 /machine:x64 /debug /INCREMENTAL /subsystem:windows kernel32.lib
```
## Additional Experiment
The same crash reproduces when throwing a by-reference exception object passed into the coroutine, suggesting this is not specific to a temporary exception originating directly in `throw std::runtime_error(...)`.
```cpp
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
std::runtime_error e{"TEST_EXCEPTION"};
auto task = Fmain(e);
return 0;
}
Task2 FThrow(std::runtime_error const& e) {
throw e;
co_return;
}
```
## Notes
- Debugger inspection shows `exception_` (`std::exception_ptr`) itself appears non-null/valid around `rethrow_exception`; crash occurs when using the caught reference.
- Because the crash only occurs when the co_await is inside a loop, this strongly hints at a frame lifetime or context‑switching defect rather than a basic null pointer issue.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs