# Copyright Contributors to the OpenImageIO project.
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO

# This file is only reached when OIIO_BUILD_FUZZ_TARGETS=ON (guarded in the
# root CMakeLists.txt).

# libFuzzer requires a capable clang. If the compiler can't build the fuzz
# targets, skip them with a warning rather than failing the whole configure --
# that lets the rest of OIIO still build in a tree that happens to have
# OIIO_BUILD_FUZZ_TARGETS=ON with an unsuitable compiler.
#
# - GCC does not support libFuzzer at all.
# - Apple's clang (CMAKE_CXX_COMPILER_ID "AppleClang", shipped with Xcode and
#   the Command Line Tools) does not ship the libFuzzer runtime
#   (libclang_rt.fuzzer_osx.a), so -fsanitize=fuzzer fails to link. Use an
#   upstream LLVM clang instead (e.g. `brew install llvm`).
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    message (WARNING
        "OIIO_BUILD_FUZZ_TARGETS=ON requires clang; skipping fuzz targets "
        "(detected ${CMAKE_CXX_COMPILER_ID}). Set "
        "CMAKE_CXX_COMPILER=clang++ to build them.")
    return ()
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
    message (WARNING
        "OIIO_BUILD_FUZZ_TARGETS=ON does not work with Apple's clang, which "
        "lacks the libFuzzer runtime; skipping fuzz targets. Install upstream "
        "LLVM (e.g. 'brew install llvm') and configure with "
        "-DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++ "
        "-DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang.")
    return ()
endif ()

# The fuzz targets are always instrumented with the address sanitizer (see the
# target_compile_options below), independently of the global SANITIZE option.
# _FORTIFY_SOURCE is incompatible with ASan (clang predefines it to 0), so strip
# any inherited _FORTIFY_SOURCE define for this directory to avoid a
# -Wmacro-redefined error when OIIO's hardening defined it.
get_directory_property (_fuzz_defs COMPILE_DEFINITIONS)
list (FILTER _fuzz_defs EXCLUDE REGEX "^_FORTIFY_SOURCE")
set_directory_properties (PROPERTIES COMPILE_DEFINITIONS "${_fuzz_defs}")

# Resolve the fuzzing engine.
# - Local dev: defaults to -fsanitize=fuzzer (links the libFuzzer runtime).
# - OSS-Fuzz: sets LIB_FUZZING_ENGINE to a .a path for the chosen engine.
if (DEFINED ENV{LIB_FUZZING_ENGINE})
    set (OIIO_FUZZING_ENGINE "$ENV{LIB_FUZZING_ENGINE}")
else ()
    set (OIIO_FUZZING_ENGINE "-fsanitize=fuzzer")
endif ()

# Executable is named oiio_fuzz_image (not just fuzz_image) so that if it is
# ever accidentally built and installed, the name makes clear which package it
# belongs to. The source file keeps its shorter fuzz_image.cpp name.
add_executable (oiio_fuzz_image fuzz_image.cpp)

target_link_libraries (oiio_fuzz_image PRIVATE OpenImageIO)

# -fsanitize=fuzzer-no-link instruments code for coverage tracing without
# linking the fuzzer runtime; the runtime comes from OIIO_FUZZING_ENGINE at
# link time. address+undefined catch memory errors and UB respectively.
target_compile_options (oiio_fuzz_image PRIVATE
    -fsanitize=fuzzer-no-link,address,undefined
    -fno-omit-frame-pointer
)

target_link_options (oiio_fuzz_image PRIVATE
    ${OIIO_FUZZING_ENGINE}
    -fsanitize=address,undefined
)

# all_fuzz_targets is the conventional build alias used by OSS-Fuzz build.sh
# and local "cmake --build . --target all_fuzz_targets".
# add_custom_target (all_fuzz_targets DEPENDS oiio_fuzz_image)
install_targets (oiio_fuzz_image)

# Install the libFuzzer options file alongside the binary. OSS-Fuzz/ClusterFuzz
# keys the options file to the binary basename, so it is named to match:
# oiio_fuzz_image.options.
install (FILES oiio_fuzz_image.options DESTINATION ${CMAKE_INSTALL_BINDIR})
