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

Reply via email to