# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

import("//build_overrides/pigweed.gni")

import("$dir_pw_build/error.gni")
import("$dir_pw_build/target_types.gni")
import("$dir_pw_unit_test/test.gni")
import("$pw_external_fuzztest/fuzztest.gni")

config("public_include_path") {
  include_dirs = [ "public" ]
  visibility = [ ":*" ]
}

config("private_include_path") {
  include_dirs = [ "private" ]
  visibility = [ ":*" ]
}

config("overrides_include_path") {
  include_dirs = [
    "public_overrides",
    "private_overrides",
  ]
  visibility = [ ":*" ]
}

# See https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
config("fuzzing_build_mode_unsafe_for_production") {
  defines = [ "FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" ]
}

pw_test_group("tests") {
  group_deps = [
    ":fuzztest_tests",
    "examples/fuzztest:tests",
    "examples/libfuzzer:tests",
  ]
}

pw_source_set("asan_default_options") {
  sources = [ "asan_default_options.c" ]
}

################################################################################
# FuzzTest support
#
# Create FuzzTest-style fuzzers by adding a dep on dir_pw_fuzzer:fuzztest

group("fuzztest") {
  if (dir_pw_third_party_fuzztest != "" && pw_toolchain_FUZZING_ENABLED) {
    public_deps = [ ":fuzztest.enabled" ]
  } else {
    public_deps = [ ":fuzztest.disabled" ]
  }
}

# Used by fuzzable unit tests when fuzzing is enabled. Includes headers and deps
# that provide a Pigweed-compatible subset of FuzzTest, as well as extensions to
# support common Pigweed types.
if (dir_pw_third_party_fuzztest != "" && pw_toolchain_FUZZING_ENABLED) {
  pw_source_set("fuzztest.enabled") {
    public = [
      "private/pw_fuzzer/internal/fuzztest.h",
      "public/pw_fuzzer/fuzztest.h",
    ]
    public_configs = [
      ":public_include_path",
      ":private_include_path",
    ]
    public_deps = [
      "$pw_external_fuzztest/fuzztest:fuzztest_core",
      dir_pw_containers,
      dir_pw_result,
      dir_pw_status,
      dir_pw_string,
    ]
  }
}

# Used by fuzzable unit tests when fuzzing is disabled. Includes stubs of the
# Pigweed-compatible subset of FuzzTest's interface, as well as extensions to
# support common Pigweed types.
pw_source_set("fuzztest.disabled") {
  public = [
    "private_overrides/pw_fuzzer/internal/fuzztest.h",
    "public/pw_fuzzer/fuzztest.h",
    "public_overrides/fuzztest/fuzztest.h",
  ]
  public_configs = [
    ":public_include_path",
    ":overrides_include_path",
  ]
  public_deps = [
    dir_pw_containers,
    dir_pw_result,
    dir_pw_status,
    dir_pw_string,
  ]
}

pw_test("fuzztest_tests") {
  sources = [ "domain_test.cc" ]
  deps = [ ":fuzztest" ]
}

# This target should only be used when defining a fuzzing toolchain, e.g. to set
# `pw_unit_test_BACKEND = "$dir_pw_fuzzer:gtest"
# TODO: b/295961502 - Support running FuzzTest-based fuzzers on OSS-Fuzz.
if (dir_pw_third_party_googletest == "") {
  pw_error("gtest") {
    message_lines = [
      "pw_unit_test_BACKEND is set to dir_pw_fuzzer:gtest, ",
      "but dir_pw_third_party_googletest is not set.",
    ]
  }
} else if (!pw_toolchain_FUZZING_ENABLED) {
  pw_error("gtest") {
    message_lines = [
      "pw_unit_test_BACKEND is set to dir_pw_fuzzer:gtest, ",
      "but $current_toolchain does not support fuzzing.",
    ]
  }
} else {
  group("gtest") {
    if (pw_toolchain_OSS_FUZZ_ENABLED) {
      public_deps = [ "$dir_pw_unit_test:light" ]
    } else {
      public_deps = [ "$dir_pw_unit_test:googletest" ]
    }
  }
}

# This target should only be used when defining a fuzzing toolchain, e.g. to set
# `pw_unit_test_MAIN = "$dir_pw_fuzzer:fuzztest_main"
# TODO: b/295961502 - Support running FuzzTest-based fuzzers on OSS-Fuzz.
if (dir_pw_third_party_fuzztest == "") {
  pw_error("fuzztest_main") {
    message_lines = [
      "pw_unit_test_MAIN is set to dir_pw_fuzzer:fuzztest_main, ",
      "but dir_pw_third_party_fuzztest is not set.",
    ]
  }
} else if (!pw_toolchain_FUZZING_ENABLED) {
  pw_error("fuzztest_main") {
    message_lines = [
      "pw_unit_test_MAIN is set to dir_pw_fuzzer:fuzztest_main, ",
      "but $current_toolchain does not support fuzzing.",
    ]
  }
} else {
  group("fuzztest_main") {
    if (pw_toolchain_OSS_FUZZ_ENABLED) {
      deps = [ "$dir_pw_unit_test:simple_printing_main" ]
    } else {
      deps = [
        ":asan_default_options",
        "$pw_external_fuzztest/fuzztest:fuzztest_gtest_main",
      ]
    }
  }
}

################################################################################
# libFuzzer support
#
# Create libFuzzer-style fuzzers by using the `pw_fuzzer` template from
# fuzzer.gni.

# Add flags for linking against compiler-rt's libFuzzer. This is added
# automatically by `pw_fuzzer`.
config("libfuzzer_config") {
  ldflags = [ "-fsanitize=fuzzer" ]
}

# Includes wrapper's for LLVM's libFuzzer compiler runtime library.
pw_source_set("libfuzzer") {
  public = [
    "public/pw_fuzzer/asan_interface.h",
    "public/pw_fuzzer/fuzzed_data_provider.h",
  ]
  public_configs = [ ":public_include_path" ]
  public_deps = [ dir_pw_log ]
}

# This can be linked against fuzz target functions to create unit tests for
# them.
pw_source_set("libfuzzer_test") {
  testonly = pw_unit_test_TESTONLY
  sources = [ "pw_fuzzer_disabled.cc" ]
  public_deps = [ ":libfuzzer" ]
  deps = [ dir_pw_unit_test ]
}

# libFuzzer-based fuzzers have a distinct dep graph.
group("fuzzers") {
  deps = [ "examples/libfuzzer:fuzzers" ]
}

################################################################################
# Local fuzzing support

# Add flags for adding LLVM sanitizer coverage for fuzzing. This is added by
# the host_clang_fuzz toolchains.
config("instrumentation") {
  cflags = [ "-fsanitize=fuzzer-no-link" ]
}

################################################################################
# OSS-Fuzz support
#
# OSS-Fuzz manipulates compiler and linker flags directly. See
# google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements.
#
# WARNING: This is not hermetic by design. It never can be, and never will be.

config("oss_fuzz_instrumentation") {
  cflags_c = string_split(getenv("CFLAGS"))
  cflags_cc = string_split(getenv("CXXFLAGS"))

  # OSS-Fuzz sets "-stdlib=libc++", which conflicts with the "-nostdinc++" set
  # by `pw_minimal_cpp_stdlib`.
  if (cflags_cc + [ "-stdlib=libc++" ] - [ "-stdlib=libc++" ] != cflags_cc) {
    cflags_cc += [ "-Wno-unused-command-line-argument" ]
  }

  # Disable UBSan vptr when the target is built with -fno-rtti.
  if (cflags_cc + [ "-fno-rtti" ] - [ "-fno-rtti" ] != cflags_cc) {
    cflags_cc += [ " -fno-sanitize=vptr" ]
  }
  cflags_cc += [ "-fcoverage-compilation-dir=" + getenv("PW_ROOT") ]

  ldflags = cflags_cc + [ "-fuse-ld=lld" ]
}

config("libfuzzer_oss_fuzz_config") {
  engine = getenv("LIB_FUZZING_ENGINE")
  if (engine == "") {
    engine = "-fsanitize=fuzzer"
  }
  ldflags = [ engine ]
}
