blob: 51f379262dce417dfbe47a52827aba8b537b22e6 [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/host.gni")
import("//build/package.gni")
import("//build/test.gni")
# TODO(aarongreen): SEC-224. Add tests to catch fuzzer building/packaging
# regressions.
# 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:
#
# 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 quoted inputs, one per line, that
# the fuzzer will use to generate new mutations.
#
template("fuzzer") {
fuzzer_name = target_name
# Generate the component binary
executable(fuzzer_name) {
forward_variables_from(invoker,
"*",
[
"dictionary",
"options",
"visibility",
])
# Explicitly forward visibility for nested scopes.
forward_variables_from(invoker, [ "visibility" ])
testonly = true
_target_type = "fuzzed_executable"
}
# Generate the fuzzer component manifest
action("${fuzzer_name}_cmx") {
script = "//build/fuzzing/gen_fuzzer_manifest.py"
outputs = [
"${target_gen_dir}/${fuzzer_name}.cmx",
]
args = [
"--out",
rebase_path(outputs[0]),
"--bin",
fuzzer_name,
]
if (defined(invoker.cmx)) {
args += [
"--cmx",
rebase_path(invoker.cmx),
]
}
}
# Generate a unit test for the fuzzer.
test("${fuzzer_name}_test") {
deps = []
forward_variables_from(invoker,
"*",
[
"dictionary",
"options",
"target_name",
"visibility",
])
# Explicitly forward visibility for nested scopes.
forward_variables_from(invoker, [ "visibility" ])
deps += [
"//src/fuzzing/cpp:fuzzer_test",
"//third_party/googletest:gtest_main",
]
}
# Generate the fuzzer test component manifest
action("${fuzzer_name}_test_cmx") {
script = "//build/fuzzing/gen_fuzzer_manifest.py"
outputs = [
"${target_gen_dir}/${fuzzer_name}_test.cmx",
]
args = [
"--test",
"--out",
rebase_path(outputs[0]),
"--bin",
"${fuzzer_name}_test",
]
}
# Generate data files needed at runtime
output_dictionary = "${target_gen_dir}/${fuzzer_name}/dictionary"
if (defined(invoker.dictionary)) {
copy("${fuzzer_name}_dictionary") {
sources = [
invoker.dictionary,
]
outputs = [
output_dictionary,
]
}
} else {
generated_file("${fuzzer_name}_dictionary") {
contents = []
outputs = [
output_dictionary,
]
}
}
generated_file("${fuzzer_name}_options") {
contents = []
if (defined(invoker.options)) {
contents = invoker.options
}
outputs = [
"${target_gen_dir}/${fuzzer_name}/options",
]
}
}
set_defaults("fuzzer") {
configs = default_executable_configs +
[ "//build/fuzzing:fuzzing_build_mode_unsafe_for_production" ]
}
# Defines a package of fuzzers
#
# The fuzzers_package template is used to bundle several fuzzers and their
# associated data into a single Fuchsia package.
#
# Parameters
#
# fuzzers (required)
# [list of labels] The fuzzer() labels to include in this package.
#
# sanitizers (optional)
# [list of variants] A set of sanitizer variants. The resulting package
# will contain binaries for each sanitizer/fuzzer combination. Defaults to
# [ "asan", "ubsan" ]
#
# fuzz_host (optional)
# [boolean] Indicates whether to also build fuzzer binaries on host.
# Defaults to false.
#
# generated_test_package (optional)
# [string] The name of a package of fuzzer tests to create.
#
# 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("fuzzers_package") {
assert(defined(invoker.fuzzers), "fuzzers must be defined for $target_name}")
package_name = target_name
if (defined(invoker.sanitizers)) {
sanitizers = invoker.sanitizers
} else {
sanitizers = [
"asan",
"ubsan",
]
}
if (defined(invoker.fuzz_host)) {
fuzz_host = invoker.fuzz_host
} else {
fuzz_host = false
}
pkg = {
meta = []
binaries = []
resources = []
deps = []
fuzzers = []
host_deps = []
host_outputs = []
tests = []
test_meta = []
test_deps = []
}
# Collect the selected fuzzers listed in this package based on the variants
# selected in args.gn and filtered by the package's list of supported
# sanitizers.
foreach(fuzzer, invoker.fuzzers) {
selected = false
fuzzer_name = get_label_info(fuzzer, "name")
fuzzer_path = get_label_info(fuzzer, "target_gen_dir")
fuzzer_label = get_label_info(fuzzer, "label_no_toolchain")
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 == [ fuzzer ]) ||
(defined(selector.output_name) &&
selector.output_name == [ fuzzer ]))) {
selected = true
# Package details.
pkg.meta += [
{
path = "${fuzzer_path}/${fuzzer_name}.cmx"
dest = "${fuzzer_name}.cmx"
},
]
pkg.binaries += [
{
name = "${fuzzer_name}"
},
]
pkg.resources += [
{
path = "${fuzzer_path}/${fuzzer_name}/dictionary"
dest = "${fuzzer_name}/dictionary"
},
{
path = "${fuzzer_path}/${fuzzer_name}/options"
dest = "${fuzzer_name}/options"
},
]
pkg.deps += [
"${fuzzer_label}",
"${fuzzer_label}_cmx",
"${fuzzer_label}_dictionary",
"${fuzzer_label}_options",
]
pkg.fuzzers += [ fuzzer_name ]
# Host fuzzers
if (fuzz_host) {
pkg.deps += [ ":host_${target_name}" ]
pkg.host_deps += [ fuzzer_label ]
pkg.host_outputs += [ fuzzer_name ]
}
}
}
}
# Fuzzer tests
pkg.tests += [
{
name = "${fuzzer_name}_test"
},
]
pkg.test_meta += [
{
path = "${fuzzer_path}/${fuzzer_name}_test.cmx"
dest = "${fuzzer_name}_test.cmx"
},
]
pkg.test_deps += [
"${fuzzer_label}_test",
"${fuzzer_label}_test_cmx",
]
not_needed([ "selected" ])
}
# Assemble the Fuchsia package
if (pkg.deps != []) {
package(target_name) {
meta = []
binaries = []
resources = []
deps = []
forward_variables_from(invoker,
"*",
[
"fuzzers",
"generated_test_package",
"metadata",
"sanitizers",
"visibility",
])
# Explicitly forward visibility for nested scopes.
forward_variables_from(invoker, [ "visibility" ])
testonly = true
meta += pkg.meta
binaries = pkg.binaries
resources = pkg.resources
deps = pkg.deps
# This metadata will be used to generate out/default/fuzzers.json
metadata = {
if (defined(invoker.metadata)) {
forward_variables_from(invoker.metadata, "*", [ "fuzz_spec" ])
}
fuzz_spec = [
{
fuzzers_package = package_name
fuzzers = pkg.fuzzers
fuzz_host = fuzz_host
},
]
}
}
# Treat host fuzzers as tools. If we get to the point of having name
# collisions, we'll need to extend `install_host_tools` to allow copying to
# specific subdirectories of `host_tools_dir`
if (fuzz_host) {
install_host_tools("host_${target_name}") {
testonly = true
deps = pkg.host_deps
outputs = pkg.host_outputs
}
}
} else {
# Dummy package for non-fuzzed builds
group(target_name) {
}
not_needed([ "fuzz_host" ])
not_needed(invoker, "*")
not_needed([
"package_name",
"fuzz_host",
])
}
# Assemble the Fuchsia test package. This uses `package` instead of
# `test_package`, as the latter only provides an inconvenient constraint on
# where the cmx file must come from.
if (defined(invoker.generated_test_package)) {
package(invoker.generated_test_package) {
forward_variables_from(invoker, [ "visibility" ])
testonly = true
meta = pkg.test_meta
tests = pkg.tests
deps = pkg.test_deps
}
}
}
# TODO(aarongreen): Complete soft transition and remove.
# These templates are the old names for `fuzzer` and `fuzzers_package`. They
# are included to allow third_party projects to transition softly.
template("fuzz_target") {
source_set(target_name) {
}
not_needed(invoker, "*")
}
set_defaults("fuzz_target") {
configs = []
}
template("fuzz_package") {
group(target_name) {
}
not_needed(invoker, "*")
}