Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions .github/workflows/sanitizers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
name: Sanitizer Tests

# Run on main branch pushes, pull requests, and manual trigger
on:
push:
branches: [ main ]
pull_request:
workflow_dispatch:

env:
CMAKE_VERSION: 3.21.7
NINJA_VERSION: 1.11.0

jobs:
sanitizers:
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: false
matrix:
config:
# GCC 14 with AddressSanitizer + UndefinedBehaviorSanitizer
- {
name: "Linux GCC 14 + ASan + UBSan",
os: ubuntu-24.04,
build_type: RelWithDebInfo,
cc: "gcc-14", cxx: "g++-14",
cxx_standard: 23
}

# Clang 18 with AddressSanitizer + UndefinedBehaviorSanitizer
- {
name: "Linux Clang 18 + ASan + UBSan (libc++)",
os: ubuntu-24.04,
build_type: RelWithDebInfo,
cc: "clang-18", cxx: "clang++-18",
cxx_standard: 23,
use_libcxx: true
}

# Clang 21 with AddressSanitizer + UndefinedBehaviorSanitizer
- {
name: "Linux Clang 21 + ASan + UBSan (libc++)",
os: ubuntu-24.04,
build_type: RelWithDebInfo,
cc: "clang-21", cxx: "clang++-21",
cxx_standard: 23,
use_libcxx: true
}

steps:
- uses: actions/checkout@master

- name: Download Ninja and CMake
id: cmake_and_ninja
shell: cmake -P {0}
run: |
set(cmake_version $ENV{CMAKE_VERSION})
set(ninja_version $ENV{NINJA_VERSION})

message(STATUS "Using host CMake version: ${CMAKE_VERSION}")

set(ninja_suffix "linux.zip")
set(cmake_suffix "linux-x86_64.tar.gz")
set(cmake_dir "cmake-${cmake_version}-linux-x86_64/bin")

set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}")
file(DOWNLOAD "${ninja_url}" ./ninja.zip SHOW_PROGRESS)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip)

set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}")
file(DOWNLOAD "${cmake_url}" ./cmake.zip SHOW_PROGRESS)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip)

# Save the path for other steps
file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/${cmake_dir}" cmake_dir)
message("::set-output name=cmake_dir::${cmake_dir}")

execute_process(
COMMAND chmod +x ninja
COMMAND chmod +x ${cmake_dir}/cmake
)

- name: Install Clang and libc++ (C++23 support)
id: install_clang
if: contains(matrix.config.cxx, 'clang++')
shell: bash
run: |
# Extract version number from compiler name (e.g., clang++-21 -> 21)
CLANG_VERSION=$(echo "${{ matrix.config.cxx }}" | grep -oP '\d+')

# Add LLVM repository for newer versions (18 is pre-installed)
if [[ "$CLANG_VERSION" != "18" ]]; then
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo add-apt-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-${CLANG_VERSION} main"
fi

sudo apt-get update

# Install Clang and libc++ for the specific version
sudo apt-get -y install \
clang-${CLANG_VERSION} \
libc++-${CLANG_VERSION}-dev \
libc++abi-${CLANG_VERSION}-dev

- name: Install vcpkg
id: vcpkg
shell: bash
run: |
mkdir -p ${GITHUB_WORKSPACE}/vcpkg
cd ${GITHUB_WORKSPACE}/vcpkg
git init
git remote add origin https://github.com/microsoft/vcpkg.git
git fetch origin master
git checkout -b master origin/master
./bootstrap-vcpkg.sh

# For Clang builds, use custom triplet with libc++ and set compiler
if [[ "${{ matrix.config.use_libcxx }}" == "true" ]]; then
export CC=${{ matrix.config.cc }}
export CXX=${{ matrix.config.cxx }}
./vcpkg install uni-algo \
--triplet x64-linux-libcxx \
--overlay-triplets=${GITHUB_WORKSPACE}/cmake/vcpkg-triplets
else
./vcpkg install uni-algo
fi

- name: Configure
shell: cmake -P {0}
run: |
set(ENV{CC} ${{ matrix.config.cc }})
set(ENV{CXX} ${{ matrix.config.cxx }})

file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/ninja" ninja_program)

# Determine triplet for vcpkg
if ("${{ matrix.config.use_libcxx }}" STREQUAL "true")
set(vcpkg_triplet "x64-linux-libcxx")
set(use_libcxx ON)
else()
set(vcpkg_triplet "x64-linux")
set(use_libcxx OFF)
endif()

execute_process(
COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake
-S .
-B build
-D CMAKE_BUILD_TYPE=${{ matrix.config.build_type }}
-G Ninja
-D CMAKE_MAKE_PROGRAM=${ninja_program}
-D CMAKE_TOOLCHAIN_FILE=${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake
-D VCPKG_TARGET_TRIPLET=${vcpkg_triplet}
-D skyr_BUILD_TESTS=OFF
-D skyr_BUILD_EXAMPLES=OFF
-D skyr_ENABLE_SANITIZERS=ON
-D skyr_BUILD_WITH_LLVM_LIBCXX=${use_libcxx}
-D skyr_WARNINGS_AS_ERRORS=OFF
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Bad exit status")
endif()

- name: Build
shell: cmake -P {0}
run: |
set(ENV{NINJA_STATUS} "[%f/%t %o/sec] ")

file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}" ccache_basedir)
set(ENV{CCACHE_BASEDIR} "${ccache_basedir}")
set(ENV{CCACHE_DIR} "${ccache_basedir}/.ccache")
set(ENV{CCACHE_COMPRESS} "true")
set(ENV{CCACHE_COMPRESSLEVEL} "6")
set(ENV{CCACHE_MAXSIZE} "400M")

execute_process(
COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --build build --target url_sanitizer_tests
RESULT_VARIABLE result
)
if (NOT result EQUAL 0)
message(FATAL_ERROR "Build failed")
endif()

- name: Run Sanitizer Tests
shell: bash
run: |
echo "========================================"
echo "Running AddressSanitizer + UBSan Tests"
echo "========================================"

# Set sanitizer options for comprehensive checking
# alloc_dealloc_mismatch=0: Suppress false positive from libc++ exception handling
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
export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=0

# Run the sanitizer test
./build/tests/sanitizers/url_sanitizer_tests

TEST_RESULT=$?

if [ $TEST_RESULT -eq 0 ]; then
echo "✓ All sanitizer tests passed - no memory safety issues detected!"
else
echo "✗ Sanitizer tests failed or detected issues"
exit 1
fi
5 changes: 1 addition & 4 deletions .github/workflows/wpt.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
name: Web Platform Tests

# Run on push to main, weekly schedule, and manual trigger
# Run on push to main and manual trigger
on:
push:
branches:
- main
schedule:
# Run every Monday at 00:00 UTC
- cron: '0 0 * * 1'
workflow_dispatch:
# Allow manual triggering

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

if (skyr_IS_TOP_LEVEL_PROJECT)
Expand Down Expand Up @@ -74,6 +75,7 @@ set(full_warnings $<BOOL:${skyr_FULL_WARNINGS}>)
set(warnings_as_errors $<BOOL:${skyr_WARNINGS_AS_ERRORS}>)
set(no_exceptions $<BOOL:${skyr_BUILD_WITHOUT_EXCEPTIONS}>)
set(no_rtti $<BOOL:${skyr_BUILD_WITHOUT_RTTI}>)
set(enable_sanitizers $<BOOL:${skyr_ENABLE_SANITIZERS}>)

set(gnu $<CXX_COMPILER_ID:GNU>)
set(clang $<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>)
Expand All @@ -92,6 +94,15 @@ if (skyr_BUILD_TESTS)
add_subdirectory(tests)
endif()

# Sanitizer tests (independent, no Catch2 needed)
if (skyr_ENABLE_SANITIZERS)
message(STATUS "[skyr-url] Configuring sanitizer tests")
if (NOT skyr_BUILD_TESTS)
enable_testing() # Only call this if not already enabled
endif()
add_subdirectory(tests/sanitizers)
endif()

# Documentation
if (skyr_BUILD_DOCS)
message(STATUS "[skyr-url] Configuring documentation")
Expand Down
86 changes: 86 additions & 0 deletions tests/sanitizers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright (c) Glyn Matthews 2025.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

include(${PROJECT_SOURCE_DIR}/cmake/skyr-url-functions.cmake)

# Sanitizer tests - only build when sanitizers are enabled
if (NOT skyr_ENABLE_SANITIZERS)
message(STATUS "Sanitizer tests skipped (enable with -Dskyr_ENABLE_SANITIZERS=ON)")
return()
endif()

message(STATUS "Building sanitizer tests with AddressSanitizer + UndefinedBehaviorSanitizer")

foreach(
file_name
url_sanitizer_tests.cpp
)
skyr_remove_extension(${file_name} basename)
set(test ${basename})
add_executable(${test} ${file_name})
add_dependencies(${test} skyr-url)

target_compile_options(
${test}
PRIVATE
# Standard warnings (but no -Werror for sanitizer tests)
$<$<AND:$<OR:${gnu},${clang}>,${full_warnings}>:-Wall>
$<$<AND:$<OR:${gnu},${clang}>,${no_exceptions}>:-fno-exceptions>
$<$<AND:$<OR:${gnu},${clang}>,${no_rtti}>:-fno-rtti>
$<${libcxx}:-stdlib=libc++>

# AddressSanitizer flags (GCC/Clang)
$<$<OR:${gnu},${clang}>:-fsanitize=address>
$<$<OR:${gnu},${clang}>:-fsanitize=undefined>
$<$<OR:${gnu},${clang}>:-fno-omit-frame-pointer>
$<$<OR:${gnu},${clang}>:-fno-optimize-sibling-calls>
$<$<OR:${gnu},${clang}>:-g>
$<$<OR:${gnu},${clang}>:-O1>

# MSVC sanitizer flags
$<$<AND:${msvc},${full_warnings}>:/W4>
$<$<AND:${msvc},$<NOT:${no_exceptions}>>:/EHsc>
$<$<AND:${msvc},${no_rtti}>:/GR->
$<${msvc}:/fsanitize=address>
$<${msvc}:/Zi>
)

target_link_options(
${test}
PRIVATE
# Sanitizer linker flags (GCC/Clang)
$<$<OR:${gnu},${clang}>:-fsanitize=address>
$<$<OR:${gnu},${clang}>:-fsanitize=undefined>

# MSVC sanitizer linker flags
$<${msvc}:/fsanitize=address>
)

target_link_libraries(
${test}
PRIVATE
skyr-url
)

set_target_properties(
${test}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/tests/sanitizers/
)

# Add as a test so it can be run with ctest
add_test(
NAME ${test}
COMMAND ${test}
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/tests/sanitizers/
)

# Set environment variables for sanitizers
set_tests_properties(
${test}
PROPERTIES
ENVIRONMENT "ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1"
)
endforeach()
Loading
Loading