Skip to content
Open
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
152 changes: 152 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,157 @@ jobs:
USE_BAZEL_VERSION: "7.4.1"
run: bazel test //...

sanitizers:
needs: [preflight-check]
# needs: [preflight-check, static-code-analysis, cargo-nextest]
if: ${{ needs.changes.outputs.source-code == 'true' }}
strategy:
matrix:
os: [ubuntu-latest]
toolchain: [stable] # Rust sanitizers require nightly
sanitizer: ["address", "ub", "address;ub", "thread"]
exclude:
# Only run address sanitizer on macOS
- os: macos-latest
sanitizer: "thread"
- os: macos-latest
sanitizer: "ub"
- os: macos-latest
sanitizer: "address;ub"
timeout-minutes: 60
runs-on: ${{ matrix.os }}
env:
RUST_BACKTRACE: 1
steps:
- name: Checkout sources
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4

- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: "3.31.x"

- name: Use cmake
run: cmake --version

- name: Setup Rust
uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 # ratchet:dtolnay/rust-toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
components: rustfmt, clippy
# targets: x86_64-unknown-linux-gnu, x86_64-apple-darwin

- name: Download artifact cargo-nextest
uses: ./.github/actions/download-cached-rust-tool
with:
artifact-bin-name: cargo-nextest
artifact-upload-name: ${{ runner.os }}-cargo-nextest

- name: Prepare Linux
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
internal/scripts/ci_prepare_ubuntu.sh
uname -a

# - name: Set Rust sanitizer flags
# run: |
# # Set default target based on OS
# if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
# RUST_TARGET="x86_64-unknown-linux-gnu"
# elif [[ "${{ matrix.os }}" == "macos-latest" ]]; then
# RUST_TARGET="x86_64-apple-darwin"
# fi

# # Set sanitizer-specific flags
# if [[ "${{ matrix.sanitizer }}" == "address" ]]; then
# echo "RUSTFLAGS=-Zsanitizer=address -Copt-level=1" >> $GITHUB_ENV
# elif [[ "${{ matrix.sanitizer }}" == "ub" ]]; then
# # Rust doesn't have a specific undefined behavior sanitizer, but we can enable debug assertions
# echo "RUSTFLAGS=-Cdebug-assertions=on -Coverflow-checks=on -Copt-level=1" >> $GITHUB_ENV
# elif [[ "${{ matrix.sanitizer }}" == "address;ub" ]]; then
# echo "RUSTFLAGS=-Zsanitizer=address -Cdebug-assertions=on -Coverflow-checks=on -Copt-level=1" >> $GITHUB_ENV
# elif [[ "${{ matrix.sanitizer }}" == "thread" ]]; then
# echo "RUSTFLAGS=-Zsanitizer=thread -Copt-level=1" >> $GITHUB_ENV
# fi

# echo "RUST_TARGET=$RUST_TARGET" >> $GITHUB_ENV

- name: Run cargo build
run: cargo build --workspace --all-targets

- name: Run cargo nextest
run: cargo nextest run --workspace --all-targets --no-fail-fast

- name: Build iceoryx_hoofs
run: internal/scripts/ci_build_and_install_iceoryx_hoofs.sh

- name: Build language bindings with sanitizers
run: |
cmake -S . -B target/ff/cc/build \
-DBUILD_EXAMPLES=ON \
-DBUILD_TESTING=ON \
-DCMAKE_BUILD_TYPE=Debug \
-DSANITIZERS="${{ matrix.sanitizer }}" \
-DCMAKE_INSTALL_PREFIX=target/ff/cc/install \
-DCMAKE_PREFIX_PATH="${{ github.workspace }}/target/ff/iceoryx/install" \
-DRUST_BUILD_ARTIFACT_PATH="${{ github.workspace }}/target/debug"
cmake --build target/ff/cc/build
cmake --install target/ff/cc/build

- name: Run C language binding tests with sanitizers
env:
ASAN_OPTIONS: "detect_leaks=1:detect_stack_use_after_return=1:detect_stack_use_after_scope=1:check_initialization_order=true:strict_init_order=true:new_delete_type_mismatch=0:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/asan_runtime.txt:intercept_tls_get_addr=0"
LSAN_OPTIONS: "verbosity=1:log_threads=1:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/lsan_runtime.txt"
UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
TSAN_OPTIONS: "halt_on_error=1:history_size=7:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/tsan_runtime.txt"
run: target/ff/cc/build/tests/iceoryx2-c-tests

- name: Run C++ language binding tests with sanitizers
env:
ASAN_OPTIONS: "detect_leaks=1:detect_stack_use_after_return=1:detect_stack_use_after_scope=1:check_initialization_order=true:strict_init_order=true:new_delete_type_mismatch=0:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/asan_runtime.txt:intercept_tls_get_addr=0"
LSAN_OPTIONS: "verbosity=1:log_threads=1:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/lsan_runtime.txt"
UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
TSAN_OPTIONS: "halt_on_error=1:history_size=7:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/tsan_runtime.txt"
run: target/ff/cc/build/tests/iceoryx2-cxx-tests

# - name: Run C examples with sanitizers
# env:
# ASAN_OPTIONS: "detect_leaks=1:detect_stack_use_after_return=1:detect_stack_use_after_scope=1:check_initialization_order=true:strict_init_order=true:new_delete_type_mismatch=0:intercept_tls_get_addr=0"
# LSAN_OPTIONS: "verbosity=1:log_threads=1"
# UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
# TSAN_OPTIONS: "halt_on_error=1:history_size=7"
# run: |
# cd target/ff/cc/build/examples/c
# find . -maxdepth 1 -type f -executable | while read example; do
# echo "Running C example: $example"
# timeout 10s $example || true
# done

# - name: Run C++ examples with sanitizers
# env:
# ASAN_OPTIONS: "detect_leaks=1:detect_stack_use_after_return=1:detect_stack_use_after_scope=1:check_initialization_order=true:strict_init_order=true:new_delete_type_mismatch=0:intercept_tls_get_addr=0"
# LSAN_OPTIONS: "verbosity=1:log_threads=1"
# UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
# TSAN_OPTIONS: "halt_on_error=1:history_size=7"
# run: |
# cd target/ff/cc/build/examples/cxx
# find . -maxdepth 1 -type f -executable | while read example; do
# echo "Running C++ example: $example"
# timeout 10s $example || true
# done

- name: Run end-to-end tests with sanitizers
# Skip end-to-end tests with thread sanitizer due to potential issues with dynamic linking
if: ${{ matrix.sanitizer != 'thread' }}
env:
ASAN_OPTIONS: "detect_leaks=1:detect_stack_use_after_return=1:detect_stack_use_after_scope=1:check_initialization_order=true:strict_init_order=true:new_delete_type_mismatch=0:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/asan_runtime.txt:intercept_tls_get_addr=0"
LSAN_OPTIONS: "verbosity=1:log_threads=1:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/lsan_runtime.txt"
UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
TSAN_OPTIONS: "halt_on_error=1:history_size=7:suppressions=${{ github.workspace }}/target/ff/cc/build/sanitizer_blacklist/tsan_runtime.txt"
run: |
cd "${GITHUB_WORKSPACE}"
internal/scripts/ci_build_and_run_end_to_end_tests.sh no-build

cargo-publish-dry-run:
needs: [preflight-check, static-code-analysis]
if: ${{ needs.changes.outputs.source-code == 'true' }}
Expand Down Expand Up @@ -792,6 +943,7 @@ jobs:
x86_32,
python-bindings,
end-to-end-tests,
sanitizers,
]
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 2 additions & 0 deletions doc/release-notes/iceoryx2-unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
[#1133](https://github.com/eclipse-iceoryx/iceoryx2/issues/1133)
* Enable -Wconversion warning for the C and C++ code
[#956](https://github.com/eclipse-iceoryx/iceoryx2/issues/956)
* Add thread sanitizer
[#957](https://github.com/eclipse-iceoryx/iceoryx2/issues/957)

### Workflow

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ if(NOT ICEORYX2_COMMON_OPTIONS_AND_PARAMS_LISTED)
DEFAULT_VALUE OFF
)

add_option(
add_param(
NAME SANITIZERS
DESCRIPTION "Build with undefined-behavior- and address-sanitizer"
DEFAULT_VALUE OFF
DESCRIPTION "Sanitizer configuration: 'address', 'ub', 'address;ub', 'thread', or empty string (disabled)"
DEFAULT_VALUE ""
)

add_option(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ endmacro()
macro(add_param)
set(ONE_VALUE_ARGS NAME DESCRIPTION DEFAULT_VALUE)
cmake_parse_arguments(ADD_PARAM "" "${ONE_VALUE_ARGS}" "" ${ARGN})
if(NOT ${ADD_PARAM_NAME})
if(NOT DEFINED ${ADD_PARAM_NAME})
set(${ADD_PARAM_NAME} ${ADD_PARAM_DEFAULT_VALUE})
endif()
message(STATUS " ${ADD_PARAM_NAME}: ${${ADD_PARAM_NAME}} (Description: ${ADD_PARAM_DESCRIPTION})")
Expand Down
130 changes: 128 additions & 2 deletions iceoryx2-cmake-modules/modules/Iceoryx2Sanitizer.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,131 @@
#
# SPDX-License-Identifier: Apache-2.0 OR MIT

# TODO #957: differentiate between address and thread sanitizer
set(ICEORYX2_SANITZER_FLAGS -fsanitize=address -fsanitize=undefined CACHE INTERNAL "")
# Sanitizer blacklist creation functions
function(iox2_create_asan_compile_time_blacklist BLACKLIST_FILE_PATH)
# Suppressing Errors in Recompiled Code (Blacklist)
# (https://clang.llvm.org/docs/AddressSanitizer.html#suppressing-errors-in-recompiled-code-blacklist)
# More details about the syntax can be found here (https://clang.llvm.org/docs/SanitizerSpecialCaseList.html)
if(NOT EXISTS ${BLACKLIST_FILE_PATH})
file(WRITE ${BLACKLIST_FILE_PATH} "# This file is auto-generated from iceoryx2-cmake-modules/modules/Iceoryx2Sanitizer.cmake\n")
file(APPEND ${BLACKLIST_FILE_PATH} "# src:*file_name.cpp*\n")
file(APPEND ${BLACKLIST_FILE_PATH} "# fun:*Test_Name*\n")
file(APPEND ${BLACKLIST_FILE_PATH} "# End of file\n")
endif()
endfunction()

function(iox2_create_asan_runtime_blacklist BLACKLIST_FILE_PATH)
# Suppress errors in external libraries (https://clang.llvm.org/docs/AddressSanitizer.html#suppressing-reports-in-external-libraries)
# List of errors generated in .inl files. These cannot be suppressed with -fsanitize-blacklist!
# We enable sanitizer flags for core components, not in tests (mainly to avoid catching errors in test cases, at least for now)
# NOTE : AddressSanitizer won't generate any report for the suppressed errors.
# Only way to see detailed errors is to disable the entries here & run
if(NOT EXISTS ${BLACKLIST_FILE_PATH})
file(WRITE ${BLACKLIST_FILE_PATH} "# This file is auto-generated from iceoryx2-cmake-modules/modules/Iceoryx2Sanitizer.cmake\n")
file(APPEND ${BLACKLIST_FILE_PATH} "#interceptor_via_fun:-[ClassName objCMethodToSuppress:]\n")
file(APPEND ${BLACKLIST_FILE_PATH} "#interceptor_via_lib:NameOfTheLibraryToSuppress\n")
file(APPEND ${BLACKLIST_FILE_PATH} "# End of file\n")
endif()
endfunction()

function(iox2_create_tsan_runtime_blacklist BLACKLIST_FILE_PATH)
# (https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions)
# The suppression types are:
# race suppresses data races and use-after-free reports
# race_top same as race, but matched only against the top stack frame
# thread suppresses reports related to threads (leaks)
# mutex suppresses reports related to mutexes (destruction of a locked mutex)
# signal suppresses reports related to signal handlers (handler calls malloc())
# deadlock suppresses lock inversion reports
# called_from_lib suppresses all interceptors in a particular library
if(NOT EXISTS ${BLACKLIST_FILE_PATH})
file(WRITE ${BLACKLIST_FILE_PATH} "# This file is auto-generated from iceoryx2-cmake-modules/modules/Iceoryx2Sanitizer.cmake\n")
file(APPEND ${BLACKLIST_FILE_PATH} "mutex:*MutexWithDeadlockDetectionsFailsWhenSameThreadTriesToUnlockItTwice*\n")
file(APPEND ${BLACKLIST_FILE_PATH} "mutex:*MutexWithDeadlockDetectionsFailsWhenAnotherThreadTriesToUnlock*\n")
file(APPEND ${BLACKLIST_FILE_PATH} "mutex:*MutexWithStallWhenLockedBehaviorDoesntUnlockMutexWhenThreadTerminates*\n")
file(APPEND ${BLACKLIST_FILE_PATH} "race:*\n")
file(APPEND ${BLACKLIST_FILE_PATH} "deadlock:*TimingTest_AttachingInCallbackWorks*\n")
file(APPEND ${BLACKLIST_FILE_PATH} "# End of file\n")
endif()
endfunction()

function(iox2_create_lsan_runtime_blacklist BLACKLIST_FILE_PATH)
# Suppress known memory leaks (https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer)
# Below function/files contains memory leaks!
# LeakSanitizer wont report the problem for the entries here , however you can find the suppression report in the log
#
# e.g.
# Suppressions used:
# count bytes template
# 8 642 libacl.so.1
# 1 24 iox::UnixDomainSocket::timedReceive
# 1 24 iox::MessageQueue::receive
if(NOT EXISTS ${BLACKLIST_FILE_PATH})
file(WRITE ${BLACKLIST_FILE_PATH} "# This file is auto-generated from iceoryx2-cmake-modules/modules/Iceoryx2Sanitizer.cmake\n")
file(APPEND ${BLACKLIST_FILE_PATH} "#leak:libacl.so.1\n")
file(APPEND ${BLACKLIST_FILE_PATH} "#leak:iox::UnixDomainSocket::timedReceive\n")
file(APPEND ${BLACKLIST_FILE_PATH} "# End of file\n")
endif()
endfunction()

# Parse SANITIZERS string and set appropriate flags
set(ICEORYX2_SANITIZER_FLAGS "" CACHE INTERNAL "")
set(ICEORYX2_SANITIZER_BLACKLIST "" CACHE INTERNAL "")

# Strip any whitespace from SANITIZERS
if(DEFINED SANITIZERS)
string(STRIP "${SANITIZERS}" SANITIZERS_STRIPPED)
else()
set(SANITIZERS_STRIPPED "")
endif()

# Check for invalid combinations
if(SANITIZERS_STRIPPED MATCHES "address" AND SANITIZERS_STRIPPED MATCHES "thread")
message(FATAL_ERROR "You cannot run address sanitizer and thread sanitizer together. Choose one or the other!")
endif()

# Create blacklist directories and files if sanitizers are enabled
if(DEFINED SANITIZERS AND NOT SANITIZERS_STRIPPED STREQUAL "" AND NOT SANITIZERS_STRIPPED STREQUAL "OFF" AND NOT SANITIZERS_STRIPPED STREQUAL "FALSE")
# Create sanitizer blacklist directory
set(ICEORYX2_SANITIZER_BLACKLIST_DIR ${CMAKE_BINARY_DIR}/sanitizer_blacklist)
file(MAKE_DIRECTORY ${ICEORYX2_SANITIZER_BLACKLIST_DIR})

# Create runtime blacklists (always created when any sanitizer is enabled)
iox2_create_asan_runtime_blacklist(${ICEORYX2_SANITIZER_BLACKLIST_DIR}/asan_runtime.txt)
iox2_create_lsan_runtime_blacklist(${ICEORYX2_SANITIZER_BLACKLIST_DIR}/lsan_runtime.txt)
iox2_create_tsan_runtime_blacklist(${ICEORYX2_SANITIZER_BLACKLIST_DIR}/tsan_runtime.txt)

# Create compile-time blacklist for Clang
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set(ICEORYX2_SANITIZER_BLACKLIST_FILE ${ICEORYX2_SANITIZER_BLACKLIST_DIR}/sanitizer_compile_time.txt)
iox2_create_asan_compile_time_blacklist(${ICEORYX2_SANITIZER_BLACKLIST_FILE})
set(ICEORYX2_SANITIZER_BLACKLIST -fsanitize-blacklist=${ICEORYX2_SANITIZER_BLACKLIST_FILE} CACHE INTERNAL "")
endif()
endif()

# Common sanitizer flags
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set(ICEORYX2_SANITIZER_COMMON_FLAGS -fno-omit-frame-pointer -fno-optimize-sibling-calls)
else()
set(ICEORYX2_SANITIZER_COMMON_FLAGS "")
endif()

# Set sanitizer flags based on SANITIZERS value
if(NOT DEFINED SANITIZERS OR SANITIZERS_STRIPPED STREQUAL "" OR SANITIZERS_STRIPPED STREQUAL "OFF" OR SANITIZERS_STRIPPED STREQUAL "FALSE")
# No sanitizers enabled - empty string is the default
elseif(SANITIZERS_STRIPPED STREQUAL "address")
set(ICEORYX2_SANITIZER_FLAGS ${ICEORYX2_SANITIZER_COMMON_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope ${ICEORYX2_SANITIZER_BLACKLIST} CACHE INTERNAL "")
elseif(SANITIZERS_STRIPPED STREQUAL "ub")
set(ICEORYX2_SANITIZER_FLAGS ${ICEORYX2_SANITIZER_COMMON_FLAGS} -fsanitize=undefined -fno-sanitize-recover=undefined CACHE INTERNAL "")
elseif(SANITIZERS_STRIPPED STREQUAL "address;ub")
set(ICEORYX2_SANITIZER_FLAGS ${ICEORYX2_SANITIZER_COMMON_FLAGS} -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined -fno-sanitize-recover=undefined ${ICEORYX2_SANITIZER_BLACKLIST} CACHE INTERNAL "")
elseif(SANITIZERS_STRIPPED STREQUAL "thread")
set(ICEORYX2_SANITIZER_FLAGS ${ICEORYX2_SANITIZER_COMMON_FLAGS} -fsanitize=thread CACHE INTERNAL "")
else()
message(FATAL_ERROR "Invalid SANITIZERS value: '${SANITIZERS_STRIPPED}' (original: '${SANITIZERS}'). Valid options are: 'address', 'ub', 'address;ub', 'thread', or empty string (disabled)")
endif()

# Export the blacklist directory path for use in CI
if(DEFINED ICEORYX2_SANITIZER_BLACKLIST_DIR)
set(ICEORYX2_SANITIZER_BLACKLIST_DIR ${ICEORYX2_SANITIZER_BLACKLIST_DIR} CACHE PATH "Path to sanitizer blacklist directory" FORCE)
endif()
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ if(WARNING_AS_ERROR)
set(ICEORYX2_CXX_WARNINGS ${ICEORYX2_CXX_WARNINGS} -Werror CACHE INTERNAL "")
endif()

set(ICEORYX2_SANITZER_FLAGS CACHE INTERNAL "")
if(SANITIZERS)
set(ICEORYX2_SANITIZER_FLAGS CACHE INTERNAL "")
set(ICEORYX2_LINK_FLAGS CACHE INTERNAL "")

if(DEFINED SANITIZERS AND NOT SANITIZERS STREQUAL "")
include(Iceoryx2Sanitizer)

set(ICEORYX2_C_FLAGS ${ICEORYX2_C_FLAGS} ${ICEORYX2_SANITIZER_FLAGS} CACHE INTERNAL "")
set(ICEORYX2_CXX_FLAGS ${ICEORYX2_CXX_FLAGS} ${ICEORYX2_SANITIZER_FLAGS} CACHE INTERNAL "")
set(ICEORYX2_TEST_CXX_FLAGS ${ICEORYX2_TEST_CXX_FLAGS} ${ICEORYX2_SANITIZER_FLAGS} CACHE INTERNAL "")

endif()

set(ICEORYX2_COVERAGE_FLAGS CACHE INTERNAL "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ if(WARNING_AS_ERROR)
set(ICEORYX2_CXX_WARNINGS ${ICEORYX2_CXX_WARNINGS} -Werror CACHE INTERNAL "")
endif()

if(SANITIZERS)
set(ICEORYX2_LINK_FLAGS CACHE INTERNAL "")

if(DEFINED SANITIZERS AND NOT SANITIZERS STREQUAL "")
include(Iceoryx2Sanitizer)

set(ICEORYX2_CXX_FLAGS "${ICEORYX2_CXX_FLAGS} ${ICEORYX2_SANITZER_FLAGS}" CACHE INTERNAL "")
set(ICEORYX2_TEST_CXX_FLAGS "${ICEORYX2_TEST_CXX_FLAGS} ${ICEORYX2_SANITZER_FLAGS}" CACHE INTERNAL "")
set(ICEORYX2_C_FLAGS ${ICEORYX2_C_FLAGS} ${ICEORYX2_SANITIZER_FLAGS} CACHE INTERNAL "")
set(ICEORYX2_CXX_FLAGS ${ICEORYX2_CXX_FLAGS} ${ICEORYX2_SANITIZER_FLAGS} CACHE INTERNAL "")
set(ICEORYX2_TEST_CXX_FLAGS ${ICEORYX2_TEST_CXX_FLAGS} ${ICEORYX2_SANITIZER_FLAGS} CACHE INTERNAL "")

# Sanitizer flags must also be added to linker flags
set(ICEORYX2_LINK_FLAGS ${ICEORYX2_LINK_FLAGS} ${ICEORYX2_SANITIZER_FLAGS} CACHE INTERNAL "")

endif()
Loading