blob: 40971834c086b4b0eeccc4a4cdc0060b1090b8dc [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:
#
# cmx (optional)
# [file] If specified, a file containing a component manifest to start from
# when generating manifests for fuzzers.
#
# 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") {
fuzzer_name = target_name
# Generate the component binary
executable(fuzzer_name) {
forward_variables_from(invoker,
"*",
[
"cmx",
"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
generated_file("$fuzzer_name.cmx") {
if (defined(invoker.cmx)) {
cmx = read_file(invoker.cmx, "json")
} else {
cmx = {
program = {
}
sandbox = {
}
}
}
contents = {
program = {
forward_variables_from(cmx.program, "*")
binary = "bin/${fuzzer_name}"
}
sandbox = {
services = []
features = []
forward_variables_from(cmx.sandbox, "*")
services += [ "fuchsia.process.Launcher" ]
features += [
"isolated-persistent-storage",
"deprecated-ambient-replace-as-executable"
]
}
forward_variables_from(cmx,
[
"dev",
"runner",
"facets",
])
}
outputs = [
"$target_gen_dir/$target_name",
]
output_conversion = "json"
}
# 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/lib/fuzzing/cpp:fuzzer_test",
"//third_party/googletest:gtest_main",
]
}
# Generate the fuzzer test component manifest
generated_file("${fuzzer_name}_test.cmx") {
contents = {
program = {
binary = "test/${fuzzer_name}_test"
}
sandbox = {
services = [ "fuchsia.process.Launcher" ]
features = [
"isolated-persistent-storage",
"deprecated-ambient-replace-as-executable"
]
}
}
outputs = [
"$target_gen_dir/$target_name",
]
output_conversion = "json"
}
# 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}")
if (defined(invoker.sanitizers)) {
sanitizers = invoker.sanitizers
} else {
sanitizers = [
"asan",
"ubsan",
]
}
pkg = {
name = target_name
meta = []
binaries = []
resources = []
deps = []
fuzzers = []
host = defined(invoker.fuzz_host) && invoker.fuzz_host
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
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" ])
}
if (pkg.deps == []) {
# Dummy package for non-fuzzed builds
group(target_name) {
}
not_needed(invoker, "*")
} else {
if (pkg.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`
install_host_tools("host_${target_name}") {
testonly = true
deps = pkg.host_deps
outputs = pkg.host_outputs
}
pkg.deps += [ ":host_${pkg.name}" ]
}
# Assemble the Fuchsia package
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 = pkg.name
fuzzers = pkg.fuzzers
fuzz_host = pkg.host
},
]
}
}
}
# TODO(aarongreen): Soft transition from the optional $generated_test_package
# to the required $test_package_name. Once template consumers are updated;
# drop support for $generated_test_package.
if (defined(invoker.test_package_name)) {
test_package_name = invoker.test_package_name
} else if (defined(invoker.generated_test_package)) {
test_package_name = invoker.generated_test_package
} else {
test_package_name = "${pkg.name}_tests"
}
# Include host unit tests as appropriate
if (pkg.host) {
group("host_$test_package_name") {
visibility = [ ":*" ]
testonly = true
deps = []
foreach(test_dep, pkg.test_deps) {
deps += [ "$test_dep($host_toolchain)" ]
}
}
pkg.test_deps += [ ":host_$test_package_name" ]
}
# 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.
package(test_package_name) {
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, "*")
}