chromium/chrome/test/fuzzing/in_process_fuzzer.gni

# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//mojo/public/tools/fuzzers/mojolpm.gni")
import("//testing/libfuzzer/fuzzer_test.gni")
import("//third_party/protobuf/proto_library.gni")

# This template allows creation of a fuzzer which has access to all the
# functionality of a browser_test, including a full GUI instance of Chromium.
# See in_process_fuzzer.h.
#
# It accepts all the same arguments as //testing/libfuzzer/fuzzer_test.gni's
# fuzzer_test template.
#
# This template also accepts the argument `target_type`. This defaults to
# `fuzzer_test`.
template("in_process_fuzzer") {
  if (fuzzing_engine_supports_custom_main) {
    if (defined(invoker.target_type)) {
      target_type = invoker.target_type
    } else {
      target_type = "fuzzer_test"
    }
    target(target_type, target_name) {
      # Those tests are big, they need a lot of resources.
      high_end_job_required = true
      deps = [ "//chrome/test/fuzzing:in_process_fuzzer_runner" ]
      if (defined(invoker.deps)) {
        deps += invoker.deps
      }
      forward_variables_from(invoker,
                             "*",
                             [
                               "deps",
                               "exclude_main",
                               "asan_options",
                               "libfuzzer_options",
                               "centipede_options",
                             ])

      # As these are full browser tests, we have crashpad running,
      # and we can't intercept signals that crashpad needs.
      sanitizer_options = [
        "allow_user_segv_handler=1",
        "handle_sigtrap=1",
        "handle_abort=1",
        "handle_segv=1",
        "handle_sigbus=1",
        "handle_sigfpe=1",
        "handle_sigill=1",
        "handle_sigtrap=1",
      ]

      asan_options = [
        # Browser tests store arbitrary data in globals; leak
        # detection will come up with lots of false positives.
        "detect_leaks=0",
      ]

      asan_options += sanitizer_options

      ubsan_options = sanitizer_options

      msan_options = sanitizer_options

      # Again, these are full browser tests, so it needs a fairly high memory
      # limit so that it runs correctly.
      libfuzzer_options = [
        "rss_limit_mb=8192",

        # We can't allow libfuzzer to fork because these fuzzers may have
        # pre-existing threads. See crbug.com/325928325
        "fork=0",
      ]

      centipede_options = [
        # As for now, CF machines only have 8GB. Centipede allocates two
        # buffers of `shmem_size_mb`, one for the input test cases, one for the
        # output (features, cmps, stats). Because those fuzzers are big, this
        # means we are very likely to fill-up the output buffers, and thus
        # running out of memory if `shmem_size_mb` value is too big.
        # Note that 1024 is currently the default value in Centipede.
        "shmem_size_mb=1024",

        # Since `shmem_size_mb` is fairly low, we want not to lose features
        # that would overflow Centipede's output buffer. For this reason, we
        # are lowering `batch_size`. This number has been chosen after some
        # experiments. This might not be 100% accurate.
        "batch_size=400",

        # Browser tests are big
        "address_space_limit_mb=0",

        # For testing. Those fuzzers are extremely slow on ClusterFuzz, and
        # using this option doesn't help.
        "fork_server=0",

        "rss_limit_mb=8192",

        # This value is the one used by CF for Centipede fuzzers.
        # Default computed timeout per batch is ridiculously low (1127, which
        # means ~1.1 sec per input average) for those fuzzers.
        "timeout_per_batch=8400",
      ]

      if (defined(invoker.asan_options)) {
        asan_options += invoker.asan_options
      }
      if (defined(invoker.libfuzzer_options)) {
        libfuzzer_options += invoker.libfuzzer_options
      }
      if (defined(invoker.centipede_options)) {
        centipede_options += invoker.centipede_options
      }
      exclude_main = true
    }
  } else {
    # noop if the fuzzer harness always provides its own main
    not_needed(invoker, "*")

    group(target_name) {
    }
  }
}

# This template is a proto-based version of the in_process_fuzzer template.
#
# This rule copies the proto-relevant bits from the mojolpm_fuzzer_test
# template (//mojo/public/tools/fuzzers/mojolpm.gni)
#
# Parameters:
#   sources
#       List of source .cc files to compile.
#
#   deps
#       List of dependencies to compile this target.
#
#   proto_source
#       Single source .proto file defining the structure of a testcase.
#
#   proto_deps
#       List of additional dependencies for compiling proto_source.
#
#   testcase_proto_kind (optional, required if seed_corpus_sources provided)
#       Name of proto message type representing a testcase.
#
#   seed_corpus_sources (optional)
#       List of source .textproto files used to build a seed corpus.

template("in_process_proto_fuzzer") {
  if (fuzzing_engine_supports_custom_main) {
    assert(defined(invoker.sources) && defined(invoker.proto_source),
           "\"sources\" and \"proto_source\" must be defined for $target_name")

    assert(
        !defined(invoker.seed_corpus_sources) ||
            defined(invoker.testcase_proto_kind),
        "\"testcase_proto_kind\" must be defined for $target_name since \"seed_corpus_sources\" is defined.")

    proto_target_name = "${target_name}_proto"

    proto_library(proto_target_name) {
      # Work relative to src (//) instead of (by default) the BUILD file.
      proto_in_dir = "//"

      sources = [ invoker.proto_source ]
      generate_python = false

      proto_deps = []

      import_dirs = [ root_gen_dir ]

      link_deps = []

      if (defined(invoker.proto_deps)) {
        proto_deps += invoker.proto_deps
        link_deps += invoker.proto_deps
      }

      testonly = true
    }

    if (defined(invoker.seed_corpus_sources)) {
      protoc_convert_target_name = "${target_name}_protoc_convert"
      seed_corpus_path = "${target_gen_dir}/${target_name}_seed_corpus"

      protoc_convert(protoc_convert_target_name) {
        sources = invoker.seed_corpus_sources

        inputs = [ invoker.proto_source ]

        output_pattern = "${seed_corpus_path}/{{source_name_part}}.binarypb"

        args = [
          "--encode=${invoker.testcase_proto_kind}",
          "-I",
          rebase_path(root_gen_dir),
          "-I",
          rebase_path("//"),
          rebase_path(inputs[0]),
        ]

        deps = []

        if (defined(invoker.proto_deps)) {
          deps += invoker.proto_deps
        }

        testonly = true
      }
    }

    in_process_fuzzer(target_name) {
      sources = invoker.sources
      deps = [
        ":${proto_target_name}",
        "//chrome/test/fuzzing:in_process_proto_fuzzer_runner",
        "//third_party/libprotobuf-mutator",
      ]

      if (defined(invoker.deps)) {
        deps += invoker.deps
      }

      if (defined(invoker.seed_corpus_sources)) {
        seed_corpus = seed_corpus_path
        seed_corpus_deps = [ ":${protoc_convert_target_name}" ]
      }

      forward_variables_from(invoker,
                             [
                               "libfuzzer_options",
                               "centipede_options",
                             ])
    }
  } else {
    # noop if the fuzzer harness always provides its own main
    not_needed(invoker, "*")

    group(target_name) {
    }
  }
}

# This template aims at mirroring what `mojolpm_fuzzer_test` does, except that
# it embeds InProcessFuzzer internals in order to function correctly.
#
# Parameters:
#  see parameters for //mojo/public/tools/fuzzers/mojolpm.gni:mojolpm_fuzzer_test
template("in_process_mojolpm_fuzzer") {
  in_process_fuzzer(target_name) {
    target_type = "mojolpm_fuzzer_test"
    forward_variables_from(invoker,
                           "*",
                           [
                             "testonly",
                             "visibility",
                           ])
  }
}

# This template aims at mirroring what `mojolpm_generated_fuzzer` does, except
# that it embeds InProcessFuzzer internals in order to function correctly.
#
# Parameters:
#  see parameters for //mojo/public/tools/fuzzers/mojolpm.gni:mojolpm_generated_fuzzer
template("in_process_mojolpm_generated_fuzzer") {
  in_process_fuzzer(target_name) {
    target_type = "mojolpm_generated_fuzzer"
    ensure_remote = true
    forward_variables_from(invoker,
                           "*",
                           [
                             "testonly",
                             "visibility",
                           ])
  }
}