blob: 9c8015c307d3d2c37b9391d3f3ae2edc558d5d55 [file] [log] [blame]
# Copyright 2018 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/test.gni")
# TODO(fxbug.dev/60168): Consumers should import fuzzer_package explicitly. Remove once all are
# migrated.
import("//build/fuzzing/fuzzer_package.gni")
# A list of configs present in fuzzer toolchain variants.
#
# GN logic that needs to behave differently between fuzzer and non-fuzzer variants can determine the
# variant type by checking the `toolchain_variant.configs`, e.g.
# if (toolchain_variant.configs + fuzzer_configs - fuzzer_configs == toolchain_variant.configs) {
# # Not using a fuzzer toolchain variant.
# } else {
# # Using a fuzzer toolchain variant.
# }
#
# This approach works even with the way `import` caches this file.
#
fuzzer_configs = [ "//build/config/fuzzer" ]
# Instructs the linker to link against libFuzzer, see config documentation for details.
fuzzer_engine_configs = [ "//build/config/fuzzer:engine" ]
# Defines a fuzzer binary.
#
# The fuzzer template is used to create components containing binaries which leverage LLVM's
# libFuzzer to perform fuzz testing.
#
# Parameters are precisely those of an `executable`, along with:
#
# cmx (optional)
# [file] If specified, a file containing a component manifest to start from when generating
# manifests for fuzzers.
#
# corpus (optional)
# [path] If specified, a directory containing elements of a seed corpus. These should be a
# combination of hand-crafted fuzzer inputs that increase coverage, and artifacts from
# previous fuzzing runs, e.g. fuzzer inputs that triggered crashes. These will be used to seed
# new fuzzing runs, and as regression unit tests.
#
# dictionary (optional)
# [file] If specified, a file containing quoted inputs, one per line, that the fuzzer will use
# to generate new mutations.
#
#
# options (optional)
# [list of strings] Each option is of the form "key=value" and indicates command line options
# that the fuzzer should be invoked with. Valid keys are libFuzzer options
# (https://llvm.org/docs/LibFuzzer.html#options).
#
template("fuzzer") {
_label = get_label_info(":$target_name", "label_no_toolchain")
base_output_name = target_name
if (defined(invoker.output_name)) {
base_output_name = invoker.output_name
}
# Generate the component binary
executable(target_name) {
forward_variables_from(invoker,
"*",
[
"cmx",
"corpus",
"dictionary",
"options",
"visibility",
])
# Explicitly forward visibility for nested scopes.
forward_variables_from(invoker, [ "visibility" ])
testonly = true
variant_selector_target_type = "fuzzed_executable"
# TODO(fxbug.dev/27011): This shouldn't be necessary, but libzircon isn't currently
# linked into libFuzzer on Fuchsia.
if (is_fuchsia) {
if (configs + fuzzer_engine_configs - fuzzer_engine_configs != configs) {
if (!defined(deps)) {
deps = []
}
deps += [ "//src/zircon/lib/zircon" ]
}
}
}
# Generate the fuzzer component manifest
cmx_target = "${target_name}_cmx"
cmx_output = "$target_gen_dir/${base_output_name}.cmx"
generated_file(cmx_target) {
if (defined(invoker.cmx)) {
cmx = read_file(invoker.cmx, "json")
} else {
cmx = {
program = {
}
sandbox = {
}
}
}
contents = {
program = {
forward_variables_from(cmx.program, "*")
binary = "bin/$base_output_name"
}
sandbox = {
services = []
features = []
forward_variables_from(cmx.sandbox, "*")
services += [
"fuchsia.logger.LogSink",
"fuchsia.process.Launcher",
]
features += [
"isolated-persistent-storage",
"isolated-temp",
]
}
forward_variables_from(cmx,
[
"dev",
"runner",
"facets",
])
}
outputs = [ cmx_output ]
output_conversion = "json"
metadata = {
fuzz_spec = [
{
label = _label
fuzzer = base_output_name
manifest = get_path_info(cmx_output, "file")
},
]
}
}
# Generate seed corpus. See https://llvm.org/docs/LibFuzzer.html#corpus
# TODO(fxbug.dev/58940): Move away from invoking add_corpus.py and towards using
# in-tree corpus manifests.
corpus_target = "${target_name}_corpus"
corpus_output = "${target_gen_dir}/${base_output_name}/corpus.manifest"
action(corpus_target) {
script = "//build/fuzzing/add_corpus.py"
depfile = "$corpus_output.d"
outputs = [ corpus_output ]
args = [
"--fuzzer",
base_output_name,
"--depfile",
rebase_path(depfile, root_build_dir),
"--manifest",
rebase_path(corpus_output, root_build_dir),
]
if (defined(invoker.corpus)) {
args += [
"--corpus",
rebase_path(invoker.corpus, root_build_dir),
]
metadata = {
fuzz_spec = [
{
label = _label
corpus = get_label_info(invoker.corpus, "label_no_toolchain")
},
]
}
}
}
# Generate dictionary. See https://llvm.org/docs/LibFuzzer.html#dictionaries
dictionary_target = "${target_name}_dictionary"
dictionary_output = "${target_gen_dir}/${base_output_name}/dictionary"
if (defined(invoker.dictionary)) {
copy(dictionary_target) {
sources = [ invoker.dictionary ]
outputs = [ dictionary_output ]
}
} else {
generated_file(dictionary_target) {
contents = [ "# empty dictionary" ]
outputs = [ dictionary_output ]
}
}
# Generate options. See https://llvm.org/docs/LibFuzzer.html#options
options_target = "${target_name}_options"
options_output = "${target_gen_dir}/${base_output_name}/options"
generated_file(options_target) {
contents = []
if (defined(invoker.options)) {
contents = invoker.options
}
outputs = [ options_output ]
}
# Generate a unit test for the fuzzer.
test_target = "${target_name}_test"
test_output = "${base_output_name}_test"
test(test_target) {
output_name = test_output
sources = []
deps = []
forward_variables_from(invoker,
"*",
[
"cmx",
"corpus",
"dictionary",
"options",
"output_name",
"target_name",
"visibility",
])
# Explicitly forward visibility for nested scopes.
forward_variables_from(invoker, [ "visibility" ])
configs -= fuzzer_engine_configs
deps += [
":$corpus_target",
"//sdk/lib/syslog/cpp:backend_legacy",
"//src/lib/fuzzing/cpp:fuzzer_test",
]
}
# Generate the fuzzer test component manifest
test_cmx_target = "${test_target}_cmx"
test_cmx_output = "$target_gen_dir/${test_output}.cmx"
generated_file(test_cmx_target) {
contents = {
program = {
binary = "test/$test_output"
args = [
# Suppress warnings about missing directories
"-q",
# Seed corpus
"pkg/data/${base_output_name}/corpus",
# Live corpus
"data/${base_output_name}/corpus",
]
}
sandbox = {
services = [ "fuchsia.logger.LogSink" ]
}
}
outputs = [ test_cmx_output ]
output_conversion = "json"
deps = []
metadata = {
fuzz_spec = [
{
label = _label
fuzzer_test = test_output
test_manifest = get_path_info(test_cmx_output, "file")
},
]
}
}
}
set_defaults("fuzzer") {
configs = default_executable_configs + fuzzer_engine_configs
}