Open
Description
Summary
Starting with Clang 17 at commit 54225c4, attempting to execute the provided reduced programs compiled with clang
using -O2
results in a crash at runtime.
Specifically, the crash occurs in the function void bx::await_suspend
when the result of q
, a null pointer, is dereferenced to call x()
.
I have confirmed that both reproducers run without triggering sanitizers in my environment (address, memory, undefined, etc.).
Reproducers
reduced-Version-A.cpp
-- https://godbolt.org/z/bxEf8Tfc6
#include <coroutine>
#include <memory>
struct br;
struct bs {
bs(std::coroutine_handle<br>);
int x() { return bt; }
void bu() { bt = 0; }
void t() { bv.resume(); }
std::coroutine_handle<br> bv;
int bt;
};
struct bw {
using promise_type = br;
bw(std::coroutine_handle<promise_type> h) : v(make_shared<bs>(h)) {}
void t() { v->t(); }
std::shared_ptr<bs> r() { return v; }
std::shared_ptr<bs> v;
};
struct bx {
int await_ready() { return 0; }
void await_resume() {}
void await_suspend(std::coroutine_handle<br>);
bw by;
};
struct br {
auto initial_suspend() { return std::suspend_always(); }
auto final_suspend() noexcept { return std::suspend_always(); }
auto get_return_object() {
return std::coroutine_handle<br>::from_promise(*this);
}
void unhandled_exception() {}
auto await_transform(bw h) { return bx(h); }
void return_void() {}
void u(bs *h) { v = h; }
bs *r() { return v; }
bs *v;
};
void bx::await_suspend(std::coroutine_handle<br> h) {
auto bi = h.promise();
auto q = bi.r();
auto s = by.r();
if (q->x())
s->bu();
by.t();
}
bs::bs(std::coroutine_handle<br> h) {
bt = 0;
bv = h;
auto &bz = h.promise();
bz.u(this);
}
bw ca() {
fprintf(stderr, "ca (co_return) called\n");
co_return;
}
bw cb() {
fprintf(stderr, "cb (co_await) called\n");
co_await ca();
}
bw cc = cb();
int main() {
bw cd(cc);
cd.t();
}
Here is the same reproducer reduced including headers from glibc 2.35 / glibcxx 3.4.30
reduced-Version-B.cpp
-- https://godbolt.org/z/fsaKof3EE
namespace std {
template <int a> struct b {
struct c {
int d[a];
};
};
inline namespace {
template <typename e> struct coroutine_traits : e {};
template <typename = void> struct coroutine_handle;
template <> struct coroutine_handle<> {};
template <typename f> struct coroutine_handle {
static coroutine_handle g(f &h) {
coroutine_handle i;
i.k = __builtin_coro_promise(&h, 0, 1);
return i;
}
static coroutine_handle from_address(void *h) {
coroutine_handle i;
i.k = h;
return i;
}
coroutine_handle<> j;
operator coroutine_handle<>() { return j; }
void l() { __builtin_coro_resume(k); }
f &m() {
void *aa = __builtin_coro_promise(k, 0, 0);
return *static_cast<f *>(aa);
}
void *k;
};
struct ab {
int await_ready() noexcept { return 0; }
void await_suspend(coroutine_handle<>) noexcept {}
void await_resume() noexcept {}
};
} // namespace
template <typename, typename> struct n;
template <template <typename...> class w, typename y, typename ac,
typename... ad>
struct n<w<ac, ad...>, y> {
using c = w<y>;
};
} // namespace std
void *operator new(unsigned long, void *);
template <typename ac, typename... ae> void af(ac *h, ae... o) {
new (h) ac(o...);
}
template <typename ac> struct ag {
ac *ah(int) { return static_cast<ac *>(operator new(sizeof(ac))); }
};
namespace std {
template <typename ai, typename y> using aj = n<ai, y>::c;
template <typename> struct p;
template <typename ac> struct p<ag<ac>> {
using ak = ag<ac>;
using al = ac;
using am = ac *;
using an = int;
static am ah(ak h, an) { return h.ah(0); }
template <typename y, typename... ae> static void ao(ak, y o, ae... ap) {
af(o, ap...);
}
};
} // namespace std
namespace std {
template <typename ai> struct aq {
using am = p<ai>::am;
using al = p<ai>::al;
aq(ai, am o) : ar(o) {}
al *as() { return ar; }
am ar;
};
template <typename ai> aq<ai> at(ai h) { return {h, p<ai>::ah(h, 0)}; }
} // namespace std
template <typename ac> struct au {
std::b<sizeof(ac)>::c av;
ac *ar() {
void *j = &av;
return static_cast<ac *>(j);
}
};
namespace std {
template <typename> struct aw;
template <typename ac> struct ax {
ax(ac) {}
};
template <typename ai> struct ay {
ai az;
};
template <typename ac, typename ai> struct ba {
struct bb : ax<ai> {
au<ac> av;
};
using bc = aj<ai, ba>;
template <typename... ae> ba(ai h, ae... o) : bd(h) {
p<ai>::ao(h, ar(), o...);
}
ac *ar() { return bd.av.ar(); }
bb bd;
};
struct be {
template <typename ac, typename ai, typename... ae>
be(ac *&h, ay<ai> o, ae... ap) {
typedef ba<ac, ai> bf;
typename bf::bc bg;
auto bh = at(bg);
bf *bj = bh.as();
auto bk = new (bj) bf(o.az, ap...);
h = bk->ar();
}
};
template <typename ac> struct z {
using bl = ac;
bl *operator->() {
bl *bm = static_cast<aw<ac> *>(this)->as();
return bm;
}
};
template <typename ac> struct aw : z<ac> {
using bl = ac;
bl *as() { return ar; }
template <typename ai, typename... ae> aw(ai h, ae... o) : bn(ar, h, o...) {}
bl *ar;
be bn;
};
template <typename ac> struct bo : aw<ac> {
template <typename ai, typename... ae> bo(ai h, ae... o) : aw<ac>(h, o...) {}
};
template <typename ac, typename ai, typename... ae> bo<ac> bp(ai h, ae... o) {
return bo<ac>(ay<ai>{h}, o...);
}
template <typename ac, typename... ae> bo<ac> bq(ae... h) {
return bp<ac>(ag<int>(), h...);
}
} // namespace std
struct br;
struct bs {
bs(std::coroutine_handle<br>);
int x() { return bt; }
void bu() { bt = 0; }
void t() { bv.l(); }
std::coroutine_handle<br> bv;
int bt;
};
struct bw {
using promise_type = br;
bw(std::coroutine_handle<promise_type> h) : v(bq<bs>(h)) {}
void t() { v->t(); }
std::bo<bs> r() { return v; }
std::bo<bs> v;
};
struct bx {
int await_ready() { return 0; }
void await_resume() {}
void await_suspend(std::coroutine_handle<br>);
bw by;
};
struct br {
auto initial_suspend() { return std::ab(); }
auto final_suspend() noexcept { return std::ab(); }
auto get_return_object() { return std::coroutine_handle<br>::g(*this); }
void unhandled_exception() {}
auto await_transform(bw h) { return bx(h); }
void return_void() {}
void u(bs *h) { v = h; }
bs *r() { return v; }
bs *v;
};
// When reduced including headers, the following is required under
// clang -O2 @ 54225c457a336b1609c6d064b2b606a9238a28b9, otherwise this
// entire function is optimized out which masks the problem since the crash
// occurs at `q->x()` (q is nullptr).
#pragma clang optimize off
void bx::await_suspend(std::coroutine_handle<br> h) {
auto bi = h.m();
auto q = bi.r();
auto s = by.r();
if (q->x())
s->bu();
by.t();
}
#pragma clang optimize on
bs::bs(std::coroutine_handle<br> h) {
bt = 0;
bv = h;
auto &bz = h.m();
bz.u(this);
}
bw ca() { co_return; }
bw cb() { co_await ca(); }
bw cc = cb();
int main() {
bw cd(cc);
cd.t();
}
Reproduction Steps
Latest Clang -- Crashes
$ clang++ --version
clang version 20.0.0git (https://github.com/llvm/llvm-project.git 381a803da253b75c8b7b10bb732e9e90925185e8)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: <redacted>
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
Segmentation fault (core dumped)
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
Segmentation fault (core dumped)
Clang At Bisected Commit -- Crashes
$ clang++ --version
clang version 17.0.0 (https://github.com/llvm/llvm-project.git 54225c457a336b1609c6d064b2b606a9238a28b9)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: <redacted>
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
Segmentation fault (core dumped)
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
Segmentation fault (core dumped)
Clang Before Bisected Commit -- Does not crash
$ clang++ --version
clang version 17.0.0 (https://github.com/llvm/llvm-project.git 32be3405f57f1e4d0ec0da943434113450583e89)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: <redacted>
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out