Skip to content

Commit 61a87db

Browse files
Use pointer-by-value captures in coroutine test lambdas
Defense-in-depth against a GCC 14 bug where reference captures in coroutine lambdas are corrupted in the coroutine frame. The root cause is fixed in CoroTaskRunner::init() (heap storage), but using explicit pointer captures avoids any residual risk. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 00d166c commit 61a87db

File tree

2 files changed

+39
-26
lines changed

2 files changed

+39
-26
lines changed

src/test/core/Coroutine_test.cpp

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ class Coroutine_test : public beast::unit_test::suite
4040
}
4141
};
4242

43+
// NOTE: All coroutine lambdas passed to postCoroTask use explicit
44+
// pointer-by-value captures instead of [&] to work around a GCC 14
45+
// bug where reference captures in coroutine lambdas are corrupted
46+
// in the coroutine frame.
47+
4348
void
4449
correct_order()
4550
{
@@ -56,11 +61,11 @@ class Coroutine_test : public beast::unit_test::suite
5661
gate g1, g2;
5762
std::shared_ptr<JobQueue::CoroTaskRunner> c;
5863
env.app().getJobQueue().postCoroTask(
59-
jtCLIENT, "CoroTest", [&](auto runner) -> CoroTask<void> {
60-
c = runner;
61-
g1.signal();
64+
jtCLIENT, "CoroTest", [cp = &c, g1p = &g1, g2p = &g2](auto runner) -> CoroTask<void> {
65+
*cp = runner;
66+
g1p->signal();
6267
co_await runner->suspend();
63-
g2.signal();
68+
g2p->signal();
6469
co_return;
6570
});
6671
BEAST_EXPECT(g1.wait_for(5s));
@@ -84,14 +89,14 @@ class Coroutine_test : public beast::unit_test::suite
8489

8590
gate g;
8691
env.app().getJobQueue().postCoroTask(
87-
jtCLIENT, "CoroTest", [&](auto runner) -> CoroTask<void> {
92+
jtCLIENT, "CoroTest", [gp = &g](auto runner) -> CoroTask<void> {
8893
// Schedule a resume before suspending. The posted job
8994
// cannot actually call resume() until the current resume()
9095
// releases CoroTaskRunner::mutex_, which only happens after
9196
// the coroutine suspends at co_await.
9297
runner->post();
9398
co_await runner->suspend();
94-
g.signal();
99+
gp->signal();
95100
co_return;
96101
});
97102
BEAST_EXPECT(g.wait_for(5s));
@@ -126,20 +131,23 @@ class Coroutine_test : public beast::unit_test::suite
126131

127132
for (int i = 0; i < N; ++i)
128133
{
129-
jq.postCoroTask(jtCLIENT, "CoroTest", [&, id = i](auto runner) -> CoroTask<void> {
130-
a[id] = runner;
131-
g.signal();
132-
co_await runner->suspend();
133-
134-
this->BEAST_EXPECT(*lv == -1);
135-
*lv = id;
136-
this->BEAST_EXPECT(*lv == id);
137-
g.signal();
138-
co_await runner->suspend();
139-
140-
this->BEAST_EXPECT(*lv == id);
141-
co_return;
142-
});
134+
jq.postCoroTask(
135+
jtCLIENT,
136+
"CoroTest",
137+
[this, ap = &a, gp = &g, lvp = &lv, id = i](auto runner) -> CoroTask<void> {
138+
(*ap)[id] = runner;
139+
gp->signal();
140+
co_await runner->suspend();
141+
142+
this->BEAST_EXPECT(**lvp == -1);
143+
**lvp = id;
144+
this->BEAST_EXPECT(**lvp == id);
145+
gp->signal();
146+
co_await runner->suspend();
147+
148+
this->BEAST_EXPECT(**lvp == id);
149+
co_return;
150+
});
143151
BEAST_EXPECT(g.wait_for(5s));
144152
a[i]->join();
145153
}

src/test/core/JobQueue_test.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class JobQueue_test : public beast::unit_test::suite
4343
}
4444
}
4545

46+
// NOTE: All coroutine lambdas passed to postCoroTask use explicit
47+
// pointer-by-value captures instead of [&] to work around a GCC 14
48+
// bug where reference captures in coroutine lambdas are corrupted
49+
// in the coroutine frame.
50+
4651
void
4752
testPostCoroTask()
4853
{
@@ -53,8 +58,8 @@ class JobQueue_test : public beast::unit_test::suite
5358
// Test repeated post()s until the coroutine completes.
5459
std::atomic<int> yieldCount{0};
5560
auto const runner = jQueue.postCoroTask(
56-
jtCLIENT, "PostCoroTest1", [&yieldCount](auto runner) -> CoroTask<void> {
57-
while (++yieldCount < 4)
61+
jtCLIENT, "PostCoroTest1", [ycp = &yieldCount](auto runner) -> CoroTask<void> {
62+
while (++(*ycp) < 4)
5863
co_await runner->suspend();
5964
co_return;
6065
});
@@ -81,8 +86,8 @@ class JobQueue_test : public beast::unit_test::suite
8186
// Test repeated resume()s until the coroutine completes.
8287
int yieldCount{0};
8388
auto const runner = jQueue.postCoroTask(
84-
jtCLIENT, "PostCoroTest2", [&yieldCount](auto runner) -> CoroTask<void> {
85-
while (++yieldCount < 4)
89+
jtCLIENT, "PostCoroTest2", [ycp = &yieldCount](auto runner) -> CoroTask<void> {
90+
while (++(*ycp) < 4)
8691
co_await runner->suspend();
8792
co_return;
8893
});
@@ -118,8 +123,8 @@ class JobQueue_test : public beast::unit_test::suite
118123
// Not recommended for the faint of heart...
119124
bool unprotected;
120125
auto const runner = jQueue.postCoroTask(
121-
jtCLIENT, "PostCoroTest3", [&unprotected](auto) -> CoroTask<void> {
122-
unprotected = false;
126+
jtCLIENT, "PostCoroTest3", [up = &unprotected](auto) -> CoroTask<void> {
127+
*up = false;
123128
co_return;
124129
});
125130
BEAST_EXPECT(runner == nullptr);

0 commit comments

Comments
 (0)