diff --git a/.github/workflows/cmake_ubuntu_sanitizers.yml b/.github/workflows/cmake_ubuntu_sanitizers.yml new file mode 100644 index 000000000..c379171a1 --- /dev/null +++ b/.github/workflows/cmake_ubuntu_sanitizers.yml @@ -0,0 +1,69 @@ +name: cmake Ubuntu 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: + fail-fast: false + matrix: + os: [ubuntu-22.04] + sanitizer: [asan_ubsan, tsan] + + 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: | + 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 + 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" + 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 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..347c2aa0d --- /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. ASan and UBSan can run together, but TSan requires its own separate build.") +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() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9c62b335e..b268235df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -43,16 +43,28 @@ 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 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() target_include_directories(behaviortree_cpp_test PRIVATE include)