| Issue |
63818
|
| Summary |
destructors not always properly run when a coroutine is suspended and then destroyed
|
| Labels |
c++20,
miscompilation,
coroutines
|
| Assignees |
|
| Reporter |
zygoloid
|
[Example](https://godbolt.org/z/zezvTqY8v):
```c++
#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:
```c++
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.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs