fix: make Task promise_type a non-aggregate to prevent Clang 21 SIGSEGV - #2580
Open
DreamDonghao wants to merge 1 commit into
Open
Conversation
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
marty1885
approved these changes
Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2579
What
drogon::Task<T>::promise_typedeclares 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 toTand theconversion throws (most commonly
nlohmann::json's implicitoperator ValueType()on a mismatched value, throwingtype_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(andTask<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 thebehavior of pre-Clang-21 compilers.
AsyncTask::promise_typehas no data members and is unaffected.Test
PromiseNotAggregateInitializedFromArgsinCoroutineTest.cc: aTask<std::string>whose argument is implicitly convertible tostd::stringand 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.