Description
#include <coroutine>
#include <iostream>
struct coroutine {
struct promise_type;
std::coroutine_handle<promise_type> handle;
~coroutine() { handle.destroy(); }
};
struct coroutine::promise_type {
coroutine get_return_object() {
return {std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
struct Printy {
Printy(const char *name) : name(name) { std::cout << "Printy(" << name << ")\n"; }
Printy(const Printy&) = delete;
~Printy() { std::cout << "~Printy(" << name << ")\n"; }
const char *name;
};
int main() {
[] -> coroutine {
Printy a("a");
Printy arr[] = {
Printy("b"), Printy("c"),
(co_await std::suspend_always{}, Printy("d")),
Printy("e")
};
}();
}
When the coroutine is destroyed after being suspended, a
is destroyed, but arr[0]
and arr[1]
are not. Clang does not in general properly create cleanups for non-exceptional control flow that occurs in the middle of an expression / an initializer. At least array initialization is missing cleanups here, but it'd be worth checking through all exception-only cleanups because most of them are probably incorrect. It looks like there are 15 places where we currently push an EH-only cleanup:
CGCall.cpp: pushFullExprCleanup<DestroyUnpassedArg>(EHCleanup, Slot.getAddress(),
CGClass.cpp: CGF.EHStack.pushCleanup<CallBaseDtor>(EHCleanup, BaseClassDecl,
CGClass.cpp: EHStack.pushCleanup<CallDelegatingCtorDtor>(EHCleanup,
CGCoroutine.cpp: EHStack.pushCleanup<CallCoroEnd>(EHCleanup);
CGDecl.cpp: pushDestroy(EHCleanup, addr, type, getDestroyer(dtorKind), true);
CGDecl.cpp: pushFullExprCleanup<IrregularPartialArrayDestroy>(EHCleanup,
CGDecl.cpp: pushFullExprCleanup<RegularPartialArrayDestroy>(EHCleanup,
CGException.cpp: pushFullExprCleanup<FreeException>(EHCleanup, addr.getPointer());
CGExprAgg.cpp: CGF.pushDestroy(EHCleanup, LV.getAddress(CGF), CurField->getType(),
CGExprAgg.cpp: CGF.pushDestroy(EHCleanup, LV.getAddress(CGF), field->getType(),
CGExprCXX.cpp: .pushCleanupWithExtra<DirectCleanup>(EHCleanup,
CGExprCXX.cpp: .pushCleanupWithExtra<ConditionalCleanup>(EHCleanup,
ItaniumCXXABI.cpp: CGF.EHStack.pushCleanup<CallGuardAbort>(EHCleanup, guard);
MicrosoftCXXABI.cpp: CGF.EHStack.pushCleanup<ResetGuardBit>(EHCleanup, GuardAddr, GuardNum);
MicrosoftCXXABI.cpp: CGF.EHStack.pushCleanup<CallInitThreadAbort>(EHCleanup, GuardAddr);
This bug is not new with coroutines; the same thing happens with statement expressions:
int main() {
Printy arr[] = { Printy("a"), ({ return 0; Printy("b"); }) };
}
... never destroys arr[0]
. But it seems more pressing now that it's reachable from standard C++20 code.
Metadata
Metadata
Assignees
Type
Projects
Status
Done