blob: 9049b53dfea14b944124c81ccbc83b6441174ba6 [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/package.gni")
# TODO(aarongreen): SEC-224. Add tests to catch fuzzer building/packaging
# regressions.
# Defines a fuzz target component
#
# The fuzz_target template is used to create components containing binaries
# which leverage LLVM's libFuzzer to perform fuzz testing.
#
# Parameters
#
# sources (optional)
# Usual GN meaning. If present, the list must include a file defining
# `LLVMFuzzerTestOneInput`.
#
# binary (optional)
# [path] The path to the primary binary for the component. This can be
# used to indicate a fuzz_target binary built in a different phase of the
# build, e.g. in zircon.
#
# NOTE: Exactly one of sources or binary must be set.
#
# 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).
#
# dictionary (optional)
# [file] If specified, a file containing inputs, one per line, that the
# fuzzer will use to generate new mutations.
#
template("fuzz_target") {
assert(
(defined(invoker.binary) && !defined(invoker.sources)) ||
(!defined(invoker.binary) || defined(invoker.sources)),
"Exactly one of `binary` or `sources` must be defined for $target_name")
fuzz_target_name = target_name
# If not specified, generate the component binary
if (!defined(invoker.binary)) {
executable(fuzz_target_name) {
# Explicitly forward visibility, implicitly forward everything else.
# See comment in //build/config/BUILDCONFIG.gn for details on this pattern.
forward_variables_from(invoker, [ "visibility" ])
forward_variables_from(invoker,
"*",
[
"dictionary",
"options",
"visibility",
])
testonly = true
_target_type = "fuzzed_executable"
}
}
# Generate the component manifest
fuzz_target_cmx = "${target_gen_dir}/${fuzz_target_name}.cmx"
action("${fuzz_target_name}_cmx") {
script = "//build/fuzzing/gen_fuzzer_manifest.py"
outputs = [
fuzz_target_cmx,
]
args = [
"--out",
rebase_path(outputs[0]),
"--bin",
]
if (defined(invoker.binary)) {
args += [ invoker.binary ]
} else {
args += [ fuzz_target_name ]
}
if (defined(invoker.cmx)) {
args += [
"--cmx",
rebase_path(invoker.cmx),
]
}
}
# Generate data files needed at runtime
output_dictionary = "${target_gen_dir}/${fuzz_target_name}/dictionary"
if (defined(invoker.dictionary)) {
copy("${fuzz_target_name}_dictionary") {
sources = [
invoker.dictionary,
]
outputs = [
output_dictionary,
]
}
} else {
generated_file("${fuzz_target_name}_dictionary") {
contents = []
outputs = [
output_dictionary,
]
}
}
generated_file("${fuzz_target_name}_options") {
contents = []
if (defined(invoker.options)) {
contents = invoker.options
}
outputs = [
"${target_gen_dir}/${fuzz_target_name}/options",
]
}
# Create the fuzz_target component
fuchsia_component("${fuzz_target_name}_component") {
testonly = true
manifest = fuzz_target_cmx
if (defined(invoker.binary)) {
binary = invoker.binary
} else {
binary = "${fuzz_target_name}"
}
resources = [
{
path = "${target_gen_dir}/${fuzz_target_name}/dictionary"
dest = "${fuzz_target_name}/dictionary"
},
{
path = "${target_gen_dir}/${fuzz_target_name}/options"
dest = "${fuzz_target_name}/options"
},
]
deps = []
forward_variables_from(invoker, [ "visibility" ])
if (!defined(invoker.binary)) {
# deps, public_deps, and data_deps are forwarded to ${fuzz_target_name}
deps += [ ":${fuzz_target_name}" ]
} else {
forward_variables_from(invoker,
[
"deps",
"public_deps",
"data_deps",
])
}
deps += [
":${fuzz_target_name}_cmx",
":${fuzz_target_name}_dictionary",
":${fuzz_target_name}_options",
]
}
}
set_defaults("fuzz_target") {
configs = default_executable_configs +
[ "//build/fuzzing:fuzzing_build_mode_unsafe_for_production" ]
}
# Defines a package of fuzz target components
#
# The fuzz_package template is used to bundle several fuzz_targets and their
# associated data into a single Fuchsia package.
#
# Parameters
#
# targets (required)
# [list of labels] The fuzz_target() targets to include in this package.
#
# sanitizers (optional)
# [list of variants] A set of sanitizer variants. The resulting package
# will contain binaries for each sanitizer/target combination. Defaults to
# [ "asan", "ubsan" ]
#
# fuzz_host (optional)
# [boolean] Indicates whether to also build fuzzer binaries on host.
# Defaults to false.
#
# meta (optional)
# binaries (optional)
# components (optional)
# tests (optional)
# drivers (optional)
# loadable_modules (optional)
# resources (optional)
# extra (optional)
# Same meanings as in //build/package.gni
#
# deps (optional)
# public_deps (optional)
# data_deps (optional)
# testonly (optional)
# Usual GN meanings.
template("fuzz_package") {
assert(defined(invoker.targets), "targets must be defined for $target_name}")
if (defined(invoker.sanitizers)) {
sanitizers = invoker.sanitizers
} else {
sanitizers = [
"asan",
"ubsan",
]
}
# Collect the selected fuzz targets listed in this package based on the
# variants selected in args.gn and filtered by the package's list of
# supported sanitizers.
selected_targets = []
foreach(fuzz_target, invoker.targets) {
selected = false
foreach(sanitizer, sanitizers) {
foreach(selector, select_variant_canonical) {
if (!selected && selector.variant == "${sanitizer}-fuzzer" &&
((defined(selector.target_type) &&
selector.target_type == [ "fuzzed_executable" ]) ||
(defined(selector.name) && selector.name == [ fuzz_target ]) ||
(defined(selector.output_name) &&
selector.output_name == [ fuzz_target ]))) {
selected = true
selected_targets += [ fuzz_target ]
}
}
}
# When no variants are present, `selected` is unused.
not_needed([ "selected" ])
}
# Assemble the Fuchsia package
if (selected_targets != []) {
package(target_name) {
metadata = {
fuzz_spec = [
{
fuzz_package = target_name
fuzz_targets = []
foreach(selected_target, selected_targets) {
fuzz_targets += [ get_label_info(selected_target, "name") ]
}
},
]
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*", [ "fuzz_spec" ])
}
}
components = []
forward_variables_from(invoker, [ "visibility" ])
forward_variables_from(invoker,
"*",
[
"targets",
"sanitizers",
"visibility",
"fuzz_host",
])
testonly = true
foreach(selected_target, selected_targets) {
components += [ "${selected_target}_component" ]
}
}
} else {
# Dummy package for non-fuzzed builds
group(target_name) {
}
not_needed(invoker, "*")
}
# Create a (possibly empty) target for host fuzzers
group("host_${target_name}") {
testonly = true
if (defined(invoker.fuzz_host) && invoker.fuzz_host) {
deps = selected_targets
}
}
}