pe-parse/cmake/sanitizers.cmake
Eric Kilmer 7d285ea7b9 CMake better cache variable properties options for sanitizers
Makes it easier for CMake GUI interfaces to know this is a variable that
takes in different strings instead of a boolean.
2021-05-19 17:40:42 -06:00

175 lines
7.0 KiB
CMake

# Enable C/C++ sanitizers in your CMake project by `include`ing this file
# somewhere in your CMakeLists.txt.
#
# Example:
#
# include("cmake/sanitizers.cmake")
# process_sanitizer(MY_PROJECT)
#
# Example CMake configuration usage for `MY_PROJECT`:
#
# cmake -Bbuild-asan-ubsan -H. -DMY_PROJECT_USE_SANITIZER=Address,Undefined
#
# where "MY_PROJECT" can by any arbitrary text that will be prepended to some
# expected CMake variables--see below.
#
# This file expects the following variables to be set during CMake
# configuration, where the prefix is set by the caller of `process_sanitizer`:
#
# - *_USE_SANITIZER
# - A string value that is one of the following:
# - Address
# - HWAddress
# - Memory
# - MemoryWithOrigins
# - Undefined
# - Thread
# - DataFlow
# - Leak
# - Address,Undefined
#
# - *_OPTIMIZE_SANITIZED_BUILDS
# - A boolean value to set whether a higher optimization is used in debug
# builds
#
# - *_BLACKLIST_FILE
# - A filepath to a sanitizer blacklist file.
function(append value)
foreach(variable ${ARGN})
set(${variable}
"${${variable}} ${value}"
PARENT_SCOPE)
endforeach(variable)
endfunction()
macro(append_common_sanitizer_flags prefix)
if (NOT MSVC)
# Append -fno-omit-frame-pointer and turn on debug info to get better
# stack traces.
append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
append("-fno-optimize-sibling-calls" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
append("-gline-tables-only" CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZE_REL)
# Use -O1 even in debug mode, otherwise sanitizers slowdown is too large.
if (${prefix}_OPTIMIZE_SANITIZED_BUILDS)
message(STATUS "Optimizing sanitized Debug build")
append("-O1" CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG)
endif()
else()
# Keep frame pointers around.
append("/Oy-" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Always ask the linker to produce symbols with asan.
append("/Zi" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Workaround for incompatible (warning-producing) default CMake flag
# https://docs.microsoft.com/en-us/cpp/sanitizers/asan-known-issues
# https://gitlab.kitware.com/cmake/cmake/-/issues/19084
string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
append("/debug" CMAKE_EXE_LINKER_FLAGS CMAKE_MODULE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS)
endif()
endmacro()
# Main logic
macro(process_sanitizer prefix)
# Add options for the project to use sanitizers
set(${prefix}_USE_SANITIZER "false" CACHE STRING "Enable building with sanitizer support.")
set_property(CACHE ${prefix}_USE_SANITIZER PROPERTY STRINGS Address HWAddress Memory MemoryWithOrigins Undefined Thread DataFlow Leak "Address,Undefined")
if (UNIX)
option(${prefix}_OPTIMIZE_SANITIZED_BUILDS "Optimize builds that use sanitization" false)
option(${prefix}_BLACKLIST_FILE "Path to blacklist file for sanitizers" "")
option(${prefix}_USE_SANITIZE_COVERAGE "Set for libFuzzer-required instrumentation, no linking." false)
endif()
if (${prefix}_USE_SANITIZER)
if(UNIX)
if(${prefix}_USE_SANITIZER STREQUAL "Address")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "HWAddress")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=hwaddress" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER MATCHES "Memory(WithOrigins)?")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=memory" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
if(${prefix}_USE_SANITIZER STREQUAL "MemoryWithOrigins")
message(STATUS "Building with MemoryWithOrigins sanitizer")
append("-fsanitize-memory-track-origins" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(STATUS "Building with Memory sanitizer")
endif()
elseif(${prefix}_USE_SANITIZER STREQUAL "Undefined")
message(STATUS "Building with Undefined sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Execution error on undefined detection. Could be optional to add this
append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "Thread")
message(STATUS "Building with Thread sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=thread" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "DataFlow")
message(STATUS "Building with DataFlow sanitizer")
append("-fsanitize=dataflow" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "Leak")
message(STATUS "Building with Leak sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=leak" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "Address,Undefined"
OR ${prefix}_USE_SANITIZER STREQUAL "Undefined,Address")
message(STATUS "Building with Address, Undefined sanitizers")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=address,undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Execution error on undefined detection. Could be optional to add this
append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(
FATAL_ERROR "Unsupported value of ${prefix}_USE_SANITIZER: '${${prefix}_USE_SANITIZER}'")
endif()
elseif(MSVC)
if(${prefix}_USE_SANITIZER STREQUAL "Address")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("/fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(FATAL_ERROR "This sanitizer is not yet supported in the MSVC environment: '${${prefix}_USE_SANITIZER}'")
endif()
else()
message(FATAL_ERROR "${prefix}_USE_SANITIZER is not supported on this platform.")
endif()
# If specified, use a blacklist file
if (EXISTS "${${prefix}_BLACKLIST_FILE}")
message(STATUS "Using sanitizer blacklist file: ${${prefix}_BLACKLIST_FILE}")
append("-fsanitize-blacklist=${${prefix}_BLACKLIST_FILE}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
# Set a once non-default option for more detection
if (${prefix}_USE_SANITIZER MATCHES "(Undefined,)?Address(,Undefined)?")
if (UNIX)
append("-fsanitize-address-use-after-scope" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
endif()
# Set for libFuzzer-required instrumentation, no linking.
if (${prefix}_USE_SANITIZE_COVERAGE)
message(STATUS "Setting up sanitizer for coverage support with 'fuzzer-no-link'")
append("-fsanitize=fuzzer-no-link" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
endif()
endmacro()