cmake_minimum_required(VERSION 3.12)

add_library(__project_sanitizer__ INTERFACE)
add_library(project::sanitizer ALIAS __project_sanitizer__)

set(COMPILER_ID "GNU")
if(CMAKE_C_COMPILER_ID AND CMAKE_CXX_COMPILER_ID)
    if(${CMAKE_C_COMPILER_ID} STREQUAL ${CMAKE_CXX_COMPILER_ID})
        set(COMPILER_ID CMAKE_CXX_COMPILER_ID)
    else()
        message(WARNING "C-Compiler does not fit C++-Compiler")
    endif()
elseif(CMAKE_C_COMPILER_ID)
    set(COMPILER_ID ${CMAKE_C_COMPILER_ID})
elseif(CMAKE_CXX_COMPILER_ID)
    set(COMPILER_ID ${CMAKE_CXX_COMPILER_ID})
else()
    message(WARNING "Language not supported")
endif()

if(COMPILER_ID STREQUAL "Clang")
    option(SANITIZER_ADDRESS "Include address sanitizer" OFF)
    option(SANITIZER_THREAD "Include thread sanitizer" OFF)
    option(SANITIZER_MEMORY "Include memory sanatizer" OFF)
	#option(SANITIZER_DATAFLOW "Include dataflow sanitizer" OFF)
	#option(SANITIZER_CFI "Include cfi sanitizer" OFF)
    option(SANITIZER_SAFE_STACK "Include safe-stack sanitizer" OFF)
    option(SANITIZER_UNDEFINED "Include undefined sanitizer" OFF)


    function(setup_sanitizer)
        set(SANITIZER_LIST)
        if(SANITIZER_ADDRESS)
            if(SANITIZER_THREAD OR SANITIZER_MEMORY OR SANITIZER_SAFE_STACK)
                message(SEND_ERROR "It is not possible to combine more than one of the address, safe stack, thread and memory sanitizer in the same program")
            endif()
            set(SANITIZER_LIST
                "address"
                )
        elseif(SANITIZER_SAFE_STACK)
            if(SANITIZER_THREAD OR SANITIZER_MEMORY)
                message(SEND_ERROR "It is not possible to combine more than one of the address, safe stack, thread and memory sanitizer in the same program")
            endif()
            set(SANITIZER_LIST
                "safe-stack"
                )
        elseif(SANITIZER_THREAD)
            if(SANITIZER_MEMORY)
                message(SEND_ERROR "It is not possible to combine more than one of the address, safe stack, thread and memory sanitizer in the same program")
            endif()
            set(SANITIZER_LIST
                "thread"
                )
        elseif(SANITIZER_MEMORY)
            set(SANITIZER_LIST
                "memory"
                )
        endif()
        if(SANITIZER_UNDEFINED)
            set(SANITIZER_LIST ${SANITIZER_LIST} "undefined")
        endif()
        if(SANITIZER_DATAFLOW)
            set(SANITIZER_LIST ${SANITIZER_LIST} "dataflow")
        endif()
        if(SANITIZER_CFI)
            set(SANITIZER_LIST ${SANITIZER_LIST} "cfi")
        endif()
        
        string(JOIN "," SANITIZER_LIST_STR ${SANITIZER_LIST})
        list(LENGTH SANITIZER_LIST SANITIZER_COUNT)
        if(SANITIZER_COUNT GREATER 0)
            target_compile_options(__project_sanitizer__ INTERFACE
                "-fsanitize=${SANITIZER_LIST_STR}"
                )
            target_link_libraries(__project_sanitizer__ INTERFACE
				"-fsanitize=${SANITIZER_LIST_STR}"
                )
        endif()
    endfunction()
elseif(COMPILER_ID STREQUAL "GNU")
    option(SANITIZER_ADDRESS "Include address sanitizer" OFF)
	# option(SANITIZER_KERNEL_ADDRESS "Include kernel-address sanitizer" OFF)
    option(SANITIZER_POINTER_COMPARE "Include pointer-compare sanitizer" OFF)
    option(SANITIZER_POINTER_SUBTRACT "Include pointer-subtract sanitizer" OFF)
    option(SANITIZER_THREAD "Include thread sanitizer" OFF)
    option(SANITIZER_LEAK "Include leak sanitizer" OFF)
    option(SANITIZER_UNDEFINED "Include undefined behavior sanitizer" OFF)

    function(setup_sanitizer)
        set(SANITIZER_LIST)

        if(SANITIZER_ADDRESS)
            if(SANITIZER_THREAD OR SANITIZER_LEAK OR SANITIZER_KERNEL_ADDRESS)
                message(SHOW_ERROR "It is not possible to combine more than one of the address, kernel address, thread and leak sanitizer in the same program")
            endif()
            set(SANITIZER_LIST "address")
        elseif(SANITIZER_KERNEL_ADDRESS)
            if(SANITIZER_THREAD OR SANITIZER_LEAK)
                message(SEND_ERROR "It is not possible to combine more than one of the address, kernel address, thread and leak sanitizer in the same program")
            endif()
            set(SANITIZER_LIST "kernel-address")
        elseif(SANITIZER_THREAD)
            if(SANITIZER_LEAK)
                message(SEND_ERROR "It is not possible to combine more than one of the address, kernel address, thread and leak sanitizer in the same program")
            endif()
            set(SANITIZER_LIST "thread")
        elseif(SANITIZER_LEAK)
            set(SANITIZER_LIST "leak")
        endif()

        if(SANITIZER_POINTER_COMPARE)
            if(SANITIZER_ADDRESS OR SANITIZER_KERNEL_ADDRESS)
                set(SANITIZER_LIST ${SANITIZER_LIST} "pointer-compare")
            else()
                message(SEND_ERROR "SANITIZER_POINTER_COMPARE must be combined with SANITIZER_ADDRESS or SANITIZER_KERNEL_ADDRESS")
            endif()
        endif()
        if(SANITIZER_POINTER_SUBTRACT)
            if(SANITIZER_ADDRESS OR SANITIZER_KERNEL_ADDRESS)
                set(SANITIZER_LIST ${SANITIZER_LIST} "pointer-subtract")
            else()
                message(SEND_ERROR "SANITIZER_POINTER_COMPARE must be combined with SANITIZER_ADDRESS or SANITIZER_KERNEL_ADDRESS")
            endif()
        endif()
        if(SANITIZER_UNDEFINED)
            set(SANITIZER_LIST ${SANITIZER_LIST} "undefined")
        endif()

        string(JOIN "," SANITIZER_LIST_STR ${SANITIZER_LIST})
        list(LENGTH SANITIZER_LIST SANITIZER_COUNT)
        if(SANITIZER_COUNT GREATER 0)
            target_compile_options(__project_sanitizer__ INTERFACE
                "-fsanitize=${SANITIZER_LIST_STR}"
                )
            target_link_libraries(__project_sanitizer__ INTERFACE
				"-fsanitize=${SANITIZER_LIST_STR}"
                )
        endif()
    endfunction()
else()
    message(WARNING "Compiler ${COMPILER_ID} not supported.")
endif()
