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

Reply via email to