Skip to content

Commit ba9623a

Browse files
committed
Add ASan/TSan/Valgrind configs and GitHub Actions CI (#21)
* Add ASan/TSan/Valgrind configs and GitHub Actions CI Add `--config=asan`, `--config=tsan` and `--config=valgrind` to .bazelrc, mirroring the configuration used by the dallison/co coroutines repository. Add a GitHub Actions workflow that builds and tests on ubuntu-latest, ubuntu-24.04-arm and macos-latest, plus dedicated sanitizer and Valgrind jobs on Linux. Bump coroutines to 3.3.1 to pick up the matching sanitizer / fiber cooperation hooks. Fix a heap-buffer-overflow in sockets_test caught by ASan: the UDP unicast and broadcast tests called std::strcmp on a Receive_buffer sized exactly TEST_DATA.size(), which has no NUL terminator. The preceding ASSERT_EQ on a string_view already validates the bytes fully, so the strcmp calls are redundant and have been removed. * Fix CI failures for ASan, TSan, Valgrind and macOS sockets - payload_buffer_test: zero backing storage with calloc so Hexdump does not read uninitialized padding under Memcheck. Tear down the resizable-buffer test with an explicit destructor plus free() after realloc, fixing ASan alloc-dealloc-mismatch from operator delete on malloc/realloc storage. - pipe_test: compare fixed-length pipe reads with std::string_view instead of ASSERT_STREQ/strcmp on non-NUL-terminated buffers. Skip the high-iteration OverFull stress tests under TSan where the sanitizer hits nested DEADLYSIGNAL on many fiber switches. - sockets_test: skip UDP broadcast and multicast loopback tests on Apple platforms where GitHub Actions macOS runners reject or drop those packets. * Free backing storage in small-block PayloadBuffer tests SmallBlockAllocSimple, SmallBlockAlloc, and SmallBlockAllocFree used placement-new into calloc storage but never destroyed the object or released the block, which Valgrind reported as definitely lost. Tear down with an explicit destructor and free(). * Fix VectorHeader/pointer invalidation after buffer relocation PayloadBuffer's VectorPush, VectorReserve, VectorResize, Realloc, PrimeBitmapAllocator, and BitMapRun::Allocate all dereferenced pointers into the buffer (hdr, p) after Allocate/Realloc/AllocateBitMapRun calls that may have relocated the underlying buffer, leaving those pointers dangling. Save the offset before any allocation and re-derive the pointer afterward across all six call sites. Also drop a leftover write through the dangling hdr in VectorPush that slipped through a previous partial fix. Add VectorPushWithResize, VectorReserveWithResize, and VectorResizeWithResize regression tests that drive the resizer with a small (256-byte) resizable buffer and verify data integrity afterward. The existing tests all used fixed-size buffers and could not trigger the bug. Original fix by Damian Stulich <damjan.stulic@getcruise.com>.
1 parent 213d678 commit ba9623a

9 files changed

Lines changed: 586 additions & 230 deletions

File tree

.bazelrc

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,57 @@
1-
build:asan --strip=never
2-
build:asan --copt -fsanitize=address
3-
build:asan --copt -DADDRESS_SANITIZER
4-
build:asan --copt -g
5-
build:asan --copt -fno-omit-frame-pointer
6-
build:asan --linkopt -fsanitize=address
7-
81
# For all builds, use C++17
92
build --cxxopt="-std=c++17"
103

114
# For Apple Silicon
125
build:apple_silicon --cpu=darwin_arm64
136
build:apple_silicon --features=oso_prefix_is_pwd
147

8+
# -----------------------------------------------------------------------------
9+
# Sanitizer / dynamic-analysis configurations.
10+
#
11+
# Pick one with `--config=<name>`, e.g.
12+
# bazel test --config=asan //toolbelt:sockets_test
13+
# bazel test --config=tsan //toolbelt:sockets_test
14+
# bazel test --config=valgrind //toolbelt:sockets_test
15+
#
16+
# All three configs preserve debug information so failures point back to
17+
# meaningful source locations.
18+
# -----------------------------------------------------------------------------
19+
20+
# AddressSanitizer. Detects use-after-free, heap/stack/global buffer
21+
# overflows, use-after-return, etc.
22+
build:asan --strip=never
23+
build:asan --copt=-fsanitize=address
24+
build:asan --copt=-DADDRESS_SANITIZER
25+
build:asan --copt=-g
26+
build:asan --copt=-O1
27+
build:asan --copt=-fno-omit-frame-pointer
28+
build:asan --linkopt=-fsanitize=address
29+
# Note: leak detection (LSan) is only supported on Linux; opting out keeps the
30+
# config portable to macOS. On Linux you can enable it with
31+
# --test_env=ASAN_OPTIONS=halt_on_error=1:detect_leaks=1
32+
test:asan --test_env=ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:detect_leaks=0:print_summary=1
33+
34+
# ThreadSanitizer. Detects data races and several kinds of synchronization
35+
# bugs.
36+
build:tsan --strip=never
37+
build:tsan --copt=-fsanitize=thread
38+
build:tsan --copt=-DTHREAD_SANITIZER
39+
build:tsan --copt=-g
40+
build:tsan --copt=-O1
41+
build:tsan --copt=-fno-omit-frame-pointer
42+
build:tsan --linkopt=-fsanitize=thread
43+
test:tsan --test_env=TSAN_OPTIONS=halt_on_error=1:second_deadlock_stack=1:history_size=7
44+
45+
# Valgrind. Runs the unmodified binary under Memcheck.
46+
#
47+
# Notes:
48+
# * Valgrind is not available on macOS arm64. Use Linux (or x86_64) to
49+
# exercise this config.
50+
# * --error-exitcode=1 makes the test fail on any reported error.
51+
# * --child-silent-after-fork=yes suppresses noise from forking helpers.
52+
build:valgrind --strip=never
53+
build:valgrind --copt=-g
54+
build:valgrind --copt=-O1
55+
build:valgrind --copt=-fno-omit-frame-pointer
56+
test:valgrind --run_under='valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=definite,possible --track-origins=yes --child-silent-after-fork=yes --trace-children=yes'
57+
test:valgrind --test_timeout=300

.github/workflows/ci.yml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
name: CI
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
# Plain build + test on each supported host. Mirrors the workflow used by
7+
# the dallison/co repository so behavior stays consistent across
8+
# projects.
9+
test:
10+
name: Build & test (${{ matrix.os }})
11+
runs-on: ${{ matrix.os }}
12+
timeout-minutes: 30
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
include:
17+
- os: ubuntu-latest
18+
- os: ubuntu-24.04-arm
19+
- os: macos-latest
20+
bazel_flags: --config=apple_silicon
21+
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v6
25+
26+
- name: Install Bazel
27+
uses: bazel-contrib/setup-bazel@0.18.0
28+
with:
29+
# Avoid downloading Bazel every time.
30+
bazelisk-cache: true
31+
# Store the build cache per workflow.
32+
disk-cache: ${{ github.workflow }}-${{ matrix.os }}
33+
# Share the repository cache between workflows.
34+
repository-cache: true
35+
36+
- name: Build all targets
37+
run: |
38+
bazel build //... \
39+
--verbose_failures \
40+
${{ matrix.bazel_flags }}
41+
42+
- name: Run tests
43+
run: |
44+
bazel test //... \
45+
--verbose_failures \
46+
--test_output=errors \
47+
${{ matrix.bazel_flags }}
48+
49+
- name: Upload Bazel test logs
50+
uses: actions/upload-artifact@v7
51+
if: failure()
52+
with:
53+
name: bazel-test-logs-${{ matrix.os }}
54+
path: bazel-testlogs
55+
56+
# Run the test suite under AddressSanitizer and ThreadSanitizer. Linux
57+
# only because LSan + ASan, and TSan, behave most uniformly there.
58+
sanitizers:
59+
name: ${{ matrix.config }}
60+
runs-on: ubuntu-latest
61+
timeout-minutes: 30
62+
strategy:
63+
fail-fast: false
64+
matrix:
65+
config: [asan, tsan]
66+
67+
steps:
68+
- name: Checkout code
69+
uses: actions/checkout@v6
70+
71+
- name: Install Bazel
72+
uses: bazel-contrib/setup-bazel@0.18.0
73+
with:
74+
bazelisk-cache: true
75+
disk-cache: ${{ github.workflow }}-${{ matrix.config }}
76+
repository-cache: true
77+
78+
- name: Run tests under ${{ matrix.config }}
79+
run: |
80+
bazel test //... \
81+
--config=${{ matrix.config }} \
82+
--verbose_failures \
83+
--test_output=errors
84+
85+
- name: Upload Bazel test logs
86+
uses: actions/upload-artifact@v7
87+
if: failure()
88+
with:
89+
name: bazel-test-logs-${{ matrix.config }}
90+
path: bazel-testlogs
91+
92+
# Run the test suite under Valgrind's Memcheck. Valgrind is only
93+
# available on Linux/x86_64 in practice.
94+
valgrind:
95+
name: valgrind
96+
runs-on: ubuntu-latest
97+
timeout-minutes: 45
98+
99+
steps:
100+
- name: Checkout code
101+
uses: actions/checkout@v6
102+
103+
- name: Install Valgrind
104+
run: |
105+
sudo apt-get update
106+
sudo apt-get install -y valgrind
107+
108+
- name: Install Bazel
109+
uses: bazel-contrib/setup-bazel@0.18.0
110+
with:
111+
bazelisk-cache: true
112+
disk-cache: ${{ github.workflow }}-valgrind
113+
repository-cache: true
114+
115+
- name: Run tests under Valgrind
116+
run: |
117+
bazel test //... \
118+
--config=valgrind \
119+
--verbose_failures \
120+
--test_output=errors
121+
122+
- name: Upload Bazel test logs
123+
uses: actions/upload-artifact@v7
124+
if: failure()
125+
with:
126+
name: bazel-test-logs-valgrind
127+
path: bazel-testlogs

MODULE.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
module(
22
name = "cpp_toolbelt",
3-
version = "2.0.2",
3+
version = "2.1.1",
44
)
55

66
bazel_dep(name = "platforms", version = "1.0.0")
77
bazel_dep(name = "bazel_skylib", version = "1.9.0")
88
bazel_dep(name = "abseil-cpp", version = "20250814.1")
99
bazel_dep(name = "googletest", version = "1.17.0.bcr.2")
10-
bazel_dep(name = "coroutines", version = "3.2.1")
10+
bazel_dep(name = "coroutines", version = "3.3.1")
1111
bazel_dep(name = "rules_cc", version = "0.2.17")
1212

1313
# For local debugging of co coroutine library.

0 commit comments

Comments
 (0)