<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/57339>57339</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            [clang] Destroying a coroutine with a throwing promise_type::unhandled_exception() can call destructor of final_suspend() return-value even though final_suspend() was never called
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          lewissbaker
      </td>
    </tr>
</table>

<pre>
    See https://godbolt.org/z/9hn3s3a34

Expected output:
```
maybe_throwing()
final_suspend()
final_awaiter()
final_awaiter::await_ready()
final_awaiter::await_suspend()
coro.destroy()
~final_awaiter()
-------
maybe_throwing()
unhandled_exception()
coro.destroy()
caught X
```

Actual output:
```
maybe_throwing()
final_suspend()
final_awaiter()
final_awaiter::await_ready()
final_awaiter::await_suspend()
coro.destroy()
~final_awaiter()
-------
maybe_throwing()
unhandled_exception()
coro.destroy()
~final_awaiter()
caught X
```

If the coroutine runs to completion naturally and we suspend at the final-suspend point then `coroutine_handle::destroy()` correctly destroys the temporary object returned from `promise.final_suspend()`.

However, if the same coroutine can also complete with an exception, and the exception is rethrown from `unhandled_exception()` then the coroutine is also considered suspended at the final-suspend point.

Unfortunately, the `destroy()` call follows the same code-path for all final-suspend points which assumes that we are destroying at a point in the program inside the scope of the `co_await promise.final_suspend()` expression, even though we reached a final-suspend point that did not begin evaluating the `co_await promise.final_suspend()` expression.

The logic for suspending at the final-suspend point only stores a nullptr in the resume-function-pointer slot - which indicates that the coroutine is at a final-suspend-point and thus returns `true` from `coroutine_handle::done()`. It does not update the suspend-point index and `[clone .destroy()]` cannot therefore distinguish between the two final-suspend-points.

I think in the case that the `promise_type::unhandled_exception()` method is potentially throwing and thus the coroutine has two potential final-suspend-points, the compiler needs to generate code for the two final-suspend points that stores a unique suspend-point index for each and then uses the suspend-point index to dispatch to the correct path (one that destroys the `final_suspend()` temporaries, and one that does not) rather than looking at the resume-function-pointer.
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJztVktz6jYU_jWw0cAYG0xYsEhv7p1m3XamO0ZYB6xGSK4e8aW_vp9km0cCuWmny2YIYMk653ucc8zWiOP6FyJWe9-4UfE4yr_htTdia5SfGrvH1V_4X9W6cAUv5qPsaZQ9du9fvzdUeRLMBN8EH493u2XWv9LlgR-3tPG1Na3UCPgwylfdzk5qrjYuuIa0eL_BWy492fsbSFg8pouNJS6On7rzRrrKWDMV5Lw1VzFGy693kUy6vx9RDLrmWigSG_peUeOl0Z_JXPGwrz37_aag3ftj5QNX_2v_n2v_UeZP-PK8Y74mFqMHLzUxG7Rj3mDl0CiKKJjmPliu1JEBIGuJ9cIw7tPhlH8yLDZG6rSuGXKdAm86dp2-1zzKLOa36E6k6Ldciuzp0BjL7ZGZ7R_YZ5YARaOJd9YcYvwGn9LR9FaBlNn0kurPpqXXKNAXJjvWjh8uqVdcM67ciTuxVvoapNmFI1-SCPH0aZFJF4FFT_UJ2H07QTepc607YvS5tZOCLDj2bOgjoa8Y_qZ3xvoAv0gdI9R4CPneyw032c4oZVp3qYSgScNBGWFYuuV9QsfaWlZQxblwoHga4FAT3NLgHQo7IuZ9KciOKpzaW37AZeTXZa1MQ8zsBpyV6aqYfewqlG8sOdf7AVNjBoNaj0DQ31UdNbtTl0AmpGDaeLalPcDRK1eB-4j6X-O4suFXRFFmL6ukY3-qF-VevxiN2nfeIB6Q66BU4-0gHRah9WQXdBXraJKOEEIrkJj0hkjkqOB8b8n76vJvJeni9PUcXN9cLirgbaDIcKjmO21sNJ17jT1DWYP8UdrQCGDpXL7KBpj0PeWMk2jxU6UQhL2ZbIunrkp1DIUYlqAk6ku6aFOQroZ3vqW-i3xrbjFzV64841apXwZNK-7orNR5kmz8senpfdjCBzS8EVHYxnjSXqYBOQz2s6jXPtTcJbSnMzdxD60b55BUMFoTiTSV96TJRmFjr6byusl_aNXE71RVQcs_w20_YqTYN8Nw0yw4cnftAxJ4gVGBE_jec4wTnKX5AZmiq12zXQ50KHenl4ZRL8kNQ_Ycoq8q3MrAHvUQ1zWazLxc9NWdNpmOaT0ry1kxL1YP2VisC7EqVnzspVe0TiXI8ShePLGni_l1YVr3FDh7-w8qJT1T0rRNMoQKXsSBd0OEvv0mcRrR1VC7dXOLQtLxcZaikxgHq9ZvfhcDdthOUUS4UOp1-JgAf3yY4lJiiEe9vy2WRbEa1-tytlxUVPJsta3m5QPPi8VuvltV2WI5E2IhxopvSbmo2ijPNbUshcB36DeW6zzL8-whn8_yvMyz6Yovl1k-my_n-Swr8Xt8ntGBSzWNOOIP9rFdJ0jbsHfYVGhwd97EM0buUfspHeLzAEVwglpk3fIXsuOUfp3g_w1hNRPt">