# detect-sanitizer.cmake -- Detect supported compiler sanitizer flags
# Licensed under the Zlib license, see LICENSE.md for details

macro(check_sanitizer_support known_checks supported_checks)
    set(available_checks "")

    # Build list of supported sanitizer flags by incrementally trying compilation with
    # known sanitizer checks

    foreach(check ${known_checks})
        if(available_checks STREQUAL "")
            set(compile_checks "${check}")
        else()
            set(compile_checks "${available_checks},${check}")
        endif()

        set(CMAKE_REQUIRED_FLAGS "-fsanitize=${compile_checks}")

        check_c_source_compiles("int main() { return 0; }" HAS_SANITIZER_${check}
            FAIL_REGEX "not supported|unrecognized command|unknown option")

        set(CMAKE_REQUIRED_FLAGS)

        if(HAS_SANITIZER_${check})
            set(available_checks ${compile_checks})
        endif()
    endforeach()

    set(${supported_checks} ${available_checks})
endmacro()

macro(add_address_sanitizer)
    set(known_checks
        address
        pointer-compare
        pointer-subtract
        )

    check_sanitizer_support("${known_checks}" supported_checks)
    if(NOT ${supported_checks} STREQUAL "")
        message(STATUS "Address sanitizer is enabled: ${supported_checks}")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=${supported_checks}")
    else()
        message(STATUS "Address sanitizer is not supported")
    endif()

    if(CMAKE_CROSSCOMPILING_EMULATOR)
        # Only check for leak sanitizer if not cross-compiling due to qemu crash
        message(WARNING "Leak sanitizer is not supported when cross compiling")
    else()
        # Leak sanitizer requires address sanitizer
        check_sanitizer_support("leak" supported_checks)
        if(NOT ${supported_checks} STREQUAL "")
            message(STATUS "Leak sanitizer is enabled: ${supported_checks}")
            set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=${supported_checks}")
        else()
            message(STATUS "Leak sanitizer is not supported")
        endif()
    endif()
endmacro()

macro(add_memory_sanitizer)
    check_sanitizer_support("memory" supported_checks)
    if(NOT ${supported_checks} STREQUAL "")
        message(STATUS "Memory sanitizer is enabled: ${supported_checks}")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=${supported_checks}")
    else()
        message(STATUS "Memory sanitizer is not supported")
    endif()
endmacro()

macro(add_undefined_sanitizer)
    set(known_checks
        array-bounds
        bool
        bounds
        builtin
        enum
        float-cast-overflow
        float-divide-by-zero
        function
        integer-divide-by-zero
        local-bounds
        null
        nonnull-attribute
        pointer-overflow
        return
        returns-nonnull-attribute
        shift
        shift-base
        shift-exponent
        signed-integer-overflow
        undefined
        unsigned-integer-overflow
        unsigned-shift-base
        vla-bound
        vptr
        )

    # Only check for alignment sanitizer flag if unaligned access is not supported
    if(NOT UNALIGNED_OK)
        list(APPEND known_checks alignment)
    endif()
    # Object size sanitizer has no effect at -O0 and produces compiler warning if enabled
    if(NOT CMAKE_C_FLAGS MATCHES "-O0")
        list(APPEND known_checks object-size)
    endif()

    check_sanitizer_support("${known_checks}" supported_checks)

    if(NOT ${supported_checks} STREQUAL "")
        message(STATUS "Undefined behavior sanitizer is enabled: ${supported_checks}")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=${supported_checks}")

        # Group sanitizer flag -fsanitize=undefined will automatically add alignment, even if
        # it is not in our sanitize flag list, so we need to explicitly disable alignment sanitizing.
        if(UNALIGNED_OK)
            set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-sanitize=alignment")
        endif()
    else()
        message(STATUS "UNdefined behavior sanitizer is not supported")
    endif()
endmacro()