From 1ed4f047946a91293dcec4a47f3df90df0669c64 Mon Sep 17 00:00:00 2001 From: Eric Riff Date: Mon, 6 Oct 2025 16:54:36 +0000 Subject: [PATCH 1/7] Add support for sanitizers including some GHAs --- .github/workflows/cmake_ubuntu_aubsan.yml | 57 +++++++++++++++++++++++ .github/workflows/cmake_ubuntu_tsan.yml | 56 ++++++++++++++++++++++ CMakeLists.txt | 5 ++ cmake/sanitizers.cmake | 30 ++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 .github/workflows/cmake_ubuntu_aubsan.yml create mode 100644 .github/workflows/cmake_ubuntu_tsan.yml create mode 100644 cmake/sanitizers.cmake diff --git a/.github/workflows/cmake_ubuntu_aubsan.yml b/.github/workflows/cmake_ubuntu_aubsan.yml new file mode 100644 index 000000000..13db9ff15 --- /dev/null +++ b/.github/workflows/cmake_ubuntu_aubsan.yml @@ -0,0 +1,57 @@ +name: cmake Ubuntu with Address and Undefined Behavior Sanitizers + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04] + + steps: + - uses: actions/checkout@v2 + + - name: Install Conan + id: conan + uses: turtlebrowser/get-conan@main + + - name: Create default profile + run: conan profile detect + + - name: Install conan dependencies + run: conan install conanfile.py -s build_type=${{env.BUILD_TYPE}} --build=missing + + - name: Normalize build type + shell: bash + # The build type is Capitalized, e.g. Release, but the preset is all lowercase, e.g. release. + # There is no built in way to do string manipulations on GHA as far as I know.` + run: echo "BUILD_TYPE_LOWERCASE=$(echo "${BUILD_TYPE}" | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Configure CMake + shell: bash + run: cmake --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} -DBTCPP_ENABLE_ASAN:BOOL=ON -DBTCPP_ENABLE_UBSAN:BOOL=ON + + - name: Build + shell: bash + run: cmake --build --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} + + - name: run test (Linux + Address and Undefined Behavior Sanitizers) + env: + GTEST_COLOR: "On" + ASAN_OPTIONS: "color=always" + UBSAN_OPTIONS: "halt_on_error=1:print_stacktrace=1:color=always" + run: ctest --test-dir build/${{env.BUILD_TYPE}} diff --git a/.github/workflows/cmake_ubuntu_tsan.yml b/.github/workflows/cmake_ubuntu_tsan.yml new file mode 100644 index 000000000..197f18ebe --- /dev/null +++ b/.github/workflows/cmake_ubuntu_tsan.yml @@ -0,0 +1,56 @@ +name: cmake Ubuntu with Thread Sanitizer + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04] + + steps: + - uses: actions/checkout@v2 + + - name: Install Conan + id: conan + uses: turtlebrowser/get-conan@main + + - name: Create default profile + run: conan profile detect + + - name: Install conan dependencies + run: conan install conanfile.py -s build_type=${{env.BUILD_TYPE}} --build=missing + + - name: Normalize build type + shell: bash + # The build type is Capitalized, e.g. Release, but the preset is all lowercase, e.g. release. + # There is no built in way to do string manipulations on GHA as far as I know.` + run: echo "BUILD_TYPE_LOWERCASE=$(echo "${BUILD_TYPE}" | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Configure CMake + shell: bash + run: cmake --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} -DBTCPP_ENABLE_TSAN:BOOL=ON + + - name: Build + shell: bash + run: cmake --build --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} + + - name: run test (Linux + Thread Sanitizer) + env: + GTEST_COLOR: "On" + TSAN_OPTIONS: "color=always" + run: sudo sysctl vm.mmap_rnd_bits=28 && ctest --test-dir build/${{env.BUILD_TYPE}} diff --git a/CMakeLists.txt b/CMakeLists.txt index a0cb7b202..320877a91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,9 @@ option(BTCPP_EXAMPLES "Build tutorials and examples" ON) option(BUILD_TESTING "Build the unit tests" ON) option(BTCPP_GROOT_INTERFACE "Add Groot2 connection. Requires ZeroMQ" ON) option(BTCPP_SQLITE_LOGGING "Add SQLite logging." ON) +option(BTCPP_ENABLE_ASAN "Enable Address Sanitizer" OFF) +option(BTCPP_ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF) +option(BTCPP_ENABLE_TSAN "Enable Thread Sanitizer" OFF) option(USE_V3_COMPATIBLE_NAMES "Use some alias to compile more easily old 3.x code" OFF) option(ENABLE_FUZZING "Enable fuzzing builds" OFF) @@ -54,6 +57,8 @@ endif() set(CMAKE_CONFIG_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CONFIG_PATH}") +include(sanitizers) + set(BTCPP_LIBRARY ${PROJECT_NAME}) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) diff --git a/cmake/sanitizers.cmake b/cmake/sanitizers.cmake new file mode 100644 index 000000000..fdb542e68 --- /dev/null +++ b/cmake/sanitizers.cmake @@ -0,0 +1,30 @@ +if(BTCPP_ENABLE_ASAN OR BTCPP_ENABLE_UBSAN OR BTCPP_ENABLE_TSAN) + if(NOT CMAKE_BUILD_TYPE MATCHES "Debug|RelWithDebInfo") + message(FATAL_ERROR "Sanitizers require debug symbols. Please set CMAKE_BUILD_TYPE to Debug or RelWithDebInfo.") + endif() + add_compile_options(-fno-omit-frame-pointer) +endif() + +# Address Sanitizer and Undefined Behavior Sanitizer can be run at the same time. +# Thread Sanitizer requires its own build. +if(BTCPP_ENABLE_TSAN AND (BTCPP_ENABLE_ASAN OR BTCPP_ENABLE_UBSAN)) + message(FATAL_ERROR "TSAN is not compatible with ASAN or UBSAN. Please enable only one of them.") +endif() + +if(BTCPP_ENABLE_ASAN) + message(STATUS "Address Sanitizer enabled") + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +if(BTCPP_ENABLE_UBSAN) + message(STATUS "Undefined Behavior Sanitizer enabled") + add_compile_options(-fsanitize=undefined) + add_link_options(-fsanitize=undefined) +endif() + +if(BTCPP_ENABLE_TSAN) + message(STATUS "Thread Sanitizer enabled") + add_compile_options(-fsanitize=thread) + add_link_options(-fsanitize=thread) +endif() From 92feada203d31f94bb1c85973811d125149a4500 Mon Sep 17 00:00:00 2001 From: Eric Riff Date: Tue, 7 Oct 2025 18:24:50 +0000 Subject: [PATCH 2/7] use gtest_discover_tests to regiester the unit tests this modern approach registers many individual tests instead of a single monolitic test so if one fails the rest continue running which allows the developer to flag multiple failing tests on a single run It also speeds up testing since tests run in parallel --- tests/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9c62b335e..56a03e501 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -43,16 +43,19 @@ if(ament_cmake_FOUND) else() + enable_testing() + find_package(GTest REQUIRED) + include(GoogleTest) - enable_testing() add_executable(behaviortree_cpp_test ${BT_TESTS}) - add_test(NAME btcpp_test COMMAND behaviortree_cpp_test) target_link_libraries(behaviortree_cpp_test GTest::gtest GTest::gtest_main) + gtest_discover_tests(behaviortree_cpp_test) + endif() target_include_directories(behaviortree_cpp_test PRIVATE include) From 2523528fcd68b197f2604fda5266d3db6a478eeb Mon Sep 17 00:00:00 2001 From: Eric Riff Date: Tue, 7 Oct 2025 18:55:31 +0000 Subject: [PATCH 3/7] Combine sanitizer actions into a single file --- ...aubsan.yml => cmake_ubuntu_sanitizers.yml} | 15 ++++- .github/workflows/cmake_ubuntu_tsan.yml | 56 ------------------- 2 files changed, 12 insertions(+), 59 deletions(-) rename .github/workflows/{cmake_ubuntu_aubsan.yml => cmake_ubuntu_sanitizers.yml} (75%) delete mode 100644 .github/workflows/cmake_ubuntu_tsan.yml diff --git a/.github/workflows/cmake_ubuntu_aubsan.yml b/.github/workflows/cmake_ubuntu_sanitizers.yml similarity index 75% rename from .github/workflows/cmake_ubuntu_aubsan.yml rename to .github/workflows/cmake_ubuntu_sanitizers.yml index 13db9ff15..4c0b3bd17 100644 --- a/.github/workflows/cmake_ubuntu_aubsan.yml +++ b/.github/workflows/cmake_ubuntu_sanitizers.yml @@ -1,4 +1,4 @@ -name: cmake Ubuntu with Address and Undefined Behavior Sanitizers +name: cmake Ubuntu Sanitizers on: push: @@ -21,6 +21,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04] + sanitizer: [asan_ubsan, tsan] steps: - uses: actions/checkout@v2 @@ -43,7 +44,14 @@ jobs: - name: Configure CMake shell: bash - run: cmake --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} -DBTCPP_ENABLE_ASAN:BOOL=ON -DBTCPP_ENABLE_UBSAN:BOOL=ON + run: | + if [[ "${{ matrix.sanitizer }}" == "asan_ubsan" ]]; then + cmake --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} \ + -DBTCPP_ENABLE_ASAN:BOOL=ON -DBTCPP_ENABLE_UBSAN:BOOL=ON + else + cmake --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} \ + -DBTCPP_ENABLE_TSAN:BOOL=ON + fi - name: Build shell: bash @@ -54,4 +62,5 @@ jobs: GTEST_COLOR: "On" ASAN_OPTIONS: "color=always" UBSAN_OPTIONS: "halt_on_error=1:print_stacktrace=1:color=always" - run: ctest --test-dir build/${{env.BUILD_TYPE}} + TSAN_OPTIONS: "color=always" + run: sudo sysctl vm.mmap_rnd_bits=28 && ctest --test-dir build/${{env.BUILD_TYPE}} --output-on-failure diff --git a/.github/workflows/cmake_ubuntu_tsan.yml b/.github/workflows/cmake_ubuntu_tsan.yml deleted file mode 100644 index 197f18ebe..000000000 --- a/.github/workflows/cmake_ubuntu_tsan.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: cmake Ubuntu with Thread Sanitizer - -on: - push: - branches: - - master - pull_request: - types: [opened, synchronize, reopened] - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Debug - -jobs: - build: - # The CMake configure and build commands are platform agnostic and should work equally - # well on Windows or Mac. You can convert this to a matrix build if you need - # cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-22.04] - - steps: - - uses: actions/checkout@v2 - - - name: Install Conan - id: conan - uses: turtlebrowser/get-conan@main - - - name: Create default profile - run: conan profile detect - - - name: Install conan dependencies - run: conan install conanfile.py -s build_type=${{env.BUILD_TYPE}} --build=missing - - - name: Normalize build type - shell: bash - # The build type is Capitalized, e.g. Release, but the preset is all lowercase, e.g. release. - # There is no built in way to do string manipulations on GHA as far as I know.` - run: echo "BUILD_TYPE_LOWERCASE=$(echo "${BUILD_TYPE}" | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - - - name: Configure CMake - shell: bash - run: cmake --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} -DBTCPP_ENABLE_TSAN:BOOL=ON - - - name: Build - shell: bash - run: cmake --build --preset conan-${{ env.BUILD_TYPE_LOWERCASE }} - - - name: run test (Linux + Thread Sanitizer) - env: - GTEST_COLOR: "On" - TSAN_OPTIONS: "color=always" - run: sudo sysctl vm.mmap_rnd_bits=28 && ctest --test-dir build/${{env.BUILD_TYPE}} From 1b989f35fbf711a3ec65534a408b8b731db541b1 Mon Sep 17 00:00:00 2001 From: Eric Riff Date: Tue, 7 Oct 2025 19:22:29 +0000 Subject: [PATCH 4/7] Do not fail fast. We want results of both sanitizer runs --- .github/workflows/cmake_ubuntu_sanitizers.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cmake_ubuntu_sanitizers.yml b/.github/workflows/cmake_ubuntu_sanitizers.yml index 4c0b3bd17..e14a34e47 100644 --- a/.github/workflows/cmake_ubuntu_sanitizers.yml +++ b/.github/workflows/cmake_ubuntu_sanitizers.yml @@ -19,6 +19,7 @@ jobs: # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-22.04] sanitizer: [asan_ubsan, tsan] From 390b99bb79ee637a9bbf52eaf7182b77aa055c41 Mon Sep 17 00:00:00 2001 From: Eric Riff Date: Tue, 7 Oct 2025 19:03:47 +0000 Subject: [PATCH 5/7] Fix windows builds --- tests/CMakeLists.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 56a03e501..b268235df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -54,7 +54,16 @@ else() GTest::gtest GTest::gtest_main) - gtest_discover_tests(behaviortree_cpp_test) + # gtest_discover_tests queries the test executable for available tests and registers them on ctest individually + # On Windows it needs a little help to find the shared libraries + if(WIN32) + gtest_discover_tests(behaviortree_cpp_test + DISCOVERY_MODE PRE_TEST + DISCOVERY_ENVIRONMENT "PATH=$;$ENV{PATH}" + ) + else() + gtest_discover_tests(behaviortree_cpp_test) + endif() endif() From d6221b0435fe6444257ab9bbe52bf5942613afa8 Mon Sep 17 00:00:00 2001 From: Eric Riff Date: Tue, 7 Oct 2025 19:40:25 +0000 Subject: [PATCH 6/7] Leave a note for posterity --- .github/workflows/cmake_ubuntu_sanitizers.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cmake_ubuntu_sanitizers.yml b/.github/workflows/cmake_ubuntu_sanitizers.yml index e14a34e47..c379171a1 100644 --- a/.github/workflows/cmake_ubuntu_sanitizers.yml +++ b/.github/workflows/cmake_ubuntu_sanitizers.yml @@ -64,4 +64,6 @@ jobs: ASAN_OPTIONS: "color=always" UBSAN_OPTIONS: "halt_on_error=1:print_stacktrace=1:color=always" TSAN_OPTIONS: "color=always" + # There is a known issue with TSAN on recent kernel versions. Without the vm.mmap_rnd_bits=28 + # workaround all binaries with TSan enabled crash with "FATAL: ThreadSanitizer: unexpected memory mapping" run: sudo sysctl vm.mmap_rnd_bits=28 && ctest --test-dir build/${{env.BUILD_TYPE}} --output-on-failure From 24f3d93ceb8fa1d71a312c1910a21fe597bed316 Mon Sep 17 00:00:00 2001 From: Eric Riff Date: Tue, 7 Oct 2025 19:46:22 +0000 Subject: [PATCH 7/7] Improve error message --- cmake/sanitizers.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/sanitizers.cmake b/cmake/sanitizers.cmake index fdb542e68..347c2aa0d 100644 --- a/cmake/sanitizers.cmake +++ b/cmake/sanitizers.cmake @@ -8,7 +8,7 @@ endif() # Address Sanitizer and Undefined Behavior Sanitizer can be run at the same time. # Thread Sanitizer requires its own build. if(BTCPP_ENABLE_TSAN AND (BTCPP_ENABLE_ASAN OR BTCPP_ENABLE_UBSAN)) - message(FATAL_ERROR "TSAN is not compatible with ASAN or UBSAN. Please enable only one of them.") + message(FATAL_ERROR "TSan is not compatible with ASan or UBSan. ASan and UBSan can run together, but TSan requires its own separate build.") endif() if(BTCPP_ENABLE_ASAN)