https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123863

            Bug ID: 123863
           Summary: Ternary false branch with co_await incorrectly
                    evaluated when result is consumed
           Product: gcc
           Version: 15.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: markus at pilman dot ch
  Target Milestone: ---

We ran into a serious problem where the false branch of a ternary expression
was partially evaluated. The specific line, in the simplest form we managed to
write for a reproduction looks like this:

std::ignore = true ? 1 : co_await false_branch();

What I expect to happen is that 1 is assigned to std::ignore, what actually
happened is that the function false_branch was evaluated. In our more complex
production code, false_branch is a coroutine which doesn't suspend immediately,
so the code would run to the first `co_await` statement. In our case, this
caused undefined behavior.

The bug is relatively easy to reproduce, but I frankly didn't quite figure out
what exactly is triggering it. Replacing the above std::ignore with "int x" or
even "auto x" doesn't reproduce the bug. But having a function call (like
consume(true ? 1 : co_await false_branch)) also triggers the bug.

The only bug report I could find which is possibly related is bug 97452.

To compile the reproduction, the only argument I passed is `-std=c++20`. The
reproduction is simple enough to confirm the issue on godbolt.org -- there I
reproduced the problem successfully on ARM64 and X86-64. 

Clang seems to do the correct thing in all cases I tested.

The simplest self-contained reproduction I could come up with is this:

#include <coroutine>
#include <tuple> // for std::ignore

struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

struct Awaitable {
    bool await_ready() { return true; }
    void await_suspend(std::coroutine_handle<>) {}
    int await_resume() { return 0; }
};

bool bug_triggered = false;

Awaitable false_branch() {
    bug_triggered = true;
    return {};
}

Task test() {
    std::ignore = true ? 1 : co_await false_branch();  // false_branch() should
not be called
    co_return;
}

int main() {
    test();
    return bug_triggered ? 1 : 0;
}

Expected: Program returns 0 (false_branch never called)
Actual: Program returns 1 (false_branch incorrectly called)

Reply via email to