Skip to content

fix: make Task promise_type a non-aggregate to prevent Clang 21 SIGSEGV - #2580

Open
DreamDonghao wants to merge 1 commit into
drogonframework:masterfrom
DreamDonghao:bugfix/2579-task-promise-aggregate-init
Open

fix: make Task promise_type a non-aggregate to prevent Clang 21 SIGSEGV#2580
DreamDonghao wants to merge 1 commit into
drogonframework:masterfrom
DreamDonghao:bugfix/2579-task-promise-aggregate-init

Conversation

@DreamDonghao

Copy link
Copy Markdown

Fixes #2579

What

drogon::Task<T>::promise_type declares no constructor, which makes it an aggregate. Clang 21 aggregate-initializes the promise from the coroutine arguments, positionally: the first argument initializes the first member, std::optional<T> value. If the argument is implicitly convertible to T and the
conversion throws (most commonly nlohmann::json's implicit operator ValueType() on a mismatched value, throwing type_error.302), the exception is thrown during promise construction — before the coroutine body runs — and unwinding from the partially constructed coroutine frame crashes the process inside
_Unwind_Resume (SIGSEGV; the exception is never catchable).

How

Add a user-declared default constructor to Task<T>::promise_type (and Task<void>::promise_type, same latent hazard), making it a non-aggregate. Compilers then fall back to default-constructing the promise — exactly the behavior prescribed when no promise constructor takes the coroutine parameters, and the
behavior of pre-Clang-21 compilers. AsyncTask::promise_type has no data members and is unaffected.

Test

PromiseNotAggregateInitializedFromArgs in CoroutineTest.cc: a Task<std::string> whose argument is implicitly convertible to std::string and throws. Before this change the process segfaulted at the call, before the test body could run; after it, the coroutine completes and returns normally.

Verified on Apple clang 21.0.0 (clang-2100.1.1.101), macOS 26.5.2 (arm64), C++20. A standalone minimal repro and the full crash analysis are in #2579.

Clang 21 aggregate-initializes Task<T>::promise_type from the coroutine arguments because it declares no user-declared constructor. The first argument initializes the first member, std::optional<T> value, so any implicit conversion to T runs during promise construction, before the coroutine body. If that conversion throws (e.g. nlohmann::json's implicit operator ValueType() on a non-string value), the exception escapes the partially constructed coroutine frame and the process crashes inside _Unwind_Resume during unwinding.

Declare a default constructor for Task<T>::promise_type and Task<void>::promise_type so they are no longer aggregates: the promise is then default-constructed, which is the behavior prescribed when no promise constructor takes the coroutine parameters, and matches pre-Clang-21 compilers. AsyncTask::promise_type has no data members and is unaffected.

Add a regression test to CoroutineTest.cc: a Task<std::string> whose argument is implicitly convertible to std::string and throws used to segfault at the call, before the test body could run; it now completes normally.

Fixes drogonframework#2579
@an-tao
an-tao requested a review from marty1885 September 3, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SIGSEGV when exception thrown during coroutine promise aggregate initialization (Clang 21 / nlohmann::json)

2 participants