Skip to content

Commit c41de1a

Browse files
authored
Add address and UB sanitizer tests (#174)
* Add address and UB sanitizer tests * Configure address sanitizer workflow
1 parent dc5b77f commit c41de1a

5 files changed

Lines changed: 490 additions & 4 deletions

File tree

.github/workflows/sanitizers.yml

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
name: Sanitizer Tests
2+
3+
# Run on main branch pushes, pull requests, and manual trigger
4+
on:
5+
push:
6+
branches: [ main ]
7+
pull_request:
8+
workflow_dispatch:
9+
10+
env:
11+
CMAKE_VERSION: 3.21.7
12+
NINJA_VERSION: 1.11.0
13+
14+
jobs:
15+
sanitizers:
16+
name: ${{ matrix.config.name }}
17+
runs-on: ${{ matrix.config.os }}
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
config:
22+
# GCC 14 with AddressSanitizer + UndefinedBehaviorSanitizer
23+
- {
24+
name: "Linux GCC 14 + ASan + UBSan",
25+
os: ubuntu-24.04,
26+
build_type: RelWithDebInfo,
27+
cc: "gcc-14", cxx: "g++-14",
28+
cxx_standard: 23
29+
}
30+
31+
# Clang 18 with AddressSanitizer + UndefinedBehaviorSanitizer
32+
- {
33+
name: "Linux Clang 18 + ASan + UBSan (libc++)",
34+
os: ubuntu-24.04,
35+
build_type: RelWithDebInfo,
36+
cc: "clang-18", cxx: "clang++-18",
37+
cxx_standard: 23,
38+
use_libcxx: true
39+
}
40+
41+
# Clang 21 with AddressSanitizer + UndefinedBehaviorSanitizer
42+
- {
43+
name: "Linux Clang 21 + ASan + UBSan (libc++)",
44+
os: ubuntu-24.04,
45+
build_type: RelWithDebInfo,
46+
cc: "clang-21", cxx: "clang++-21",
47+
cxx_standard: 23,
48+
use_libcxx: true
49+
}
50+
51+
steps:
52+
- uses: actions/checkout@master
53+
54+
- name: Download Ninja and CMake
55+
id: cmake_and_ninja
56+
shell: cmake -P {0}
57+
run: |
58+
set(cmake_version $ENV{CMAKE_VERSION})
59+
set(ninja_version $ENV{NINJA_VERSION})
60+
61+
message(STATUS "Using host CMake version: ${CMAKE_VERSION}")
62+
63+
set(ninja_suffix "linux.zip")
64+
set(cmake_suffix "linux-x86_64.tar.gz")
65+
set(cmake_dir "cmake-${cmake_version}-linux-x86_64/bin")
66+
67+
set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}")
68+
file(DOWNLOAD "${ninja_url}" ./ninja.zip SHOW_PROGRESS)
69+
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip)
70+
71+
set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}")
72+
file(DOWNLOAD "${cmake_url}" ./cmake.zip SHOW_PROGRESS)
73+
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip)
74+
75+
# Save the path for other steps
76+
file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/${cmake_dir}" cmake_dir)
77+
message("::set-output name=cmake_dir::${cmake_dir}")
78+
79+
execute_process(
80+
COMMAND chmod +x ninja
81+
COMMAND chmod +x ${cmake_dir}/cmake
82+
)
83+
84+
- name: Install Clang and libc++ (C++23 support)
85+
id: install_clang
86+
if: contains(matrix.config.cxx, 'clang++')
87+
shell: bash
88+
run: |
89+
# Extract version number from compiler name (e.g., clang++-21 -> 21)
90+
CLANG_VERSION=$(echo "${{ matrix.config.cxx }}" | grep -oP '\d+')
91+
92+
# Add LLVM repository for newer versions (18 is pre-installed)
93+
if [[ "$CLANG_VERSION" != "18" ]]; then
94+
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
95+
sudo add-apt-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-${CLANG_VERSION} main"
96+
fi
97+
98+
sudo apt-get update
99+
100+
# Install Clang and libc++ for the specific version
101+
sudo apt-get -y install \
102+
clang-${CLANG_VERSION} \
103+
libc++-${CLANG_VERSION}-dev \
104+
libc++abi-${CLANG_VERSION}-dev
105+
106+
- name: Install vcpkg
107+
id: vcpkg
108+
shell: bash
109+
run: |
110+
mkdir -p ${GITHUB_WORKSPACE}/vcpkg
111+
cd ${GITHUB_WORKSPACE}/vcpkg
112+
git init
113+
git remote add origin https://github.com/microsoft/vcpkg.git
114+
git fetch origin master
115+
git checkout -b master origin/master
116+
./bootstrap-vcpkg.sh
117+
118+
# For Clang builds, use custom triplet with libc++ and set compiler
119+
if [[ "${{ matrix.config.use_libcxx }}" == "true" ]]; then
120+
export CC=${{ matrix.config.cc }}
121+
export CXX=${{ matrix.config.cxx }}
122+
./vcpkg install uni-algo \
123+
--triplet x64-linux-libcxx \
124+
--overlay-triplets=${GITHUB_WORKSPACE}/cmake/vcpkg-triplets
125+
else
126+
./vcpkg install uni-algo
127+
fi
128+
129+
- name: Configure
130+
shell: cmake -P {0}
131+
run: |
132+
set(ENV{CC} ${{ matrix.config.cc }})
133+
set(ENV{CXX} ${{ matrix.config.cxx }})
134+
135+
file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/ninja" ninja_program)
136+
137+
# Determine triplet for vcpkg
138+
if ("${{ matrix.config.use_libcxx }}" STREQUAL "true")
139+
set(vcpkg_triplet "x64-linux-libcxx")
140+
set(use_libcxx ON)
141+
else()
142+
set(vcpkg_triplet "x64-linux")
143+
set(use_libcxx OFF)
144+
endif()
145+
146+
execute_process(
147+
COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake
148+
-S .
149+
-B build
150+
-D CMAKE_BUILD_TYPE=${{ matrix.config.build_type }}
151+
-G Ninja
152+
-D CMAKE_MAKE_PROGRAM=${ninja_program}
153+
-D CMAKE_TOOLCHAIN_FILE=${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake
154+
-D VCPKG_TARGET_TRIPLET=${vcpkg_triplet}
155+
-D skyr_BUILD_TESTS=OFF
156+
-D skyr_BUILD_EXAMPLES=OFF
157+
-D skyr_ENABLE_SANITIZERS=ON
158+
-D skyr_BUILD_WITH_LLVM_LIBCXX=${use_libcxx}
159+
-D skyr_WARNINGS_AS_ERRORS=OFF
160+
RESULT_VARIABLE result
161+
)
162+
if (NOT result EQUAL 0)
163+
message(FATAL_ERROR "Bad exit status")
164+
endif()
165+
166+
- name: Build
167+
shell: cmake -P {0}
168+
run: |
169+
set(ENV{NINJA_STATUS} "[%f/%t %o/sec] ")
170+
171+
file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}" ccache_basedir)
172+
set(ENV{CCACHE_BASEDIR} "${ccache_basedir}")
173+
set(ENV{CCACHE_DIR} "${ccache_basedir}/.ccache")
174+
set(ENV{CCACHE_COMPRESS} "true")
175+
set(ENV{CCACHE_COMPRESSLEVEL} "6")
176+
set(ENV{CCACHE_MAXSIZE} "400M")
177+
178+
execute_process(
179+
COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --build build --target url_sanitizer_tests
180+
RESULT_VARIABLE result
181+
)
182+
if (NOT result EQUAL 0)
183+
message(FATAL_ERROR "Build failed")
184+
endif()
185+
186+
- name: Run Sanitizer Tests
187+
shell: bash
188+
run: |
189+
echo "========================================"
190+
echo "Running AddressSanitizer + UBSan Tests"
191+
echo "========================================"
192+
193+
# Set sanitizer options for comprehensive checking
194+
# alloc_dealloc_mismatch=0: Suppress false positive from libc++ exception handling
195+
export ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:alloc_dealloc_mismatch=0:verbosity=0
196+
export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=0
197+
198+
# Run the sanitizer test
199+
./build/tests/sanitizers/url_sanitizer_tests
200+
201+
TEST_RESULT=$?
202+
203+
if [ $TEST_RESULT -eq 0 ]; then
204+
echo "✓ All sanitizer tests passed - no memory safety issues detected!"
205+
else
206+
echo "✗ Sanitizer tests failed or detected issues"
207+
exit 1
208+
fi

.github/workflows/wpt.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
name: Web Platform Tests
22

3-
# Run on push to main, weekly schedule, and manual trigger
3+
# Run on push to main and manual trigger
44
on:
55
push:
66
branches:
77
- main
8-
schedule:
9-
# Run every Monday at 00:00 UTC
10-
- cron: '0 0 * * 1'
118
workflow_dispatch:
129
# Allow manual triggering
1310

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ option(skyr_USE_STATIC_CRT "Use static C Runtime library (/MT or MTd)." ON)
3636
option(skyr_BUILD_WITH_LLVM_LIBCXX "Instruct Clang to use LLVM's implementation of C++ standard library" OFF)
3737
option(skyr_ENABLE_FILESYSTEM_FUNCTIONS "Enable functions to convert URL to std::filesystem::path" ON)
3838
option(skyr_ENABLE_JSON_FUNCTIONS "Enable functions to convert URL components to JSON" ON)
39+
option(skyr_ENABLE_SANITIZERS "Enable sanitizers (address, undefined, etc.) for tests and examples" OFF)
3940
option(skyr_CXX_STANDARD_LIBRARY "Path to non-system C++ standard library" "")
4041

4142
if (skyr_IS_TOP_LEVEL_PROJECT)
@@ -74,6 +75,7 @@ set(full_warnings $<BOOL:${skyr_FULL_WARNINGS}>)
7475
set(warnings_as_errors $<BOOL:${skyr_WARNINGS_AS_ERRORS}>)
7576
set(no_exceptions $<BOOL:${skyr_BUILD_WITHOUT_EXCEPTIONS}>)
7677
set(no_rtti $<BOOL:${skyr_BUILD_WITHOUT_RTTI}>)
78+
set(enable_sanitizers $<BOOL:${skyr_ENABLE_SANITIZERS}>)
7779

7880
set(gnu $<CXX_COMPILER_ID:GNU>)
7981
set(clang $<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>)
@@ -92,6 +94,15 @@ if (skyr_BUILD_TESTS)
9294
add_subdirectory(tests)
9395
endif()
9496

97+
# Sanitizer tests (independent, no Catch2 needed)
98+
if (skyr_ENABLE_SANITIZERS)
99+
message(STATUS "[skyr-url] Configuring sanitizer tests")
100+
if (NOT skyr_BUILD_TESTS)
101+
enable_testing() # Only call this if not already enabled
102+
endif()
103+
add_subdirectory(tests/sanitizers)
104+
endif()
105+
95106
# Documentation
96107
if (skyr_BUILD_DOCS)
97108
message(STATUS "[skyr-url] Configuring documentation")

tests/sanitizers/CMakeLists.txt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright (c) Glyn Matthews 2025.
2+
# Distributed under the Boost Software License, Version 1.0.
3+
# (See accompanying file LICENSE_1_0.txt or copy at
4+
# http://www.boost.org/LICENSE_1_0.txt)
5+
6+
include(${PROJECT_SOURCE_DIR}/cmake/skyr-url-functions.cmake)
7+
8+
# Sanitizer tests - only build when sanitizers are enabled
9+
if (NOT skyr_ENABLE_SANITIZERS)
10+
message(STATUS "Sanitizer tests skipped (enable with -Dskyr_ENABLE_SANITIZERS=ON)")
11+
return()
12+
endif()
13+
14+
message(STATUS "Building sanitizer tests with AddressSanitizer + UndefinedBehaviorSanitizer")
15+
16+
foreach(
17+
file_name
18+
url_sanitizer_tests.cpp
19+
)
20+
skyr_remove_extension(${file_name} basename)
21+
set(test ${basename})
22+
add_executable(${test} ${file_name})
23+
add_dependencies(${test} skyr-url)
24+
25+
target_compile_options(
26+
${test}
27+
PRIVATE
28+
# Standard warnings (but no -Werror for sanitizer tests)
29+
$<$<AND:$<OR:${gnu},${clang}>,${full_warnings}>:-Wall>
30+
$<$<AND:$<OR:${gnu},${clang}>,${no_exceptions}>:-fno-exceptions>
31+
$<$<AND:$<OR:${gnu},${clang}>,${no_rtti}>:-fno-rtti>
32+
$<${libcxx}:-stdlib=libc++>
33+
34+
# AddressSanitizer flags (GCC/Clang)
35+
$<$<OR:${gnu},${clang}>:-fsanitize=address>
36+
$<$<OR:${gnu},${clang}>:-fsanitize=undefined>
37+
$<$<OR:${gnu},${clang}>:-fno-omit-frame-pointer>
38+
$<$<OR:${gnu},${clang}>:-fno-optimize-sibling-calls>
39+
$<$<OR:${gnu},${clang}>:-g>
40+
$<$<OR:${gnu},${clang}>:-O1>
41+
42+
# MSVC sanitizer flags
43+
$<$<AND:${msvc},${full_warnings}>:/W4>
44+
$<$<AND:${msvc},$<NOT:${no_exceptions}>>:/EHsc>
45+
$<$<AND:${msvc},${no_rtti}>:/GR->
46+
$<${msvc}:/fsanitize=address>
47+
$<${msvc}:/Zi>
48+
)
49+
50+
target_link_options(
51+
${test}
52+
PRIVATE
53+
# Sanitizer linker flags (GCC/Clang)
54+
$<$<OR:${gnu},${clang}>:-fsanitize=address>
55+
$<$<OR:${gnu},${clang}>:-fsanitize=undefined>
56+
57+
# MSVC sanitizer linker flags
58+
$<${msvc}:/fsanitize=address>
59+
)
60+
61+
target_link_libraries(
62+
${test}
63+
PRIVATE
64+
skyr-url
65+
)
66+
67+
set_target_properties(
68+
${test}
69+
PROPERTIES
70+
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/tests/sanitizers/
71+
)
72+
73+
# Add as a test so it can be run with ctest
74+
add_test(
75+
NAME ${test}
76+
COMMAND ${test}
77+
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests/sanitizers/
78+
)
79+
80+
# Set environment variables for sanitizers
81+
set_tests_properties(
82+
${test}
83+
PROPERTIES
84+
ENVIRONMENT "ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1"
85+
)
86+
endforeach()

0 commit comments

Comments
 (0)