Skip to content

Commit 150ee20

Browse files
committed
Rewrite frame_cb tests to avoid coroutine_handle UB
The original tests used from_address to create a coroutine_handle from a fake frame_cb struct. This violates [coroutine.handle.export.import] and LLVM exploits the UB at -O1+, compiling the tests to `unreachable`. Tests 1-3: call frame_cb function pointers directly instead of routing through coroutine_handle. Tests 4-5: replace fake frame + from_address with real task<int> coroutines that co_await custom IoAwaitables, using run_blocking (sync) and run_async + thread_pool (async).
1 parent fcd2854 commit 150ee20

File tree

1 file changed

+78
-109
lines changed

1 file changed

+78
-109
lines changed

test/unit/ex/frame_cb.cpp

Lines changed: 78 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
#include <boost/capy/concept/io_awaitable.hpp>
1111
#include <boost/capy/detail/await_suspend_helper.hpp>
1212
#include <boost/capy/ex/io_env.hpp>
13+
#include <boost/capy/ex/run_async.hpp>
1314
#include <boost/capy/ex/thread_pool.hpp>
1415
#include <boost/capy/io_result.hpp>
16+
#include <boost/capy/task.hpp>
17+
#include <boost/capy/test/run_blocking.hpp>
1518

1619
#include "test/unit/test_helpers.hpp"
1720

@@ -45,10 +48,7 @@ struct frame_cb_test
4548
cb.destroy = +[](detail::frame_cb*) {};
4649
cb.data = &called;
4750

48-
auto h = std::coroutine_handle<>::from_address(
49-
static_cast<void*>(&cb));
50-
51-
h.resume();
51+
cb.resume(&cb);
5252
BOOST_TEST(called);
5353
}
5454

@@ -63,10 +63,7 @@ struct frame_cb_test
6363
};
6464
cb.data = &destroy_called;
6565

66-
auto h = std::coroutine_handle<>::from_address(
67-
static_cast<void*>(&cb));
68-
69-
h.destroy();
66+
cb.destroy(&cb);
7067
BOOST_TEST(destroy_called);
7168
}
7269

@@ -81,133 +78,105 @@ struct frame_cb_test
8178
cb.destroy = +[](detail::frame_cb*) {};
8279
cb.data = &value;
8380

84-
auto h = std::coroutine_handle<>::from_address(
85-
static_cast<void*>(&cb));
86-
87-
h.resume();
81+
cb.resume(&cb);
8882
BOOST_TEST_EQ(value, 42);
8983
}
9084

91-
void
92-
testWithIoAwaitable()
85+
// IoAwaitable that resumes synchronously and returns a value
86+
struct sync_awaitable
9387
{
94-
struct test_awaitable
95-
{
96-
int* result;
97-
98-
bool await_ready() const noexcept
99-
{
100-
return false;
101-
}
102-
103-
std::coroutine_handle<>
104-
await_suspend(
105-
std::coroutine_handle<> h,
106-
io_env const*) noexcept
107-
{
108-
h.resume();
109-
return std::noop_coroutine();
110-
}
111-
112-
io_result<> await_resume() noexcept
113-
{
114-
*result = 99;
115-
return {};
116-
}
117-
};
118-
119-
static_assert(IoAwaitable<test_awaitable>);
120-
121-
int result = 0;
122-
bool resumed = false;
88+
int value;
12389

124-
struct state
90+
bool await_ready() const noexcept
12591
{
126-
test_awaitable* aw;
127-
bool* resumed;
128-
};
129-
130-
state st{nullptr, &resumed};
131-
test_awaitable aw{&result};
132-
st.aw = &aw;
92+
return false;
93+
}
13394

134-
detail::frame_cb cb;
135-
cb.resume = +[](detail::frame_cb* p) {
136-
auto* s = static_cast<state*>(p->data);
137-
(void)s->aw->await_resume();
138-
*s->resumed = true;
139-
};
140-
cb.destroy = +[](detail::frame_cb*) {};
141-
cb.data = &st;
95+
std::coroutine_handle<>
96+
await_suspend(
97+
std::coroutine_handle<> h,
98+
io_env const*) noexcept
99+
{
100+
return h;
101+
}
142102

143-
int dispatch_count = 0;
144-
test_executor ex(dispatch_count);
145-
io_env env{ex, {}};
103+
io_result<int> await_resume() noexcept
104+
{
105+
return {std::error_code{}, value};
106+
}
107+
};
146108

147-
auto h = std::coroutine_handle<>::from_address(
148-
static_cast<void*>(&cb));
109+
static_assert(IoAwaitable<sync_awaitable>);
149110

150-
detail::call_await_suspend(&aw, h, &env);
111+
static task<int>
112+
await_sync(int v)
113+
{
114+
auto [ec, result] = co_await sync_awaitable{v};
115+
co_return result;
116+
}
151117

152-
BOOST_TEST(resumed);
118+
void
119+
testWithIoAwaitable()
120+
{
121+
int result = 0;
122+
test::run_blocking(
123+
[&](int v) { result = v; })(
124+
await_sync(99));
153125
BOOST_TEST_EQ(result, 99);
154126
}
155127

156-
void
157-
testWithAsyncAwaitable()
128+
// IoAwaitable that posts to executor (async resume)
129+
struct async_awaitable
158130
{
159-
thread_pool pool(1);
160-
std::latch done(1);
161-
bool resumed = false;
131+
int value;
162132

163-
struct state
133+
bool await_ready() const noexcept
164134
{
165-
bool* resumed;
166-
std::latch* done;
167-
};
135+
return false;
136+
}
168137

169-
state st{&resumed, &done};
170-
171-
detail::frame_cb cb;
172-
cb.resume = +[](detail::frame_cb* p) {
173-
auto* s = static_cast<state*>(p->data);
174-
*s->resumed = true;
175-
s->done->count_down();
176-
};
177-
cb.destroy = +[](detail::frame_cb*) {};
178-
cb.data = &st;
138+
std::coroutine_handle<>
139+
await_suspend(
140+
std::coroutine_handle<> h,
141+
io_env const* env) noexcept
142+
{
143+
env->executor.post(h);
144+
return std::noop_coroutine();
145+
}
179146

180-
struct post_awaitable
147+
io_result<int> await_resume() noexcept
181148
{
182-
bool await_ready() const noexcept
183-
{
184-
return false;
185-
}
186-
187-
std::coroutine_handle<>
188-
await_suspend(
189-
std::coroutine_handle<> h,
190-
io_env const* env) noexcept
191-
{
192-
env->executor.post(h);
193-
return std::noop_coroutine();
194-
}
195-
196-
void await_resume() noexcept {}
197-
};
149+
return {std::error_code{}, value};
150+
}
151+
};
198152

199-
static_assert(IoAwaitable<post_awaitable>);
153+
static_assert(IoAwaitable<async_awaitable>);
200154

201-
post_awaitable aw;
202-
io_env env{pool.get_executor(), {}};
155+
static task<int>
156+
await_async(int v)
157+
{
158+
auto [ec, result] = co_await async_awaitable{v};
159+
co_return result;
160+
}
203161

204-
auto h = std::coroutine_handle<>::from_address(
205-
static_cast<void*>(&cb));
162+
void
163+
testWithAsyncAwaitable()
164+
{
165+
thread_pool pool(1);
166+
std::latch done(1);
167+
int result = 0;
206168

207-
detail::call_await_suspend(&aw, h, &env);
169+
run_async(pool.get_executor(),
170+
[&](int v) {
171+
result = v;
172+
done.count_down();
173+
},
174+
[&](std::exception_ptr) {
175+
done.count_down();
176+
})(await_async(99));
208177

209178
done.wait();
210-
BOOST_TEST(resumed);
179+
BOOST_TEST_EQ(result, 99);
211180
}
212181

213182
void

0 commit comments

Comments
 (0)