Skip to content

Commit 6403288

Browse files
LeeGoDamnwysaid
andauthored
Enable AddressSanitizer in CI/CD workflows and fix test boundary issues (#36)
- Enable ASAN by default for functional tests in run_tests.sh - Add --no-sanitize and --sanitize-all options for flexibility - Update Linux and macOS workflows to use ASAN in unit tests - Fix heap-buffer-overflow in boundary condition tests for tiny widths - Add platform detection to disable ASAN on Windows automatically ASAN helps catch memory errors like Issue #30's buffer overflow early in development, improving code quality and preventing crashes. Co-authored-by: wangyang (wysaid) <wysaid@gmail.com>
1 parent abf3059 commit 6403288

4 files changed

Lines changed: 136 additions & 14 deletions

File tree

.github/workflows/linux-build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ jobs:
140140
if [ "${{ matrix.library_type }}" = "shared" ]; then
141141
export LD_LIBRARY_PATH="$PWD/../build/${{ matrix.build_type }}-${{ matrix.library_type }}:$LD_LIBRARY_PATH"
142142
fi
143+
# Run tests with AddressSanitizer enabled by default (improves memory error detection)
143144
./run_tests.sh --functional --exit-when-failed
144145
145146
- name: Upload artifacts
@@ -239,6 +240,8 @@ jobs:
239240
if [ "${{ matrix.library_type }}" = "shared" ]; then
240241
export LD_LIBRARY_PATH="$PWD/../build/${{ matrix.build_type }}-${{ matrix.library_type }}:$LD_LIBRARY_PATH"
241242
fi
243+
# Run tests with AddressSanitizer enabled by default
244+
# This helps catch memory errors early in CI
242245
./run_tests.sh --functional --exit-when-failed
243246
244247
- name: Upload artifacts
@@ -448,6 +451,8 @@ jobs:
448451
if [ "${{ matrix.library_type }}" = "shared" ]; then
449452
export LD_LIBRARY_PATH="$PWD/../build/${{ matrix.build_type }}-${{ matrix.library_type }}:$LD_LIBRARY_PATH"
450453
fi
454+
# Run tests with AddressSanitizer enabled by default
455+
# This helps catch memory errors early in CI
451456
./run_tests.sh --functional --exit-when-failed
452457
453458
- name: Upload artifacts

.github/workflows/macos-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ jobs:
105105
if [ "${{ matrix.library_type }}" = "shared" ]; then
106106
export DYLD_LIBRARY_PATH="$PWD/../build/${{ matrix.config }}-${{ matrix.library_type }}:$DYLD_LIBRARY_PATH"
107107
fi
108+
# Run tests with AddressSanitizer enabled by default (improves memory error detection)
108109
./run_tests.sh --functional --skip-build
109110
else
110111
# Set library path for shared library tests

scripts/run_tests.sh

Lines changed: 114 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@
88
# - Windows (MSVC): Uses single build directory, configs specified during build
99
# - Linux/Mac: Uses separate build/Debug and build/Release directories
1010
#
11+
# Memory Sanitizer (ASAN):
12+
# - Functional tests: ENABLED by default (can be disabled with --no-sanitize)
13+
# - Performance tests: DISABLED by default (can be enabled with --sanitize-all)
14+
# - ASAN helps detect memory errors like buffer overflows, use-after-free, etc.
15+
#
1116
# Usage:
12-
# ./run_tests.sh # Run all tests
13-
# ./run_tests.sh --functional # Run only functional tests
14-
# ./run_tests.sh --performance # Run only performance tests
17+
# ./run_tests.sh # Run all tests (functional with ASAN, performance without)
18+
# ./run_tests.sh --functional # Run only functional tests (with ASAN)
19+
# ./run_tests.sh --performance # Run only performance tests (without ASAN)
1520
# ./run_tests.sh --avx2 # Run only AVX2 performance tests
1621
# ./run_tests.sh --shuffle # Run only tests with names containing 'shuffle' (case-insensitive) in functional tests
22+
# ./run_tests.sh --no-sanitize # Disable ASAN for all tests
23+
# ./run_tests.sh --sanitize-all # Enable ASAN for all tests (including performance)
1724
# ./run_tests.sh --help # Show help
1825

1926
set -e # Exit on any error
@@ -70,6 +77,9 @@ EXIT_WHEN_FAILED=false
7077
GTEST_FAIL_FAST_PARAM=""
7178
FILTER=""
7279
SHUFFLE_ONLY=false
80+
# Memory sanitizer flags: auto-detect based on test type, can be overridden
81+
SANITIZE_FUNCTIONAL="auto" # Default: enabled for functional tests
82+
SANITIZE_PERFORMANCE="auto" # Default: disabled for performance tests
7383

7484
while [[ $# -gt 0 ]]; do
7585
case $1 in
@@ -105,19 +115,37 @@ while [[ $# -gt 0 ]]; do
105115
GTEST_FAIL_FAST_PARAM="--gtest_fail_fast"
106116
shift
107117
;;
118+
--no-sanitize)
119+
SANITIZE_FUNCTIONAL="no"
120+
SANITIZE_PERFORMANCE="no"
121+
shift
122+
;;
123+
--sanitize-all)
124+
SANITIZE_FUNCTIONAL="yes"
125+
SANITIZE_PERFORMANCE="yes"
126+
shift
127+
;;
108128
--help)
109129
echo "CCAP Unit Tests Runner"
110130
echo ""
111131
echo "Usage:"
112-
echo " $0 # Run all tests"
113-
echo " $0 --functional # Run only functional tests (Debug mode)"
114-
echo " $0 --performance # Run only performance tests (Release mode)"
132+
echo " $0 # Run all tests (functional with ASAN, performance without)"
133+
echo " $0 --functional # Run only functional tests (Debug mode, with ASAN)"
134+
echo " $0 --performance # Run only performance tests (Release mode, without ASAN)"
115135
echo " $0 --avx2 # Run only AVX2 performance tests (Release mode)"
116136
echo " $0 --shuffle # Run only tests whose names contain '*shuffle*' or '*Shuffle*' in functional tests"
117137
echo " $0 --skip-build # Skip build step, run tests only"
118138
echo " $0 --exit-when-failed # Stop at first test failure (gtest fail fast mode)"
139+
echo " $0 --no-sanitize # Disable AddressSanitizer (ASAN) for all tests"
140+
echo " $0 --sanitize-all # Enable AddressSanitizer (ASAN) for all tests (including performance)"
119141
echo " $0 --help # Show this help"
120142
echo ""
143+
echo "Memory Sanitizer (ASAN):"
144+
echo " - Functional tests: ENABLED by default (detects memory errors)"
145+
echo " - Performance tests: DISABLED by default (would affect performance measurements)"
146+
echo " - Use --no-sanitize to disable ASAN completely"
147+
echo " - Use --sanitize-all to enable ASAN for performance tests too"
148+
echo ""
121149
echo "Note: Performance tests are automatically run in Release mode for accurate results"
122150
exit 0
123151
;;
@@ -162,6 +190,48 @@ fi
162190
TEST_RESULT=0
163191
PERF_RESULT=0
164192

193+
# Determine ASAN usage
194+
# Functional tests: default enabled, Performance tests: default disabled
195+
USE_ASAN_FUNCTIONAL=false
196+
USE_ASAN_PERFORMANCE=false
197+
198+
if [ "$SANITIZE_FUNCTIONAL" = "auto" ]; then
199+
USE_ASAN_FUNCTIONAL=true # Default: enable ASAN for functional tests
200+
elif [ "$SANITIZE_FUNCTIONAL" = "yes" ]; then
201+
USE_ASAN_FUNCTIONAL=true
202+
fi
203+
204+
if [ "$SANITIZE_PERFORMANCE" = "auto" ]; then
205+
USE_ASAN_PERFORMANCE=false # Default: disable ASAN for performance tests
206+
elif [ "$SANITIZE_PERFORMANCE" = "yes" ]; then
207+
USE_ASAN_PERFORMANCE=true
208+
fi
209+
210+
# Function to check if ASAN is supported
211+
function checkAsanSupport() {
212+
# ASAN is well supported on Linux and macOS with GCC/Clang
213+
# Windows MSVC support is limited and not used here
214+
if isWindows; then
215+
return 1 # Disable ASAN on Windows for now
216+
fi
217+
return 0
218+
}
219+
220+
# Check ASAN support
221+
ASAN_SUPPORTED=false
222+
if checkAsanSupport; then
223+
ASAN_SUPPORTED=true
224+
fi
225+
226+
# Disable ASAN if not supported
227+
if [ "$ASAN_SUPPORTED" = false ]; then
228+
if [ "$USE_ASAN_FUNCTIONAL" = true ] || [ "$USE_ASAN_PERFORMANCE" = true ]; then
229+
echo -e "${YELLOW}⚠ AddressSanitizer not supported on this platform, disabling ASAN${NC}"
230+
USE_ASAN_FUNCTIONAL=false
231+
USE_ASAN_PERFORMANCE=false
232+
fi
233+
fi
234+
165235
# Build Debug version for functional tests
166236
if [ "$RUN_FUNCTIONAL" = true ]; then
167237
echo ""
@@ -170,15 +240,24 @@ if [ "$RUN_FUNCTIONAL" = true ]; then
170240
echo -e "${BLUE}Skipping build, using existing Debug binaries${NC}"
171241
else
172242
echo -e "${BLUE}Building Debug version (for functional tests)${NC}"
243+
if [ "$USE_ASAN_FUNCTIONAL" = true ]; then
244+
echo -e "${GREEN}🛡️ AddressSanitizer (ASAN) ENABLED for memory error detection${NC}"
245+
fi
173246
fi
174247
echo -e "${PURPLE}===============================================${NC}"
175248

176249
if [ "$SKIP_BUILD" = false ]; then
250+
# Prepare ASAN flags if enabled
251+
ASAN_FLAGS=""
252+
if [ "$USE_ASAN_FUNCTIONAL" = true ]; then
253+
ASAN_FLAGS="-DCMAKE_CXX_FLAGS=\"-fsanitize=address -g\" -DCMAKE_C_FLAGS=\"-fsanitize=address -g\" -DCMAKE_EXE_LINKER_FLAGS=\"-fsanitize=address\" -DCMAKE_SHARED_LINKER_FLAGS=\"-fsanitize=address\""
254+
fi
255+
177256
if isWindows; then
178257
# Windows MSVC: use single build directory, specify config during build
179258
cd build
180259
echo -e "${BLUE}Configuring CMake (Windows MSVC)...${NC}"
181-
cmake .. -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
260+
eval cmake .. -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 $ASAN_FLAGS
182261

183262
echo -e "${BLUE}Building Debug project...${NC}"
184263
cmake --build . --config Debug --parallel $(detectCores)
@@ -191,7 +270,7 @@ if [ "$RUN_FUNCTIONAL" = true ]; then
191270
# Linux/Mac: use separate Debug directory
192271
cd build/Debug
193272
echo -e "${BLUE}Configuring CMake (Debug)...${NC}"
194-
cmake ../.. -DCMAKE_BUILD_TYPE=Debug -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
273+
eval cmake ../.. -DCMAKE_BUILD_TYPE=Debug -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 $ASAN_FLAGS
195274

196275
echo -e "${BLUE}Building Debug project...${NC}"
197276
cmake --build . --config Debug --parallel $(detectCores)
@@ -211,17 +290,27 @@ if [ "$RUN_PERFORMANCE" = true ]; then
211290
echo -e "${BLUE}Skipping build, using existing Release binaries${NC}"
212291
else
213292
echo -e "${BLUE}Building Release version (for performance tests)${NC}"
293+
if [ "$USE_ASAN_PERFORMANCE" = true ]; then
294+
echo -e "${GREEN}🛡️ AddressSanitizer (ASAN) ENABLED${NC}"
295+
echo -e "${YELLOW}⚠ Note: ASAN affects performance measurements${NC}"
296+
fi
214297
fi
215298
echo -e "${PURPLE}===============================================${NC}"
216299

217300
if [ "$SKIP_BUILD" = false ]; then
301+
# Prepare ASAN flags if enabled
302+
ASAN_FLAGS=""
303+
if [ "$USE_ASAN_PERFORMANCE" = true ]; then
304+
ASAN_FLAGS="-DCMAKE_CXX_FLAGS=\"-fsanitize=address -g\" -DCMAKE_C_FLAGS=\"-fsanitize=address -g\" -DCMAKE_EXE_LINKER_FLAGS=\"-fsanitize=address\" -DCMAKE_SHARED_LINKER_FLAGS=\"-fsanitize=address\""
305+
fi
306+
218307
if isWindows; then
219308
# Windows MSVC: use single build directory, specify config during build
220309
cd build
221310
# Only configure if not already configured
222311
if [ ! -f "CMakeCache.txt" ]; then
223312
echo -e "${BLUE}Configuring CMake (Windows MSVC)...${NC}"
224-
cmake .. -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
313+
eval cmake .. -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 $ASAN_FLAGS
225314
fi
226315

227316
echo -e "${BLUE}Building Release project...${NC}"
@@ -234,7 +323,7 @@ if [ "$RUN_PERFORMANCE" = true ]; then
234323
# Linux/Mac: use separate Release directory
235324
cd build/Release
236325
echo -e "${BLUE}Configuring CMake (Release)...${NC}"
237-
cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
326+
eval cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCCAP_BUILD_TESTS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 $ASAN_FLAGS
238327

239328
echo -e "${BLUE}Building Release project...${NC}"
240329
cmake --build . --config Release --parallel $(detectCores)
@@ -265,6 +354,14 @@ if [ "$RUN_FUNCTIONAL" = true ]; then
265354
if [ -f "$TEST_EXECUTABLE" ]; then
266355
echo -e "${YELLOW}Running functional tests in Debug mode...${NC}"
267356

357+
# Set ASAN options if enabled
358+
if [ "$USE_ASAN_FUNCTIONAL" = true ]; then
359+
echo -e "${GREEN}🛡️ Running with AddressSanitizer enabled${NC}"
360+
# Disable memory leak detection in ASAN (can cause false positives in tests)
361+
# Enable detailed error reporting
362+
export ASAN_OPTIONS="detect_leaks=0:halt_on_error=1:allocator_may_return_null=1"
363+
fi
364+
268365
if [ "$SHUFFLE_ONLY" = true ]; then
269366
echo -e "${BLUE}Filtering tests to names containing '*shuffle*' or '*Shuffle*'...${NC}"
270367
"$TEST_EXECUTABLE" --gtest_filter='*shuffle*:*Shuffle*:*SHUFFLE*' $GTEST_FAIL_FAST_PARAM --gtest_output=xml:test_results_debug.xml
@@ -326,6 +423,13 @@ if [ "$RUN_PERFORMANCE" = true ]; then
326423
echo -e "${YELLOW}Running performance benchmarks in Release mode...${NC}"
327424
echo -e "${BLUE}Note: Release mode provides accurate performance measurements${NC}"
328425

426+
# Set ASAN options if enabled
427+
if [ "$USE_ASAN_PERFORMANCE" = true ]; then
428+
echo -e "${GREEN}🛡️ Running with AddressSanitizer enabled${NC}"
429+
echo -e "${YELLOW}⚠ Performance results may be affected by ASAN overhead${NC}"
430+
export ASAN_OPTIONS="detect_leaks=0:halt_on_error=1:allocator_may_return_null=1"
431+
fi
432+
329433
if [ -n "$FILTER" ]; then
330434
echo -e "${BLUE}Filter: $FILTER${NC}"
331435
"$PERF_EXECUTABLE" $FILTER $GTEST_FAIL_FAST_PARAM --gtest_output=xml:build/performance_results_release.xml

tests/test_boundary_conditions.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,15 @@ class BoundaryConditionTest : public BackendParameterizedTest {
6666
width, height);
6767

6868
// Verify conversion correctness at various positions
69-
std::vector<int> testPositions = {0, 1, width/2, width-2, width-1};
69+
std::vector<int> testPositions;
70+
testPositions.push_back(0);
71+
if (width > 1) testPositions.push_back(1);
72+
if (width > 2) testPositions.push_back(width/2);
73+
if (width > 2) testPositions.push_back(width-2);
74+
if (width > 1) testPositions.push_back(width-1);
75+
7076
for (int x : testPositions) {
71-
if (x >= width) continue;
77+
if (x < 0 || x >= width) continue;
7278

7379
for (int y = 0; y < height; ++y) {
7480
const uint8_t* srcPixel = src.data() + y * src.stride() + x * 3;
@@ -118,9 +124,15 @@ class BoundaryConditionTest : public BackendParameterizedTest {
118124
width, height);
119125

120126
// Verify conversion
121-
std::vector<int> testPositions = {0, 1, width/2, width-2, width-1};
127+
std::vector<int> testPositions;
128+
testPositions.push_back(0);
129+
if (width > 1) testPositions.push_back(1);
130+
if (width > 2) testPositions.push_back(width/2);
131+
if (width > 2) testPositions.push_back(width-2);
132+
if (width > 1) testPositions.push_back(width-1);
133+
122134
for (int x : testPositions) {
123-
if (x >= width) continue;
135+
if (x < 0 || x >= width) continue;
124136

125137
for (int y = 0; y < height; ++y) {
126138
const uint8_t* srcPixel = src.data() + y * src.stride() + x * 3;

0 commit comments

Comments
 (0)