destructors not always properly run when a coroutine is suspended and then destroyed (original) (raw)
#include #include
struct coroutine { struct promise_type; std::coroutine_handle handle; ~coroutine() { handle.destroy(); } };
struct coroutine::promise_type { coroutine get_return_object() { return {std::coroutine_handle::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.