Skip to content

Commit cef905c

Browse files
authored
Avoid GCC ubsan contexpr bug and add asan CI (#2288)
* Avoid GCC ubsan contexpr bug * Create sanitizers.yml * Update jmespath.hpp * asan testing and revert cx_rfind * properly delete thread pointer * avoid circular reference memory leaks
1 parent 393b06e commit cef905c

File tree

5 files changed

+186
-67
lines changed

5 files changed

+186
-67
lines changed

.github/workflows/sanitizers.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: sanitizers
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- feature/*
8+
paths-ignore:
9+
- '**/*.md'
10+
- 'docs/**'
11+
pull_request:
12+
branches:
13+
- main
14+
paths-ignore:
15+
- '**/*.md'
16+
- 'docs/**'
17+
workflow_dispatch:
18+
19+
jobs:
20+
asan:
21+
runs-on: ubuntu-24.04
22+
container:
23+
image: gcc:15
24+
25+
strategy:
26+
fail-fast: false
27+
matrix:
28+
build_type: [Debug]
29+
std: [23]
30+
31+
env:
32+
CC: gcc
33+
CXX: g++
34+
35+
steps:
36+
- name: Install build dependencies
37+
env:
38+
DEBIAN_FRONTEND: noninteractive
39+
run: |
40+
apt-get update
41+
apt-get install -y --no-install-recommends \
42+
ca-certificates \
43+
cmake \
44+
git \
45+
libssl-dev \
46+
python3
47+
update-ca-certificates
48+
49+
- uses: actions/checkout@v4
50+
51+
- name: Configure CMake
52+
run: |
53+
cmake -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" \
54+
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
55+
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
56+
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-sanitize-recover=all"
57+
58+
- name: Build
59+
run: cmake --build "$GITHUB_WORKSPACE/build" -j $(nproc)
60+
61+
- name: Test
62+
env:
63+
ASAN_OPTIONS: detect_leaks=1
64+
run: ctest --output-on-failure --test-dir "$GITHUB_WORKSPACE/build"

include/glaze/core/reflect.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2561,7 +2561,6 @@ namespace glz
25612561

25622562
if constexpr (K > 0) {
25632563
using keys_t = keys_wrapper<variant_deduction_keys<T>>;
2564-
constexpr auto& HashInfo = hash_info<keys_t>;
25652564

25662565
// Populate bit arrays - for each key, set bits for variant types that have it
25672566
for_each<std::variant_size_v<T>>([&]<auto I>() {
@@ -2570,10 +2569,12 @@ namespace glz
25702569
using X = std::conditional_t<is_memory_object<V>, memory_type<V>, V>;
25712570
constexpr auto Size = reflect<X>::size;
25722571
if constexpr (Size > 0) {
2572+
constexpr auto& HashInfo = hash_info<keys_t>;
25732573
for (size_t J = 0; J < Size; ++J) {
25742574
sv key = reflect<X>::keys[J];
2575-
const auto index = decode_hash_with_size<JSON, keys_t, HashInfo, HashInfo.type>::op(
2576-
key.data(), key.data() + key.size(), key.size());
2575+
const auto index =
2576+
decode_hash_with_size<JSON, keys_t, HashInfo, HashInfo.type>::op(
2577+
key.data(), key.data() + key.size(), key.size());
25772578
if (index < K) {
25782579
bits[index][I] = true;
25792580
}

include/glaze/ext/glaze_asio.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@ namespace glz
717717
thread.join();
718718
}
719719
}
720+
delete ptr;
720721
});
721722

722723
threads->reserve(concurrency - uint32_t(run_on_main_thread));

include/glaze/net/http_server.hpp

Lines changed: 81 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2463,74 +2463,110 @@ namespace glz
24632463
namespace streaming_utils
24642464
{
24652465
// Create a periodic data sender (works with both HTTP and HTTPS)
2466+
// Uses enable_shared_from_this to avoid circular reference memory leaks
24662467
template <typename T>
24672468
void send_periodic_data(std::shared_ptr<streaming_connection_interface> conn, std::function<T()> data_generator,
24682469
std::chrono::milliseconds interval, size_t max_events = 0)
24692470
{
2470-
auto counter = std::make_shared<size_t>(0);
24712471
if (!conn || !conn->is_open()) return;
2472-
auto timer = std::make_shared<asio::steady_timer>(conn->get_executor());
24732472

2474-
// Use shared_ptr to safely handle recursive lambda calls and avoid compiler-specific segfaults
2475-
auto send_next = std::make_shared<std::function<void()>>();
2476-
*send_next = [conn, timer, counter, data_generator, interval, max_events, send_next]() mutable {
2477-
if (!conn->is_open() || (max_events > 0 && *counter >= max_events)) {
2478-
conn->close();
2479-
return;
2480-
}
2473+
struct periodic_sender : std::enable_shared_from_this<periodic_sender>
2474+
{
2475+
std::shared_ptr<streaming_connection_interface> conn;
2476+
std::shared_ptr<asio::steady_timer> timer;
2477+
std::function<T()> data_generator;
2478+
std::chrono::milliseconds interval;
2479+
size_t max_events;
2480+
size_t counter = 0;
2481+
2482+
periodic_sender(std::shared_ptr<streaming_connection_interface> c, std::function<T()> gen,
2483+
std::chrono::milliseconds intv, size_t max_ev)
2484+
: conn(std::move(c)),
2485+
timer(std::make_shared<asio::steady_timer>(conn->get_executor())),
2486+
data_generator(std::move(gen)),
2487+
interval(intv),
2488+
max_events(max_ev)
2489+
{}
2490+
2491+
void send_next()
2492+
{
2493+
if (!conn->is_open() || (max_events > 0 && counter >= max_events)) {
2494+
conn->close();
2495+
return;
2496+
}
24812497

2482-
try {
2483-
T data = data_generator();
2484-
conn->send_json_event(data, "data", std::to_string(*counter), [=](std::error_code ec) mutable {
2485-
if (!ec) {
2486-
(*counter)++;
2487-
timer->expires_after(interval);
2488-
timer->async_wait([send_next, timer, counter](std::error_code) { (*send_next)(); });
2489-
}
2490-
else {
2491-
conn->close();
2492-
}
2493-
});
2494-
}
2495-
catch (const std::exception&) {
2496-
conn->close();
2498+
try {
2499+
T data = data_generator();
2500+
conn->send_json_event(
2501+
data, "data", std::to_string(counter), [self = this->shared_from_this()](std::error_code ec) {
2502+
if (!ec) {
2503+
self->counter++;
2504+
self->timer->expires_after(self->interval);
2505+
self->timer->async_wait(
2506+
[self](std::error_code) { self->send_next(); });
2507+
}
2508+
else {
2509+
self->conn->close();
2510+
}
2511+
});
2512+
}
2513+
catch (const std::exception&) {
2514+
conn->close();
2515+
}
24972516
}
24982517
};
24992518

2500-
(*send_next)();
2519+
auto sender = std::make_shared<periodic_sender>(conn, std::move(data_generator), interval, max_events);
2520+
sender->send_next();
25012521
}
25022522

25032523
// Create a data stream from a collection (works with both HTTP and HTTPS)
2524+
// Uses enable_shared_from_this to avoid circular reference memory leaks
2525+
// Note: Makes a copy of the container to ensure data outlives the async operation
25042526
template <typename Container>
25052527
void stream_collection(std::shared_ptr<streaming_connection_interface> conn, const Container& data,
25062528
std::chrono::milliseconds delay_between_items = std::chrono::milliseconds(10))
25072529
{
2508-
auto it = std::make_shared<typename Container::const_iterator>(data.begin());
2509-
auto end_it = data.end();
25102530
if (!conn || !conn->is_open()) return;
2511-
auto timer = std::make_shared<asio::steady_timer>(conn->get_executor());
2512-
2513-
// Use shared_ptr to safely handle recursive lambda calls and avoid compiler-specific segfaults
2514-
auto send_next = std::make_shared<std::function<void()>>();
2515-
*send_next = [conn, timer, it, end_it, delay_between_items, send_next]() mutable {
2516-
if (!conn->is_open() || *it == end_it) {
2517-
conn->close();
2518-
return;
2519-
}
25202531

2521-
conn->send_json_event(**it, "item", "", [=](std::error_code ec) mutable {
2522-
if (!ec) {
2523-
++(*it);
2524-
timer->expires_after(delay_between_items);
2525-
timer->async_wait([send_next, timer, it](std::error_code) { (*send_next)(); });
2526-
}
2527-
else {
2532+
struct collection_sender : std::enable_shared_from_this<collection_sender>
2533+
{
2534+
std::shared_ptr<streaming_connection_interface> conn;
2535+
std::shared_ptr<asio::steady_timer> timer;
2536+
Container data;
2537+
typename Container::const_iterator it;
2538+
std::chrono::milliseconds delay;
2539+
2540+
collection_sender(std::shared_ptr<streaming_connection_interface> c, Container d, std::chrono::milliseconds dl)
2541+
: conn(std::move(c)),
2542+
timer(std::make_shared<asio::steady_timer>(conn->get_executor())),
2543+
data(std::move(d)),
2544+
it(data.begin()),
2545+
delay(dl)
2546+
{}
2547+
2548+
void send_next()
2549+
{
2550+
if (!conn->is_open() || it == data.end()) {
25282551
conn->close();
2552+
return;
25292553
}
2530-
});
2554+
2555+
conn->send_json_event(*it, "item", "", [self = this->shared_from_this()](std::error_code ec) {
2556+
if (!ec) {
2557+
++self->it;
2558+
self->timer->expires_after(self->delay);
2559+
self->timer->async_wait([self](std::error_code) { self->send_next(); });
2560+
}
2561+
else {
2562+
self->conn->close();
2563+
}
2564+
});
2565+
}
25312566
};
25322567

2533-
(*send_next)();
2568+
auto sender = std::make_shared<collection_sender>(conn, data, delay_between_items);
2569+
sender->send_next();
25342570
}
25352571
} // namespace streaming_utils
25362572

tests/networking_tests/http_client_test/http_client_test.cpp

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -211,32 +211,49 @@ class working_test_server
211211
});
212212

213213
// Endpoint that sends data periodically, for testing client disconnect
214+
// Uses enable_shared_from_this to avoid circular reference memory leaks
214215
server_.stream_get("/slow-stream", [](request&, streaming_response& res) {
215216
res.start_stream(200, {{"Content-Type", "text/plain"}});
216-
auto conn = res.stream;
217217

218-
auto timer = std::make_shared<asio::steady_timer>(conn->get_executor());
219-
auto counter = std::make_shared<int>(0);
218+
struct slow_stream_handler : std::enable_shared_from_this<slow_stream_handler>
219+
{
220+
std::shared_ptr<streaming_connection_interface> conn;
221+
std::shared_ptr<asio::steady_timer> timer;
222+
int counter = 0;
220223

221-
// Use shared_ptr to safely handle recursive lambda calls and avoid compiler-specific segfaults
222-
auto send_data = std::make_shared<std::function<void(const std::error_code&)>>();
223-
*send_data = [conn, timer, counter, send_data](const std::error_code& ec) {
224-
if (ec || !conn->is_open() || *counter >= 10) {
225-
if (conn->is_open()) conn->close();
226-
return;
224+
explicit slow_stream_handler(std::shared_ptr<streaming_connection_interface> c)
225+
: conn(std::move(c)), timer(std::make_shared<asio::steady_timer>(conn->get_executor()))
226+
{}
227+
228+
void start()
229+
{
230+
timer->async_wait([self = shared_from_this()](const std::error_code& ec) { self->on_timer(ec); });
227231
}
228232

229-
conn->send_chunk("chunk" + std::to_string((*counter)++) + ";",
230-
[conn, timer, send_data](std::error_code write_ec) {
231-
if (write_ec || !conn->is_open()) {
232-
if (conn->is_open()) conn->close();
233-
return;
234-
}
235-
timer->expires_after(std::chrono::milliseconds(50));
236-
timer->async_wait(*send_data);
237-
});
233+
void on_timer(const std::error_code& ec)
234+
{
235+
if (ec || !conn->is_open() || counter >= 10) {
236+
if (conn->is_open()) conn->close();
237+
return;
238+
}
239+
240+
conn->send_chunk("chunk" + std::to_string(counter++) + ";",
241+
[self = shared_from_this()](std::error_code write_ec) { self->on_write(write_ec); });
242+
}
243+
244+
void on_write(std::error_code write_ec)
245+
{
246+
if (write_ec || !conn->is_open()) {
247+
if (conn->is_open()) conn->close();
248+
return;
249+
}
250+
timer->expires_after(std::chrono::milliseconds(50));
251+
timer->async_wait([self = shared_from_this()](const std::error_code& ec) { self->on_timer(ec); });
252+
}
238253
};
239-
timer->async_wait(*send_data);
254+
255+
auto handler = std::make_shared<slow_stream_handler>(res.stream);
256+
handler->start();
240257
});
241258

242259
// Endpoint that immediately returns an error

0 commit comments

Comments
 (0)