diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 0fcb07056e..3351ea62e0 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -209,7 +209,6 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: ): continue - cxx_flags = "" # Enable code coverage for Debian Bookworm using GCC 14 in Debug and no # Unity on linux/amd64 if ( @@ -218,8 +217,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: and "-Dunity=OFF" in cmake_args and architecture["platform"] == "linux/amd64" ): - cmake_args = f"-Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 {cmake_args}" - cxx_flags = f"-O0 {cxx_flags}" + cmake_args = f"-Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0 {cmake_args}" # Generate a unique name for the configuration, e.g. macos-arm64-debug # or debian-bookworm-gcc-12-amd64-release-unity. @@ -247,25 +245,26 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: "gcc-15", "clang-20", }: - configs = addSanitizerConfigs(architecture, os, cmake_args, cxx_flags) - if "asan_ubsan" in configs: - configurations.append( - { - "config_name": config_name + "-asan-ubsan", - "cmake_args": configs["asan_ubsan"], - "cmake_target": cmake_target, - "build_only": build_only, - "build_type": build_type, - "os": os, - "architecture": architecture, - "sanitizers": "Address,UndefinedBehavior", - } - ) - if "tsan_ubsan" in configs: + # Add ASAN + UBSAN configuration. + configurations.append( + { + "config_name": config_name + "-asan-ubsan", + "cmake_args": cmake_args, + "cmake_target": cmake_target, + "build_only": build_only, + "build_type": build_type, + "os": os, + "architecture": architecture, + "sanitizers": "Address,UndefinedBehavior", + } + ) + # TSAN is deactivated due to seg faults with latest compilers. + activateTSAN = False + if activateTSAN: configurations.append( { "config_name": config_name + "-tsan-ubsan", - "cmake_args": configs["tsan_ubsan"], + "cmake_args": cmake_args, "cmake_target": cmake_target, "build_only": build_only, "build_type": build_type, @@ -290,102 +289,6 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: return configurations -def addSanitizerConfigs( - architecture: dict, - os: dict, - cmake_args: str, - cxx_flags: str, -) -> dict: - extra_warning_flags = "" - linker_relocation_flags = "" - linker_flags = "" - - cxx_flags += " -fno-omit-frame-pointer" - - # Use large code model to avoid relocation errors with large binaries - # Only for x86-64 (amd64) - ARM64 doesn't support -mcmodel=large - if architecture["platform"] == "linux/amd64" and os["compiler_name"] == "gcc": - # Add -mcmodel=large to both compiler AND linker flags - # This is needed because sanitizers create very large binaries and - # large model removes the 2GB limitation that medium model has - cxx_flags += " -mcmodel=large" - linker_relocation_flags += " -mcmodel=large" - - # Create default sanitizer flags - sanitizers_flags = "undefined,float-divide-by-zero" - - # There are some differences between GCC and Clang support for sanitizers. - # Hence we must use diff. falg combinations for each compiler. - # These combination of flags were tested to work with GCC 15 and Clang 20. - # If the versions are changed, the flags might need to be updated. - - if os["compiler_name"] == "gcc": - # Suppress false positive warnings in GCC with stringop-overflow - extra_warning_flags += " -Wno-stringop-overflow" - # Disable mold, gold and lld linkers. - # Use default linker (bfd/ld) which is more lenient with mixed code models - cmake_args += " -Duse_mold=OFF -Duse_gold=OFF -Duse_lld=OFF" - # Add linker flags for Sanitizers - linker_flags += f" -DCMAKE_EXE_LINKER_FLAGS='{linker_relocation_flags} -fsanitize=address,{sanitizers_flags}'" - linker_flags += f" -DCMAKE_SHARED_LINKER_FLAGS='{linker_relocation_flags} -fsanitize=address,{sanitizers_flags}'" - elif os["compiler_name"] == "clang": - # Note: We use $GITHUB_WORKSPACE environment variable which will be expanded by the shell - # before CMake processes it. This ensures the compiler receives an absolute path. - # CMAKE_SOURCE_DIR won't work here because it's inside CMAKE_CXX_FLAGS string. - # GCC doesn't support ignorelist. - cxx_flags += " -fsanitize-ignorelist=${GITHUB_WORKSPACE}/sanitizers/suppressions/sanitizer-ignorelist.txt" - sanitizers_flags = f"{sanitizers_flags},unsigned-integer-overflow" - linker_flags += ( - f" -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address,{sanitizers_flags}'" - ) - linker_flags += ( - f" -DCMAKE_SHARED_LINKER_FLAGS='-fsanitize=address,{sanitizers_flags}'" - ) - - # Sanitizers recommend minimum of -O1 for reasonable performance - cxx_flags += " -O1" - - # First create config for asan - cmake_args_flags = f'{cmake_args} -DCMAKE_CXX_FLAGS="-fsanitize=address,{sanitizers_flags} {cxx_flags} {extra_warning_flags}" {linker_flags}' - - # Add config with asan+ubsan - configs = {} - configs["asan_ubsan"] = cmake_args_flags - - # Since TSAN runs are crashing with seg faults(could be compatibility issues with latest compilers) - # We deactivate it for now. But I would keep the code, since it took some effort to find the correct set of config needed to run this. - # This will be useful when we decide to activate it again in future. - activateTSAN = False - if activateTSAN: - linker_flags = "" - # Update configs for tsan - # gcc doesn't supports atomic_thread_fence with tsan. Suppress warnings. - # Also tsan doesn't work well with mcmode=large and bfd linker - if os["compiler_name"] == "gcc": - extra_warning_flags += " -Wno-tsan" - cxx_flags = cxx_flags.replace("-mcmodel=large", "-mcmodel=medium") - linker_relocation_flags = linker_relocation_flags.replace( - "-mcmodel=large", "-mcmodel=medium" - ) - # Add linker flags for Sanitizers - linker_flags += f" -DCMAKE_EXE_LINKER_FLAGS='{linker_relocation_flags} -fsanitize=thread,{sanitizers_flags}'" - linker_flags += f" -DCMAKE_SHARED_LINKER_FLAGS='{linker_relocation_flags} -fsanitize=thread,{sanitizers_flags}'" - elif os["compiler_name"] == "clang": - linker_flags += ( - f" -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=thread,{sanitizers_flags}'" - ) - linker_flags += ( - f" -DCMAKE_SHARED_LINKER_FLAGS='-fsanitize=thread,{sanitizers_flags}'" - ) - - cmake_args_flags = f"{cmake_args} -DCMAKE_CXX_FLAGS='-fsanitize=thread,{sanitizers_flags} {cxx_flags} {extra_warning_flags}' {linker_flags}" - - # Add config with tsan+ubsan - configs["tsan_ubsan"] = cmake_args_flags - - return configs - - def read_config(file: Path) -> Config: config = json.loads(file.read_text()) if ( diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 15f1ebad6f..2edffdae96 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -51,7 +51,7 @@ on: default: 2 sanitizers: - description: "The sanitizers to enable ('Address+UndefinedBehavior' or 'Thread+UndefinedBehavior')." + description: "The sanitizers to enable ('Address,UndefinedBehavior' or 'Thread,UndefinedBehavior')." required: false type: string default: "" @@ -74,7 +74,7 @@ jobs: env: ENABLED_VOIDSTAR: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }} ENABLED_COVERAGE: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }} - ENABLED_SANITIZERS: ${{ contains(inputs.cmake_args, '-fsanitize') }} + ENABLED_SANITIZERS: ${{ inputs.sanitizers != '' }} steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} @@ -115,6 +115,7 @@ jobs: working-directory: ${{ inputs.build_dir }} env: BUILD_TYPE: ${{ inputs.build_type }} + SANITIZER: ${{ inputs.sanitizers }} run: | cmake \ -G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \ diff --git a/CMakeLists.txt b/CMakeLists.txt index ade9c2f995..697c1bf311 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ if (packages_only) return () endif () include(XrplCompiler) +include(XrplSanitizers) include(XrplInterface) option(only_docs "Include only the docs target?" FALSE) diff --git a/cmake/AbseilFix.cmake b/cmake/AbseilFix.cmake deleted file mode 100644 index 89190f5a0e..0000000000 --- a/cmake/AbseilFix.cmake +++ /dev/null @@ -1,5 +0,0 @@ -# This file is included by Conan's toolchain file. -# We set the required CMake variable as a CACHE variable to ensure -# it takes effect during the configure step. - -set(ABSL_ENABLE_CONSTANT_INIT_V2 "OFF" CACHE BOOL "Disable Abseil's V2 constant init logic to fix compiler errors.") diff --git a/cmake/XrplSanitizers.cmake b/cmake/XrplSanitizers.cmake new file mode 100644 index 0000000000..995a23b5cc --- /dev/null +++ b/cmake/XrplSanitizers.cmake @@ -0,0 +1,158 @@ +#[===================================================================[ + Configure sanitizers based on environment variables. + + This module reads the following environment variables: + - SANITIZER: The sanitizers to enable. Possible values: + - "Address" + - "Address,UndefinedBehavior" + - "Thread" + - "Thread,UndefinedBehavior" + + The compiler type and platform are detected automatically by CMake. + The sanitizer compile options are applied to the 'common' interface library + which is linked to all targets in the project. +#]===================================================================] + +# Read environment variable +set(SANITIZER $ENV{SANITIZER}) + +if(SANITIZER) + message(STATUS "Configuring sanitizers: ${SANITIZER}") + + # Parse SANITIZER value to determine which sanitizers to enable + set(ENABLE_ASAN FALSE) + set(ENABLE_TSAN FALSE) + set(ENABLE_UBSAN FALSE) + + if(SANITIZER MATCHES "Address") + set(ENABLE_ASAN TRUE) + endif() + if(SANITIZER MATCHES "Thread") + set(ENABLE_TSAN TRUE) + endif() + if(SANITIZER MATCHES "UndefinedBehavior") + set(ENABLE_UBSAN TRUE) + endif() + + # Detect compiler type + set(IS_GCC FALSE) + set(IS_CLANG FALSE) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(IS_GCC TRUE) + message(STATUS " Compiler: GCC ${CMAKE_CXX_COMPILER_VERSION}") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(IS_CLANG TRUE) + message(STATUS " Compiler: Clang ${CMAKE_CXX_COMPILER_VERSION}") + endif() + + # Detect platform (amd64/x86_64 vs arm64/aarch64) + set(IS_AMD64 FALSE) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") + set(IS_AMD64 TRUE) + message(STATUS " Platform: amd64") + else() + message(STATUS " Platform: ${CMAKE_SYSTEM_PROCESSOR}") + endif() + + # Frame pointer is required for meaningful stack traces + set(SANITIZER_COMPILE_FLAGS "-fno-omit-frame-pointer") + + # Sanitizers recommend minimum of -O1 for reasonable performance + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -O1") + + # Build the sanitizer flags string + set(SANITIZER_FLAGS "") + + if(ENABLE_ASAN) + set(SANITIZER_FLAGS "address") + elseif(ENABLE_TSAN) + set(SANITIZER_FLAGS "thread") + endif() + + if(ENABLE_UBSAN) + # UB sanitizer flags + if(IS_CLANG) + # Clang supports additional UB checks + set(UBSAN_FLAGS "undefined,float-divide-by-zero,unsigned-integer-overflow") + else() + set(UBSAN_FLAGS "undefined,float-divide-by-zero") + endif() + + if(SANITIZER_FLAGS) + set(SANITIZER_FLAGS "${SANITIZER_FLAGS},${UBSAN_FLAGS}") + else() + set(SANITIZER_FLAGS "${UBSAN_FLAGS}") + endif() + endif() + + # Configure code model for GCC on amd64 + # Use large code model for ASAN to avoid relocation errors + # Use medium code model for TSAN (large is not compatible with TSAN) + set(SANITIZER_RELOCATION_FLAGS "") + if(IS_GCC AND IS_AMD64) + if(ENABLE_ASAN) + message(STATUS " Using large code model (-mcmodel=large)") + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -mcmodel=large") + set(SANITIZER_RELOCATION_FLAGS "-mcmodel=large") + elseif(ENABLE_TSAN) + message(STATUS " Using medium code model (-mcmodel=medium)") + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -mcmodel=medium") + set(SANITIZER_RELOCATION_FLAGS "-mcmodel=medium") + endif() + endif() + + # Compiler-specific configuration + if(IS_GCC) + # Disable mold, gold and lld linkers for GCC with sanitizers + # Use default linker (bfd/ld) which is more lenient with mixed code models + set(use_mold OFF CACHE BOOL "Use mold linker" FORCE) + set(use_gold OFF CACHE BOOL "Use gold linker" FORCE) + set(use_lld OFF CACHE BOOL "Use lld linker" FORCE) + message(STATUS " Disabled mold, gold, and lld linkers for GCC with sanitizers") + + # Suppress false positive warnings in GCC with stringop-overflow + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -Wno-stringop-overflow") + + if(ENABLE_TSAN) + # GCC doesn't support atomic_thread_fence with tsan. Suppress warnings. + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -Wno-tsan") + endif() + + # Add sanitizer to compile and link flags + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -fsanitize=${SANITIZER_FLAGS}") + set(SANITIZER_LINK_FLAGS "${SANITIZER_RELOCATION_FLAGS} -fsanitize=${SANITIZER_FLAGS}") + + elseif(IS_CLANG) + # Add ignorelist for Clang (GCC doesn't support this) + # Use CMAKE_SOURCE_DIR to get the path to the ignorelist + set(IGNORELIST_PATH "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt") + if(EXISTS "${IGNORELIST_PATH}") + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -fsanitize-ignorelist=${IGNORELIST_PATH}") + message(STATUS " Using sanitizer ignorelist: ${IGNORELIST_PATH}") + else() + message(WARNING "Sanitizer ignorelist not found: ${IGNORELIST_PATH}") + endif() + + # Add sanitizer to compile and link flags + set(SANITIZER_COMPILE_FLAGS "${SANITIZER_COMPILE_FLAGS} -fsanitize=${SANITIZER_FLAGS}") + set(SANITIZER_LINK_FLAGS "-fsanitize=${SANITIZER_FLAGS}") + endif() + + message(STATUS " Compile flags: ${SANITIZER_COMPILE_FLAGS}") + message(STATUS " Link flags: ${SANITIZER_LINK_FLAGS}") + + # Convert space-separated strings to lists for CMake + separate_arguments(SANITIZER_COMPILE_FLAGS_LIST NATIVE_COMMAND "${SANITIZER_COMPILE_FLAGS}") + separate_arguments(SANITIZER_LINK_FLAGS_LIST NATIVE_COMMAND "${SANITIZER_LINK_FLAGS}") + + # Apply the sanitizer flags to the 'common' interface library + # This is the same library used by XrplCompiler.cmake + target_compile_options(common INTERFACE + $<$:${SANITIZER_COMPILE_FLAGS_LIST}> + $<$:${SANITIZER_COMPILE_FLAGS_LIST}> + ) + + # Apply linker flags + target_link_options(common INTERFACE ${SANITIZER_LINK_FLAGS_LIST}) + +endif() diff --git a/cmake/XrplSettings.cmake b/cmake/XrplSettings.cmake index a16513afc5..2550e5a4e1 100644 --- a/cmake/XrplSettings.cmake +++ b/cmake/XrplSettings.cmake @@ -92,33 +92,6 @@ option(local_protobuf option(local_grpc "Force a local build of gRPC instead of looking for an installed version." OFF) -# this one is a string and therefore can't be an option -set(san "" CACHE STRING "On gcc & clang, add sanitizer instrumentation") -set_property(CACHE san PROPERTY STRINGS ";undefined;memory;address;thread") -if(san) - string(TOLOWER ${san} san) - set(SAN_FLAG "-fsanitize=${san}") - set(SAN_LIB "") - if(is_gcc) - if(san STREQUAL "address") - set(SAN_LIB "asan") - elseif(san STREQUAL "thread") - set(SAN_LIB "tsan") - elseif(san STREQUAL "memory") - set(SAN_LIB "msan") - elseif(san STREQUAL "undefined") - set(SAN_LIB "ubsan") - endif() - endif() - set(_saved_CRL ${CMAKE_REQUIRED_LIBRARIES}) - set(CMAKE_REQUIRED_LIBRARIES "${SAN_FLAG};${SAN_LIB}") - check_cxx_compiler_flag(${SAN_FLAG} COMPILER_SUPPORTS_SAN) - set(CMAKE_REQUIRED_LIBRARIES ${_saved_CRL}) - if(NOT COMPILER_SUPPORTS_SAN) - message(FATAL_ERROR "${san} sanitizer does not seem to be supported by your compiler") - endif() -endif() - # the remaining options are obscure and rarely used option(beast_no_unit_test_inline "Prevents unit test definitions from being inserted into global table"