# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Defines fuzzer_test.
#
import("//build/config/features.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//testing/test.gni")
# fuzzer_test is used to define individual libfuzzer tests.
#
# Supported attributes:
# - (required) sources - fuzzer test source files
# - deps - test dependencies
# - libs - Additional libraries to link.
# - frameworks - Apple-only. Additional frameworks to link.
# - additional_configs - additional configs to be used for compilation
# - dict - a dictionary file for the fuzzer.
# - environment_variables - certain whitelisted environment variables for the
# fuzzer (AFL_DRIVER_DONT_DEFER is the only one allowed currently).
# - libfuzzer_options - options for the fuzzer (e.g. close_fd_mask=N).
# These are mostly applied only when the fuzzer is being run using the
# libfuzzer fuzzing engine, but a few of these may be interpreted by
# other fuzzing engines too
# - centipede_options - options for the fuzzer (e.g. shmem_size_mb=N)
# when running using the centipede fuzzing engine.
# - asan_options - AddressSanitizer options (e.g. allow_user_segv_handler=1).
# - msan_options - MemorySanitizer options.
# - ubsan_options - UndefinedBehaviorSanitizer options.
# - seed_corpus - a directory with seed corpus.
# - seed_corpus_deps - dependencies for generating the seed corpus.
# - grammar_options - defines a grammar used by a grammar based mutator.
# - exclude_main - if you're going to provide your own 'main' function
# - high_end_job_required - whether the fuzzer requires bigger machines to run.
# Optional argument.
# - is_fuzzilli_compatible - whether the fuzzer is compatible with fuzzilli.
#
# If use_libfuzzer gn flag is defined, then proper fuzzer would be build.
# Without use_libfuzzer or use_afl a unit-test style binary would be built on
# linux and the whole target is a no-op otherwise.
#
# The template wraps test() target with appropriate dependencies.
# If any test run-time options are present (dict or libfuzzer_options), then a
# config (.options file) file would be generated or modified in root output
# dir (next to test).
template("fuzzer_test") {
_high_end_job_required = false
if (defined(invoker.high_end_job_required)) {
_high_end_job_required = invoker.high_end_job_required
}
# If the job is a high_end job and that we are currently building a high_end
# target, we should compile this fuzzer_test in. Otherwise, for now, we just
# compile everything in.
# TODO(crbug.com/333831251): Once CF supports high end jobs, do not compile
# high end targets except for high end jobs.
_should_build = (_high_end_job_required || !high_end_fuzzer_targets) &&
!disable_libfuzzer && use_fuzzing_engine &&
((defined(invoker.is_fuzzilli_compatible) &&
invoker.is_fuzzilli_compatible) || !use_fuzzilli)
if (_should_build) {
assert(defined(invoker.sources), "Need sources in $target_name.")
test_deps = []
if (defined(invoker.exclude_main) && invoker.exclude_main) {
test_deps += [ "//testing/libfuzzer:fuzzing_engine_no_main" ]
} else {
test_deps += [ "//testing/libfuzzer:fuzzing_engine_main" ]
}
test_data_deps = []
if (defined(invoker.deps)) {
test_deps += invoker.deps
}
if (defined(invoker.data_deps)) {
test_data_deps += invoker.data_deps
}
supporting_file_test_deps = []
supporting_file_test_data_deps = []
if (defined(invoker.seed_corpus) || defined(invoker.seed_corpuses)) {
assert(!(defined(invoker.seed_corpus) && defined(invoker.seed_corpuses)),
"Do not use both seed_corpus and seed_corpuses for $target_name.")
out = "$root_build_dir/$target_name" + "_seed_corpus.zip"
seed_corpus_deps = []
if (defined(invoker.seed_corpus_deps)) {
seed_corpus_deps += invoker.seed_corpus_deps
}
action(target_name + "_seed_corpus") {
script = "//testing/libfuzzer/archive_corpus.py"
testonly = true
args = [
"--output",
rebase_path(out, root_build_dir),
]
if (defined(invoker.seed_corpus)) {
args += [ rebase_path(invoker.seed_corpus, root_build_dir) ]
}
if (defined(invoker.seed_corpuses)) {
foreach(seed_corpus_path, invoker.seed_corpuses) {
args += [ rebase_path(seed_corpus_path, root_build_dir) ]
}
}
outputs = [ out ]
deps = [ "//testing/libfuzzer:seed_corpus" ] + seed_corpus_deps
}
if (archive_seed_corpus) {
supporting_file_test_deps += [ ":" + target_name + "_seed_corpus" ]
}
}
if (defined(invoker.dict) || defined(invoker.libfuzzer_options) ||
defined(invoker.centipede_options) || defined(invoker.asan_options) ||
defined(invoker.msan_options) || defined(invoker.ubsan_options) ||
defined(invoker.environment_variables) ||
defined(invoker.grammar_options)) {
if (defined(invoker.dict)) {
# Copy dictionary to output.
copy(target_name + "_dict_copy") {
sources = [ invoker.dict ]
outputs = [ "$root_build_dir/" + target_name + ".dict" ]
}
supporting_file_test_deps += [ ":" + target_name + "_dict_copy" ]
}
fuzzer_name = target_name
# Generate .options file.
config_file_name = target_name + ".options"
action(config_file_name) {
script = "//testing/libfuzzer/gen_fuzzer_config.py"
args = [
"--config",
rebase_path("$root_build_dir/" + config_file_name, root_build_dir),
]
if (defined(invoker.dict)) {
args += [
"--dict",
rebase_path("$root_build_dir/" + fuzzer_name + ".dict",
root_build_dir),
]
} else {
not_needed([ "fuzzer_name" ])
}
if (defined(invoker.libfuzzer_options)) {
args += [ "--libfuzzer_options" ]
args += invoker.libfuzzer_options
}
if (defined(invoker.centipede_options)) {
args += [ "--centipede_options" ]
args += invoker.centipede_options
}
if (defined(invoker.asan_options)) {
args += [ "--asan_options" ]
args += invoker.asan_options
}
if (defined(invoker.msan_options)) {
args += [ "--msan_options" ]
args += invoker.msan_options
}
if (defined(invoker.ubsan_options)) {
args += [ "--ubsan_options" ]
args += invoker.ubsan_options
}
if (defined(invoker.environment_variables)) {
args += [ "--environment_variables" ]
args += invoker.environment_variables
}
if (defined(invoker.grammar_options)) {
args += [ "--grammar_options" ]
args += invoker.grammar_options
}
outputs = [ "$root_build_dir/$config_file_name" ]
}
test_data_deps += [ ":" + config_file_name ]
supporting_file_test_deps += [ ":" + config_file_name ]
}
if (generate_fuzzer_owners) {
# Generating owners files is slow, only enable when fuzzing engine is
# used.
owners_file_name = target_name + ".owners"
action(owners_file_name) {
script = "//testing/libfuzzer/gen_fuzzer_owners.py"
pool = "//testing/libfuzzer:fuzzer_owners_pool"
args = [
"--owners",
rebase_path("$root_build_dir/" + owners_file_name, root_build_dir),
]
if (defined(invoker.sources) && invoker.sources != []) {
args += [ "--sources" ] + rebase_path(invoker.sources, "//")
} else if (defined(invoker.deps) && invoker.deps != []) {
_full_deps = []
foreach(_dep, invoker.deps) {
_full_deps += [ get_label_info(_dep, "dir") + ":" +
get_label_info(_dep, "name") ]
}
args += [
"--build-dir",
rebase_path("$root_build_dir/", root_build_dir),
"--deps",
] + _full_deps
}
outputs = [ "$root_build_dir/$owners_file_name" ]
}
supporting_file_test_data_deps += [ ":" + owners_file_name ]
}
# Copy to executable folder and codesign for catalyst
if (is_ios) {
# iOS only supports fuzzer in catalyst environment.
assert(target_environment == "catalyst")
# Loop over two kinds of deps to codesign these in two |action_foreach|s.
# Use the "test_deps", "test_data_deps" as identifiers in for loop.
foreach(_deps_type_label,
[
"test_deps",
"test_data_deps",
]) {
_dep_list = []
if (_deps_type_label == "test_deps") {
_dep_list = supporting_file_test_deps
} else {
_dep_list = supporting_file_test_data_deps
}
_files_to_sign = []
foreach(dep, _dep_list) {
_files_to_sign += get_target_outputs(dep)
}
if (_files_to_sign != []) {
_codesign_action_name =
target_name + "_codesign_supporting_files_" + _deps_type_label
action_foreach(_codesign_action_name) {
testonly = true
script = "//build/config/apple/codesign.py"
sources = _files_to_sign
_codesign_output_path =
"${root_build_dir}/codesign/{{source_file_part}}"
outputs = [ _codesign_output_path ]
args = [
"code-sign-file",
"--identity=" + ios_code_signing_identity,
"--output=" + rebase_path(_codesign_output_path, root_build_dir),
"{{source}}",
]
deps = _dep_list
}
_bundle_data_name = target_name + "_bundle_data_" + _deps_type_label
bundle_data(_bundle_data_name) {
testonly = true
sources = get_target_outputs(":${_codesign_action_name}")
outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ]
public_deps = [ ":${_codesign_action_name}" ]
}
if (_deps_type_label == "test_deps") {
test_deps += [ ":${_bundle_data_name}" ]
} else {
test_data_deps += [ ":${_bundle_data_name}" ]
}
}
}
} else {
test_deps += supporting_file_test_deps
test_data_deps += supporting_file_test_data_deps
}
test(target_name) {
forward_variables_from(invoker,
[
"cflags",
"cflags_cc",
"check_includes",
"defines",
"include_dirs",
"output_name",
"sources",
"libs",
"frameworks",
])
deps = test_deps
data_deps = test_data_deps
if (defined(invoker.additional_configs)) {
configs += invoker.additional_configs
}
configs += [ "//testing/libfuzzer:fuzzer_test_config" ]
# Used by WebRTC to suppress some Clang warnings in their codebase.
if (defined(invoker.suppressed_configs)) {
configs -= invoker.suppressed_configs
}
if (defined(invoker.generated_sources)) {
sources += invoker.generated_sources
}
if (is_ios) {
info_plist =
"//testing/libfuzzer/fuzzer_support_ios/fuzzer-engine-Info.plist"
}
if (is_mac) {
sources += [ "//testing/libfuzzer/libfuzzer_exports.h" ]
}
}
} else {
# noop on unsupported platforms.
# mark attributes as used.
not_needed(invoker, "*")
not_needed(invoker,
[
"deps",
"seed_corpus",
"sources",
])
group(target_name) {
}
}
}