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)