diff --git a/cmake/modules/FindASan.cmake b/cmake/modules/FindASan.cmake new file mode 100644 index 00000000..3444f491 --- /dev/null +++ b/cmake/modules/FindASan.cmake @@ -0,0 +1,37 @@ +include_guard(GLOBAL) + +option(MORPHEUS_ADDRESS_SANITIZER "Enable AddressSanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_ADDRESS_SANITIZER) + return() +endif() + +if (MORPHEUS_ADDRESS_SANITIZER AND (MORPHEUS_MEMORY_SANITIZER OR MORPHEUS_THREAD_SANITIZER)) + message(FATAL_ERROR "morpheus: AddressSanitizer is not compatible with ThreadSanitizer or MemorySanitizer.") +endif () + +# For gcc and clang first attempt to sanitize address with full frame pointer information. +list(APPEND asanCandidateFlag "-g -fsanitize=address -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer") +list(APPEND asanCandidateFlag "-g -fsanitize=address -fno-omit-frame-pointer") +list(APPEND asanCandidateFlag "-g -fsanitize=address") +list(APPEND asanCandidateFlag "/fsanitize=address") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Address Sanitizer" + PREFIX ASan + FLAGS ${asanCandidateFlag} + RESULT asanSupported +) + +find_package_handle_standard_args(ASan REQUIRED_VARS asanSupported) + +if (ASan_FOUND AND NOT TARGET ASan::ASan) + add_library(ASan INTERFACE) + add_library(morpheus::ASan ALIAS ASan) + target_compile_options(ASan + INTERFACE + + ) +endif() + diff --git a/cmake/modules/FindMSan.cmake b/cmake/modules/FindMSan.cmake new file mode 100644 index 00000000..ffe1ffb9 --- /dev/null +++ b/cmake/modules/FindMSan.cmake @@ -0,0 +1,29 @@ +include_guard(GLOBAL) + +option(MORPHEUS_MEMORY_SANITIZER "Enable MemorySanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_MEMORY_SANITIZER) + return() +endif() + +list(APPEND msanCandidateFlag "-g -fsanitize=memory") # Gcc & Clang syntax +list(APPEND msanCandidateFlag "/fsanitize=memory") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Thread Sanitizer" + PREFIX MSan + FLAGS ${msanCandidateFlag} + RESULT msanSupported +) + +find_package_handle_standard_args(TSan REQUIRED_VARS msanSupported) + +if (MSan_FOUND AND NOT TARGET morpheus::MSan) + add_library(MSan INTERFACE) + add_library(morpheus::MSan ALIAS MSan) + target_compile_options(MSan + INTERFACE + #msanCandidateFlag + ) +endif() \ No newline at end of file diff --git a/cmake/modules/FindSanitizers.cmake b/cmake/modules/FindSanitizers.cmake new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/cmake/modules/FindSanitizers.cmake @@ -0,0 +1 @@ + diff --git a/cmake/modules/FindTSan.cmake b/cmake/modules/FindTSan.cmake new file mode 100644 index 00000000..0e060707 --- /dev/null +++ b/cmake/modules/FindTSan.cmake @@ -0,0 +1,29 @@ +include_guard(GLOBAL) + +option(MORPHEUS_THREAD_SANITIZER "Enable ThreadSanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_THREAD_SANITIZER) + return() +endif() + +list(APPEND tsanCandidateFlag "-g -fsanitize=thread") # Gcc & Clang syntax +list(APPEND tsanCandidateFlag "/fsanitize=thread") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Thread Sanitizer" + PREFIX TSan + FLAGS ${tsanCandidateFlag} + RESULT tsanSupported +) + +find_package_handle_standard_args(TSan REQUIRED_VARS tsanSupported) + +if (TSan_FOUND AND NOT TARGET morpheus::TSan) + add_library(TSan INTERFACE) + add_library(morpheus::TSan ALIAS TSan) + target_compile_options(TSan + INTERFACE + #tsanCandidateFlag + ) +endif() diff --git a/cmake/modules/FindUBSan.cmake b/cmake/modules/FindUBSan.cmake new file mode 100644 index 00000000..e919cae2 --- /dev/null +++ b/cmake/modules/FindUBSan.cmake @@ -0,0 +1,29 @@ +include_guard(GLOBAL) + +option(MORPHEUS_UNDEFINED_BEHAVIOUR_SANITIZER "Enable UndefineBehaviourSanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_UNDEFINED_BEHAVIOUR_SANITIZER) + return() +endif() + +list(APPEND ubsanCandidateFlag "-g -fsanitize=undefined") # Gcc & Clang syntax +list(APPEND ubsanCandidateFlag "/fsanitize=undefined") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Undefined behavior Sanitizer" + PREFIX UBSan + FLAGS ${ubsanCandidateFlag} + RESULT ubsanSupported +) + +find_package_handle_standard_args(UBSan REQUIRED_VARS ubsanSupported) + +if (UBSan_FOUND AND NOT TARGET morpheus::UBSan) + add_library(UBSan INTERFACE) + add_library(morpheus::UBSan ALIAS UBSan) + target_compile_options(UBSan + INTERFACE + #ubsanCandidateFlag + ) +endif() diff --git a/cmake/morpheus_check_compiler_flags.cmake b/cmake/morpheus_check_compiler_flags.cmake new file mode 100644 index 00000000..1962f369 --- /dev/null +++ b/cmake/morpheus_check_compiler_flags.cmake @@ -0,0 +1,151 @@ +include_guard(GLOBAL) + +cmake_minimum_required(VERSION 3.25) + +#[=======================================================================[.rst: +morpheus_check_compiler_flag +------------------ + +Overview +^^^^^^^^ + +Checks for the given compiler if the given flag is supported for the specified languages +if CMake provides a compiler check module for the underlying language. + +.. code-block:: cmake + + morpheus_check_compiler_flag( + [FLAG ] + [LANGUAGE ] + [RESULT ] + ) + -- Confirms support for the compile flag across enabled languages. + + ``FLAG`` + The ``FLAG`` compiler flag to test if is supported for the specified language. + + ``RESULT`` + The ``RESULT`` option is required to store the result of the function. TRUE if + there is compiler support for the feature. + +#]=======================================================================] +function(morpheus_check_compiler_flag) + set(options) + set(oneValueArgs FLAG LANGUAGE RESULT) + set(multiValueArgs) + cmake_parse_arguments(MORPHEUS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT MORPHEUS_FLAG) + message(FATAL_ERROR "FLAG parameter must be supplied") + endif() + if (NOT MORPHEUS_LANGUAGE) + message(FATAL_ERROR "LANGUAGE parameter must be supplied") + endif() + if (NOT MORPHEUS_RESULT) + message(FATAL_ERROR "RESULT parameter must be supplied") + endif() + + if (${MORPHEUS_LANGUAGE} STREQUAL "C") + include(CheckCCompilerFlag) + check_c_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "CXX") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "Fortran") + include(CheckFortranCompilerFlag) + check_fortran_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "OBJC") + include(CheckOBJCCompilerFlag) + check_objc_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "OBJCXX") + include(CheckOBJCXXCompilerFlag) + check_objcxx_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + else() + set(${MORPHEUS_RESULT} FALSE) + return(PROPAGATE ${MORPHEUS_RESULT}) + endif() +endfunction() + + +#[=======================================================================[.rst: +morpheus_check_compiler_flags +------------------ + +Overview +^^^^^^^^ + +Checks for the given compile supported of the given flags across all languages in +use which have compiler check modules provides in CMake. + +.. code-block:: cmake + + morpheus_check_compiler_flags( + [QUIET] + [FLAGS ] + [RESULT ] + ) + -- Confirms support for compiler flags across enabled languages. + + ``RESULT`` + The ``RESULT`` option is required to store the results of the function. TRUE if + there is compiler support for the feature. + + ``FLAGS`` + The ``FLAGS`` compiler flags to test if are supported for the enabled languages. + +#]=======================================================================] +function(morpheus_check_compiler_flags) + set(options QUIET) + set(oneValueArgs NAME PREFIX RESULT) + set(multiValueArgs FLAGS) + cmake_parse_arguments(MORPHEUS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (NOT MORPHEUS_NAME) + message(FATAL_ERROR "NAME parameter must be supplied") + endif() + if (NOT MORPHEUS_PREFIX) + message(FATAL_ERROR "PREFIX parameter must be supplied") + endif() + if (NOT MORPHEUS_RESULT) + message(FATAL_ERROR "RESULT parameter must be supplied") + endif() + if (NOT MORPHEUS_FLAGS) + message(FATAL_ERROR "FLAGS parameter must be supplied") + endif() + + get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) + + foreach(language IN LISTS languages) + + set(COMPILER ${CMAKE_${language}_COMPILER_ID}) + if (${MORPHEUS_PREFIX}_${COMPILER}_FLAGS) + continue() + endif() + + foreach(flags IN LISTS MORPHEUS_FLAGS) + if (NOT MORPHEUS_QUIET) + message(STATUS "Try ${COMPILER} ${MORPHEUS_NAME} flag = [${flags}]") + endif() + + set(CMAKE_REQUIRED_FLAGS "${flags}") + unset(flagsSupported CACHE) + morpheus_check_compiler_flag(FLAG "${flags}" LANGUAGE ${language} RESULT flagsSupported) + + if (flagsSupported) + set(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS "${flags}" CACHE STRING "${MORPHEUS_NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS) + break() + endif() + endforeach() + + if (NOT ${flagsSupported}) + set(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS "" CACHE STRING "${MORPHEUS_NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS) + message(WARNING "${MORPHEUS_NAME} is not available for ${COMPILER} compiler. Targets using " + "this compiler will be compiled without ${MORPHEUS_NAME}.") + endif() + endforeach() + + set(${MORPHEUS_RESULT} FALSE) + return(PROPAGATE ${MORPHEUS_RESULT}) + +endfunction() diff --git a/cmake/sanitisers.cmake b/cmake/sanitisers.cmake index 7d3e0696..35e3f896 100644 --- a/cmake/sanitisers.cmake +++ b/cmake/sanitisers.cmake @@ -19,6 +19,7 @@ include_guard(GLOBAL) include(targets) +#[[ option(ENABLE_ADDRESS_SANITIZER "Enable AddressSanitizer for sanitized targets." OFF) option(ENABLE_MEMORY_SANITIZER "Enable MemorySanitizer for sanitized targets." OFF) option(ENABLE_THREAD_SANITIZER "Enable ThreadSanitizer for sanitized targets." OFF) @@ -27,7 +28,8 @@ option(ENABLE_UNDEFINED_BEHAVIOUR_SANITIZER "Enable UndefineBehaviourSanitizer f if(NOT (ENABLE_ADDRESS_SANITIZER OR ENABLE_MEMORY_SANITIZER OR ENABLE_THREAD_SANITIZER OR ENABLE_UNDEFINED_BEHAVIOUR_SANITIZER)) return() endif() - +]] +#[[ FetchContent_Declare( sanitizers GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake @@ -37,13 +39,16 @@ if(NOT sanitizers_POPULATED) FetchContent_Populate(sanitizers) list(APPEND CMAKE_MODULE_PATH ${sanitizers_SOURCE_DIR}/cmake) endif() +]] set(SANITIZE_ADDRESS ${ENABLE_ADDRESS_SANITIZER} CACHE BOOL "Enable AddressSanitizer for sanitized targets." FORCE) set(SANITIZE_MEMORY ${ENABLE_MEMORY_SANITIZER} CACHE BOOL "Enable MemorySanitizer for sanitized targets." FORCE) set(SANITIZE_THREAD ${ENABLE_THREAD_SANITIZER} CACHE BOOL "Enable ThreadSanitizer for sanitized targets." FORCE) set(SANITIZE_UNDEFINED ${ENABLE_UNDEFINED_BEHAVIOUR_SANITIZER} CACHE BOOL "Enable UndefinedBehaviorSanitizer for sanitized targets." FORCE) -find_package(Sanitizers) +#find_package(Sanitizers) + +find_package(ASan) targets_get_all(RESULT allTargets DIRECTORY ${PROJECT_SOURCE_DIR}) targets_filter_for_sources(RESULT targetsWithSource TARGETS ${allTargets}) -add_sanitizers("${targetsWithSource}") +#add_sanitizers("${targetsWithSource}") diff --git a/libraries/core/src/morpheus/core/base/sanitizers.hpp b/libraries/core/src/morpheus/core/base/sanitizers.hpp index 3a76558b..539a11dd 100644 --- a/libraries/core/src/morpheus/core/base/sanitizers.hpp +++ b/libraries/core/src/morpheus/core/base/sanitizers.hpp @@ -47,4 +47,4 @@ namespace mopheus #define MORPHEUS_NO_SANITIZE_ADDRESS #endif -} // namespace mopheus +} // namespace morpheus diff --git a/libraries/core/tests/cmake/CMakeLists.txt b/libraries/core/tests/cmake/CMakeLists.txt new file mode 100644 index 00000000..c8ab07d5 --- /dev/null +++ b/libraries/core/tests/cmake/CMakeLists.txt @@ -0,0 +1,10 @@ +morpheus_add_tests( + NAME MorpheusAsanTests + FOLDER "Libraries/Core" +) + +find_package(ASan REQUIRED) +target_sources(MorpheusAsanTests + PUBLIC + asan.tests.cpp +) diff --git a/libraries/core/tests/cmake/asan.tests.cpp b/libraries/core/tests/cmake/asan.tests.cpp new file mode 100644 index 00000000..9cb959f6 --- /dev/null +++ b/libraries/core/tests/cmake/asan.tests.cpp @@ -0,0 +1,18 @@ +#include + +#include +#include + +namespace morpheus +{ + +TEST_CASE("Ensure the address sanitizer detects unsafe use after free memory usage", "[quantbox.cmake.asan]") +{ + auto storage = std::make_unique(1); + std::reference_wrapper dangling(*storage); + storage.release(); + REQUIRE(!storage); + dangling.get() = 0; // If asan works this is an error! +} + +} // morpheus