Skip to content

Commit

Permalink
Merge pull request #278 from aminya/sanitizer [skip ci]
Browse files Browse the repository at this point in the history
  • Loading branch information
aminya authored Sep 11, 2024
2 parents f9ba484 + 4a105c5 commit 7f4c940
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 118 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,8 @@ if(FEATURE_TESTS)
set(ENABLE_CPPCHECK "ENABLE_CPPCHECK")
set(ENABLE_COVERAGE "ENABLE_COVERAGE")
check_sanitizers_support(ENABLE_SANITIZER_ADDRESS
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
ENABLE_SANITIZER_LEAK
ENABLE_SANITIZER_THREAD
ENABLE_SANITIZER_MEMORY)
set(ENABLE_SANITIZER_ADDRESS "ENABLE_SANITIZER_ADDRESS")
set(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "ENABLE_SANITIZER_UNDEFINED_BEHAVIOR")
endif()
# Enable doxgen for the docs
Expand All @@ -129,6 +126,8 @@ project_options(
${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
# ${ENABLE_SANITIZER_THREAD}
# ${ENABLE_SANITIZER_MEMORY}
# ENABLE_SANITIZER_POINTER_COMPARE
# ENABLE_SANITIZER_POINTER_SUBTRACT
# ENABLE_CONTROL_FLOW_PROTECTION
# ENABLE_STACK_PROTECTION
# ENABLE_OVERFLOW_PROTECTION
Expand Down
9 changes: 4 additions & 5 deletions docs/src/project_options_example.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,8 @@ if(FEATURE_TESTS)
set(ENABLE_CPPCHECK "ENABLE_CPPCHECK")
set(ENABLE_COVERAGE "ENABLE_COVERAGE")
check_sanitizers_support(ENABLE_SANITIZER_ADDRESS
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
ENABLE_SANITIZER_LEAK
ENABLE_SANITIZER_THREAD
ENABLE_SANITIZER_MEMORY)
set(ENABLE_SANITIZER_ADDRESS "ENABLE_SANITIZER_ADDRESS")
set(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "ENABLE_SANITIZER_UNDEFINED_BEHAVIOR")
endif()
# Enable doxgen for the docs
Expand All @@ -83,6 +80,8 @@ project_options(
${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
# ${ENABLE_SANITIZER_THREAD}
# ${ENABLE_SANITIZER_MEMORY}
# ENABLE_SANITIZER_POINTER_COMPARE
# ENABLE_SANITIZER_POINTER_SUBTRACT
# ENABLE_CONTROL_FLOW_PROTECTION
# ENABLE_STACK_PROTECTION
# ENABLE_OVERFLOW_PROTECTION
Expand Down
9 changes: 7 additions & 2 deletions src/DynamicProjectOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,13 @@ macro(dynamic_project_options)
endif()

check_sanitizers_support(
ENABLE_SANITIZER_ADDRESS ENABLE_SANITIZER_UNDEFINED_BEHAVIOR ENABLE_SANITIZER_LEAK
ENABLE_SANITIZER_THREAD ENABLE_SANITIZER_MEMORY
ENABLE_SANITIZER_ADDRESS
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
ENABLE_SANITIZER_LEAK
ENABLE_SANITIZER_THREAD
ENABLE_SANITIZER_MEMORY
ENABLE_SANITIZER_POINTER_COMPARE
ENABLE_SANITIZER_POINTER_SUBTRACT
)

if(ENABLE_SANITIZER_ADDRESS)
Expand Down
4 changes: 4 additions & 0 deletions src/Index.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ macro(project_options)
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
ENABLE_SANITIZER_THREAD
ENABLE_SANITIZER_MEMORY
ENABLE_SANITIZER_POINTER_COMPARE
ENABLE_SANITIZER_POINTER_SUBTRACT
ENABLE_CONTROL_FLOW_PROTECTION
ENABLE_STACK_PROTECTION
ENABLE_OVERFLOW_PROTECTION
Expand Down Expand Up @@ -266,6 +268,8 @@ macro(project_options)
${ProjectOptions_ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
${ProjectOptions_ENABLE_SANITIZER_THREAD}
${ProjectOptions_ENABLE_SANITIZER_MEMORY}
${ProjectOptions_ENABLE_SANITIZER_POINTER_COMPARE}
${ProjectOptions_ENABLE_SANITIZER_POINTER_SUBTRACT}
)

enable_hardening(
Expand Down
236 changes: 143 additions & 93 deletions src/Sanitizers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -11,89 +11,112 @@ function(
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
ENABLE_SANITIZER_THREAD
ENABLE_SANITIZER_MEMORY
ENABLE_SANITIZER_POINTER_COMPARE
ENABLE_SANITIZER_POINTER_SUBTRACT
)

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
set(SANITIZERS "")

if(${ENABLE_SANITIZER_ADDRESS})
list(APPEND SANITIZERS "address")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8)
list(APPEND SANITIZERS "pointer-compare" "pointer-subtract")
message(
STATUS
"To enable invalid pointer pairs detection, add detect_invalid_pointer_pairs=2 to the environment variable ASAN_OPTIONS."
)
# check if the sanitizers are supported
check_sanitizers_support(
SUPPORTS_SANITIZER_ADDRESS
SUPPORTS_SANITIZER_UNDEFINED_BEHAVIOR
SUPPORTS_SANITIZER_LEAK
SUPPORTS_SANITIZER_THREAD
SUPPORTS_SANITIZER_MEMORY
SUPPORTS_SANITIZER_POINTER_COMPARE
SUPPORTS_SANITIZER_POINTER_SUBTRACT
)

# for each sanitizer, check if it is supported and enabled
set(SANITIZERS "")
foreach(
SANITIZER IN
ITEMS "address"
"leak"
"undefined"
"thread"
"memory"
"pointer-compare"
"pointer-subtract"
)
if(${ENABLE_SANITIZER_${SANITIZER}})
if(${SUPPORTS_SANITIZER_${SANITIZER}})
list(APPEND SANITIZERS ${SANITIZER})
else()
# do not enable the sanitizer if it is not supported
message(STATUS "${SANITIZER} sanitizer is not supported. Not enabling it.")
endif()
endif()
endforeach()

if(${ENABLE_SANITIZER_LEAK})
list(APPEND SANITIZERS "leak")
endif()
# Info on special cases

if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR})
list(APPEND SANITIZERS "undefined")
endif()

if(${ENABLE_SANITIZER_THREAD})
if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled")
else()
list(APPEND SANITIZERS "thread")
endif()
# Address sanitizer requires Leak sanitizer to be disabled
if(${ENABLE_SANITIZER_THREAD} AND "${SUPPORTS_SANITIZER_THREAD}" STREQUAL "ENABLE_SANITIZER_THREAD")
if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
message(
WARNING
"Thread sanitizer does not work with Address or Leak sanitizer enabled. Disabling the thread sanitizer."
)
# remove thread sanitizer from the list
list(REMOVE_ITEM SANITIZERS "thread")
endif()
endif()

if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
# Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives
if(${ENABLE_SANITIZER_MEMORY} AND "${SUPPORTS_SANITIZER_MEMORY}" STREQUAL "ENABLE_SANITIZER_MEMORY"
AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang"
)
message(
STATUS
"Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives"
)
if("address" IN_LIST SANITIZERS OR "thread" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
message(
WARNING
"Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives"
"Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled. Disabling the memory sanitizer."
)
if("address" IN_LIST SANITIZERS OR "thread" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
message(WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled")
else()
list(APPEND SANITIZERS "memory")
endif()
endif()
elseif(MSVC)
if(${ENABLE_SANITIZER_ADDRESS})
list(APPEND SANITIZERS "address")
# remove memory sanitizer from the list
list(REMOVE_ITEM SANITIZERS "memory")
endif()
if(${ENABLE_SANITIZER_LEAK}
OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
OR ${ENABLE_SANITIZER_THREAD}
OR ${ENABLE_SANITIZER_MEMORY}
endif()

if((${ENABLE_SANITIZER_POINTER_COMPARE} AND "${SUPPORTS_SANITIZER_POINTER_COMPARE}" STREQUAL
"ENABLE_SANITIZER_POINTER_COMPARE")
OR (${ENABLE_SANITIZER_POINTER_SUBTRACT} AND "${SUPPORTS_SANITIZER_POINTER_SUBTRACT}" STREQUAL
"ENABLE_SANITIZER_POINTER_SUBTRACT")
)
message(
STATUS
"To enable invalid pointer pairs detection, add detect_invalid_pointer_pairs=2 to the environment variable ASAN_OPTIONS."
)
message(WARNING "MSVC only supports address sanitizer")
endif()
endif()

# Join the sanitizers
list(JOIN SANITIZERS "," LIST_OF_SANITIZERS)

if(LIST_OF_SANITIZERS)
if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "")
if(NOT MSVC)
target_compile_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
target_link_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
else()
string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir)
if("${index_of_vs_install_dir}" STREQUAL "-1")
message(
SEND_ERROR
"Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project."
)
endif()
if(POLICY CMP0141)
if("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" OR "${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}"
STREQUAL "EditAndContinue"
)
set_target_properties(${_project_name} PROPERTIES MSVC_DEBUG_INFORMATION_FORMAT ProgramDatabase)
endif()
else()
target_compile_options(${_project_name} INTERFACE /Zi)
if(LIST_OF_SANITIZERS AND NOT "${LIST_OF_SANITIZERS}" STREQUAL "")
if(NOT MSVC)
target_compile_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
target_link_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
else()
string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir)
if("${index_of_vs_install_dir}" STREQUAL "-1")
message(
SEND_ERROR
"Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project."
)
endif()
if(POLICY CMP0141)
if("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" OR "${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}"
STREQUAL "EditAndContinue"
)
set_target_properties(${_project_name} PROPERTIES MSVC_DEBUG_INFORMATION_FORMAT ProgramDatabase)
endif()
target_compile_options(${_project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /INCREMENTAL:NO)
target_link_options(${_project_name} INTERFACE /INCREMENTAL:NO)
else()
target_compile_options(${_project_name} INTERFACE /Zi)
endif()
target_compile_options(${_project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /INCREMENTAL:NO)
target_link_options(${_project_name} INTERFACE /INCREMENTAL:NO)
endif()
endif()

Expand All @@ -104,7 +127,7 @@ endfunction()
``check_sanitizers_support``
===============

Detect sanitizers support for compiler.
Detect sanitizers support for compiler. You don't need to call this function directly anymore.

Note that some sanitizers cannot be enabled together, and this function doesn't check that. You should decide which sanitizers to enable based on your needs.

Expand All @@ -115,6 +138,8 @@ Output variables:
- ``ENABLE_SANITIZER_LEAK``: Leak sanitizer is supported
- ``ENABLE_SANITIZER_THREAD``: Thread sanitizer is supported
- ``ENABLE_SANITIZER_MEMORY``: Memory sanitizer is supported
- ``ENABLE_SANITIZER_POINTER_COMPARE``: Pointer compare sanitizer is supported
- ``ENABLE_SANITIZER_POINTER_SUBTRACT``: Pointer subtract sanitizer is supported


.. code:: cmake
Expand All @@ -123,7 +148,9 @@ Output variables:
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
ENABLE_SANITIZER_LEAK
ENABLE_SANITIZER_THREAD
ENABLE_SANITIZER_MEMORY)
ENABLE_SANITIZER_MEMORY
ENABLE_SANITIZER_POINTER_COMPARE
ENABLE_SANITIZER_POINTER_SUBTRACT)

# then pass the sanitizers (e.g. ${ENABLE_SANITIZER_ADDRESS}) to project_options(... ${ENABLE_SANITIZER_ADDRESS} ...)

Expand All @@ -135,9 +162,13 @@ function(
ENABLE_SANITIZER_LEAK
ENABLE_SANITIZER_THREAD
ENABLE_SANITIZER_MEMORY
ENABLE_SANITIZER_POINTER_COMPARE
ENABLE_SANITIZER_POINTER_SUBTRACT
)
set(SANITIZERS "")
if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
set(SUPPORTED_SANITIZERS "")
if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
)
set(HAS_SANITIZER_SUPPORT ON)

# Disable gcc sanitizer on some macos according to https://github.com/orgs/Homebrew/discussions/3384#discussioncomment-6264292
Expand All @@ -154,39 +185,58 @@ function(
endif()

if(HAS_SANITIZER_SUPPORT)
list(APPEND SANITIZERS "address")
list(APPEND SANITIZERS "undefined")
list(APPEND SANITIZERS "leak")
list(APPEND SANITIZERS "thread")
list(APPEND SANITIZERS "memory")
set(SUPPORTED_SANITIZERS "")
foreach(
SANITIZER IN
ITEMS "address"
"undefined"
"leak"
"thread"
"memory"
"pointer-compare"
"pointer-subtract"
)
if((SANITIZER STREQUAL "pointer-compare" OR SANITIZER STREQUAL "pointer-subtract")
AND (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8)
)
# pointer-compare and pointer-subtract are supported only by GCC 8 and later
continue()
endif()

list(APPEND SUPPORTED_SANITIZERS ${SANITIZER})
endforeach()
endif()
elseif(MSVC)
# or it is MSVC and has run vcvarsall
string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir)
if(NOT "${index_of_vs_install_dir}" STREQUAL "-1")
list(APPEND SANITIZERS "address")
list(APPEND SUPPORTED_SANITIZERS "address")
endif()
endif()

list(JOIN SANITIZERS "," LIST_OF_SANITIZERS)
if(NOT SUPPORTED_SANITIZERS OR "${SUPPORTED_SANITIZERS}" STREQUAL "")
message(STATUS "No sanitizer is supported for the current platform/compiler")
return()
endif()

if(LIST_OF_SANITIZERS)
if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "")
if("address" IN_LIST SANITIZERS)
set(${ENABLE_SANITIZER_ADDRESS} "ENABLE_SANITIZER_ADDRESS" PARENT_SCOPE)
endif()
if("undefined" IN_LIST SANITIZERS)
set(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} "ENABLE_SANITIZER_UNDEFINED_BEHAVIOR" PARENT_SCOPE)
endif()
if("leak" IN_LIST SANITIZERS)
set(${ENABLE_SANITIZER_LEAK} "ENABLE_SANITIZER_LEAK" PARENT_SCOPE)
endif()
if("thread" IN_LIST SANITIZERS)
set(${ENABLE_SANITIZER_THREAD} "ENABLE_SANITIZER_THREAD" PARENT_SCOPE)
endif()
if("memory" IN_LIST SANITIZERS)
set(${ENABLE_SANITIZER_MEMORY} "ENABLE_SANITIZER_MEMORY" PARENT_SCOPE)
endif()
# Set the output variables
foreach(
SANITIZER IN
ITEMS "address"
"undefined"
"leak"
"thread"
"memory"
"pointer-compare"
"pointer-subtract"
)
set(SANITIZER_UPPERCASE "${SANITIZER}")
string(TOUPPER ${SANITIZER} SANITIZER_UPPERCASE)

if(${SANITIZER} IN_LIST SUPPORTED_SANITIZERS)
set(${ENABLE_SANITIZER_${SANITIZER_UPPERCASE}} "ENABLE_SANITIZER_${SANITIZER_UPPERCASE}" PARENT_SCOPE)
else()
set(${ENABLE_SANITIZER_${SANITIZER_UPPERCASE}} "" PARENT_SCOPE)
endif()
endif()
endforeach()
endfunction()
5 changes: 2 additions & 3 deletions src/StaticAnalyzers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ macro(enable_cppcheck CPPCHECK_OPTIONS)
elseif(CMAKE_C_STANDARD MATCHES [[99|11]])
set(CMAKE_C_CPPCHECK ${CMAKE_C_CPPCHECK} --std=c${CMAKE_C_STANDARD})
else()
message(
${WARNING_MESSAGE}
"cppcheck doesn't support specified C standard ${CMAKE_C_STANDARD}. Using the cppcheck default C standard version."
message(${WARNING_MESSAGE}
"cppcheck doesn't support C ${CMAKE_C_STANDARD} standard. Using the cppcheck default"
)
endif()
endif()
Expand Down
Loading

0 comments on commit 7f4c940

Please sign in to comment.