blob: d37f35bae7b382a5c76315c33a97b85288939938 [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")
# Declare a fuzzed executable target.
#
# Creates an instrumented executable file for fuzzing with a sanitizer. Do not
# use directly; instead, use `fuzz_target` with an associated `fuzz_package`
# specifying the supported sanitizers.
#
# Takes all the same parameters as executable().
template("_fuzzed_executable") {
executable(target_name) {
_target_type = "fuzzed_executable"
# 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,
"*",
[
"visibility",
"_target_type",
])
}
}
# Declares a resource needed by the fuzzer.
#
# Creates a (potentially empty) resource file for use by a `fuzz_target`. Do
# not use directly; instead, use `fuzz_target` with an associated
# `fuzz_package` specifying the supported sanitizers.
#
# Parameters:
#
# fuzz_name (required)
# [string] Name of the associated `fuzz_target`.
#
# resource (required)
# [string] Base file name of the resource to produce.
#
# script (optional)
# [file] Script that produces the resources. Defaults to
# //build/fuzzing/gen_fuzzer_resource.py
#
# args (optional)
# [list of strings] Additional arguments to append to the script command
# line.
#
template("_fuzz_resource") {
assert(defined(invoker.fuzz_name),
"`fuzz_name` must be defined for $target_name")
assert(defined(invoker.resource),
"`resource` must be defined for $target_name")
action(target_name) {
if (defined(invoker.script)) {
script = invoker.script
} else {
script = "//build/fuzzing/gen_fuzzer_resource.py"
}
output = "${target_gen_dir}/${invoker.fuzz_name}/${invoker.resource}"
outputs = [
output,
]
args = [
"--out",
rebase_path(output),
]
if (defined(invoker.args)) {
args += invoker.args
}
}
}
# Defines a fuzz target binary
#
# The fuzz_target template is used to create binaries which leverage LLVM's
# libFuzzer to perform fuzz testing.
#
# Parameters
#
# 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.
#
# corpora (optional)
# [list of strings] One or more locations from which to get a fuzzing
# corpus, e.g. a project path to a directory of artifacts, a project
# path to a CIPD ensure file, or a GCS URL.
#
# sources (optional)
# [list of files] The C++ sources required to build the fuzzer binary. With
# the exception of the "zircon_fuzzers" targets, this list must be present
# and include a file defining `LLVMFuzzerTestOneInput`. It will typically
# be a single file (with deps including the software under test).
#
template("fuzz_target") {
assert(defined(invoker.sources), "`sources` must be defined for $target_name")
_fuzzed_executable(target_name) {
forward_variables_from(invoker,
"*",
[
"corpora",
"dictionary",
"options",
"output_name",
])
testonly = true
}
# The following rules ensure the fuzzer resources are present (even if empty)
# and in known locations. This is needed as the fuzz_package has no way of
# knowing the original file names when building the package manifest. These
# files only need to be written once, so only do it in the base toolchain.
if (current_toolchain == toolchain_variant.base) {
fuzz_name = target_name
# Copy the corpora available for the fuzzer
_fuzz_resource("${fuzz_name}_corpora") {
resource = "corpora"
if (defined(invoker.corpora)) {
args = invoker.corpora
}
}
# Copy the fuzzer dictionary
_fuzz_resource("${fuzz_name}_dictionary") {
resource = "dictionary"
if (defined(invoker.dictionary)) {
args = read_file(invoker.dictionary, "list lines")
}
}
# Copy the options the fuzzer should be invoked with
_fuzz_resource("${fuzz_name}_options") {
resource = "options"
if (defined(invoker.options)) {
args = invoker.options
}
}
# Create a component manifest
_fuzz_resource("${fuzz_name}_cmx") {
script = "//build/fuzzing/gen_fuzzer_manifest.py"
resource = "${fuzz_name}.cmx"
args = [
"--bin",
"${fuzz_name}",
]
if (defined(invoker.cmx)) {
args += [
"--cmx",
rebase_path(invoker.cmx),
]
}
}
} else {
not_needed(invoker, "*")
}
}
set_defaults("fuzz_target") {
configs = default_executable_configs +
[ "//build/fuzzing:fuzzing_build_mode_unsafe_for_production" ]
}
# Defines a package of fuzz target binaries
#
# 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 (required)
# [list of variants] A set of sanitizer variants. The resulting package
# will contain binaries for each sanitizer/target combination.
#
# binaries (optional)
# data_deps (optional)
# deps (optional)
# public_deps (optional)
# extra (optional)
# loadable_modules (optional)
# meta (optional)
# resources (optional)
# Passed through to the normal package template.
#
# fuzz_host (optional)
# [boolean] Indicates whether to also build fuzzer binaries on host.
# Defaults to false.
#
# omit_binaries (optional)
# [bool] If true, indicates the fuzz target binaries will be provided by
# a non-GN build, e.g. the Zircon build. The package will contain the
# resources needed by the fuzzer, but no binaries or component manifests.
# Defaults to false.
#
template("fuzz_package") {
assert(defined(invoker.targets), "targets must be defined for $target_name}")
assert(defined(invoker.sanitizers),
"sanitizers must be defined for $target_name")
# Only assemble the package once; handle the specific sanitizers in the loop below
if (current_toolchain == toolchain_variant.base) {
fuzz = {
binaries = []
deps = []
resources = []
meta = []
host = false
host_binaries = []
omit_binaries = false
forward_variables_from(invoker,
[
"binaries",
"data_deps",
"deps",
"public_deps",
"extra",
"loadable_modules",
"meta",
"resources",
"targets",
"sanitizers",
])
}
if (defined(invoker.fuzz_host)) {
fuzz.host = invoker.fuzz_host
}
if (defined(invoker.omit_binaries)) {
fuzz.omit_binaries = invoker.omit_binaries
}
# It's possible (although unusual) that targets could be empty, e.g. a placeholder package
if (fuzz.targets == []) {
not_needed(fuzz, [ "sanitizers" ])
}
foreach(fuzz_target, fuzz.targets) {
fuzz_name = get_label_info(fuzz_target, "name")
# Find the executable variant for the sanitized fuzzer
selected = false
sanitized_target = ""
host_target = ""
foreach(sanitizer, fuzz.sanitizers) {
if (!selected) {
foreach(selector, select_variant_canonical) {
if (selector.variant == "${sanitizer}-fuzzer") {
if (defined(selector.target_type)) {
selector_target_type = []
selector_target_type = selector.target_type
if (selector_target_type[0] == "fuzzed_executable") {
selected = true
}
}
if (defined(selector.name)) {
selector_name = []
selector_name = selector.name
if (selector_name[0] == fuzz_name) {
selected = true
}
}
if (defined(selector.output_name)) {
selector_output_name = []
selector_output_name = selector.output_name
if (selector_output_name[0] == fuzz_name) {
selected = true
}
}
if (selected) {
sanitized_target = "${fuzz_target}(${toolchain_variant.base}-${sanitizer}-fuzzer)"
host_target =
"${fuzz_target}(${host_toolchain}-${sanitizer}-fuzzer)"
}
}
}
}
}
# If enabled, add fuzz target to package
if (sanitized_target != "") {
fuzz_label = get_label_info(sanitized_target, "label_no_toolchain")
fuzz_gen_dir =
get_label_info(fuzz_target, "target_gen_dir") + "/${fuzz_name}"
if (!fuzz.omit_binaries) {
fuzz.deps += [ sanitized_target ]
fuzz_out_dir = get_label_info(sanitized_target, "root_out_dir")
fuzz.binaries += [
{
name = fuzz_name
source = "${fuzz_out_dir}/${fuzz_name}"
dest = fuzz_name
},
]
fuzz.deps += [ "${fuzz_label}_cmx(${toolchain_variant.base})" ]
fuzz.meta += [
{
path = "${fuzz_gen_dir}/${fuzz_name}.cmx"
dest = "${fuzz_name}.cmx"
},
]
fuzz.host_binaries += [ "$host_target" ]
}
fuzz.deps += [
"${fuzz_label}_corpora(${toolchain_variant.base})",
"${fuzz_label}_dictionary(${toolchain_variant.base})",
"${fuzz_label}_options(${toolchain_variant.base})",
]
fuzz.resources += [
{
path = "${fuzz_gen_dir}/corpora"
dest = "${fuzz_name}/corpora"
},
{
path = "${fuzz_gen_dir}/dictionary"
dest = "${fuzz_name}/dictionary"
},
{
path = "${fuzz_gen_dir}/options"
dest = "${fuzz_name}/options"
},
]
} else {
not_needed([
"fuzz_name",
"host_target",
])
not_needed(fuzz, "*")
}
}
group("host_${target_name}") {
testonly = true
if (fuzz.host) {
deps = fuzz.host_binaries
}
}
# Build the actual package
package(target_name) {
testonly = true
forward_variables_from(fuzz,
"*",
[
"targets",
"sanitizers",
"host",
"host_binaries",
"omit_binaries",
])
}
} else {
not_needed(invoker, "*")
}
}