blob: 960793ed1bee7935d2b1ebb44fd88830ef7cd4ab [file] [log] [blame]
# Copyright 2020 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")
# TODO(fxbug.dev/48118): Add LSan
_cpp_sanitizers = [
"asan",
"ubsan",
]
# Currently, Go doesn't have any *San specific callbacks beyond those in the dynamic linker. Still,
# all of the C/C++ sanitizers should link and run.
_go_sanitizers = _cpp_sanitizers
_rust_sanitizers = [ "asan" ]
# Defines a package of fuzzers
#
# The `fuzzers_package` template is used to bundle several fuzzers and their associated data into a
# single Fuchsia package. See //examples/fuzzer/BUILD.gn for examples.
#
# Parameters
# fuzz_host (optional)
# [boolean] Indicates whether to also build fuzzer binaries on host. Defaults to false.
#
# host_only (optional)
# [boolean] Indicates whether to skip the Fuchsia fuzzers package. Implies `fuzz_host`.
# Defaults to false.
#
# fuzzers (optional)
# cpp_fuzzers (optional)
# go_fuzzers (optional)
# rust_fuzzers (optional)
# [list] A list of fuzzers for targets in the indicated language. Each list is asscoiated with
# the corresponding list of sanitizers, e.g. each label in `cpp_fuzzers` is built as a fuzzer
# when the toolchain variant selected for that label matches an element of `_cpp_sanitizers`.
# `fuzzers` (with no language prefix) imples C++, and is folded into `cpp_fuzzers`.
#
# Each element of `fuzzers` is either a label or a scope. Label elements act as "shortcuts"
# that use default values for each field in a scope element. The scope elements can include
# the following fields:
#
# label (required)
# [label] The fuzzer() target to include.
#
# name (optional)
# output_name (optional)
# [string] Same as for fuzzer(), and should match the value(s) for the associated `label`.
# For label elements, these are both set to simple name of the label.
#
# sanitizers (optional)
# [list of strings] Overrides the default, per-language sanitizers. This should be omitted
# unless necessary.
#
# fuzz_host (optional)
# [boolean] Overrides the value of `fuzz_host` in the outer scope.
#
# host_only (optional)
# [boolean] Overrides the value of `host_only` in the outer scope.
#
# fuzzers_manifest (optional)
# [scope] Specifies how to read fuzzer names from a manifest file. This parameter is useful for
# including fuzzers from another build, e.g. pre-unification Zircon fuzzers. The scope has two
# fields:
#
# target (required)
# [label] The GN target that produces the manifest.
#
# output (required)
# [path] The path to the generated manifest file.
#
# 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)
# Usual GN meanings.
template("fuzzers_package") {
# This target will create a group with up to three parts: a package of fuzzers and/or fuzzer
# tests, a set of host fuzzers treated as host tools, and a group of host fuzzer tests. Which
# fuzzers are included in each part depends of the associated fuzzer profile and current
# toolchain variant.
group_target_name = target_name
group_deps = []
# Step 1: Gather up lists of fuzzers by language, and associated them with that language's
# sanitizers.
by_language = []
invoker_fuzz_host = defined(invoker.fuzz_host) && invoker.fuzz_host
invoker_host_only = defined(invoker.host_only) && invoker.host_only
if (defined(invoker.fuzzers)) {
by_language += [
{
fuzzers = invoker.fuzzers
fuzz_host = invoker_fuzz_host
host_only = invoker_host_only
sanitizers = _cpp_sanitizers
},
]
}
if (defined(invoker.cpp_fuzzers)) {
by_language += [
{
fuzzers = invoker.cpp_fuzzers
fuzz_host = invoker_fuzz_host
host_only = invoker_host_only
sanitizers = _cpp_sanitizers
},
]
}
if (defined(invoker.go_fuzzers)) {
not_needed([
"invoker_fuzz_host",
"invoker_host_only",
])
by_language += [
{
fuzzers = invoker.go_fuzzers
# TODO(fxbug.dev/44551): go host fuzzers fail to link on mac-x64
fuzz_host = false
host_only = false
sanitizers = _go_sanitizers
},
]
}
if (defined(invoker.rust_fuzzers)) {
not_needed([
"invoker_fuzz_host",
"invoker_host_only",
])
by_language += [
{
fuzzers = invoker.rust_fuzzers
# TODO(fxbug.dev/48624): rustc segfaults when building hostlibs.
fuzz_host = false
host_only = false
sanitizers = _rust_sanitizers
},
]
}
assert(by_language != [], "no fuzzers specified")
# Step 2: "Normalize" the fuzzers by making each one into a scope with the same fields.
normalized = []
foreach(language, by_language) {
foreach(fuzzer, language.fuzzers) {
if (fuzzer == "$fuzzer") {
fuzzer = {
label = fuzzer
}
} else {
assert(defined(fuzzer.label), "missing fuzzer label")
}
normalized += [
{
# Convert labels to scopes, if needed
label = get_label_info(fuzzer.label, "label_no_toolchain")
# Get the name early in order to set the default `output_name`.
if (defined(fuzzer.name)) {
name = fuzzer.name
} else {
name = get_label_info(label, "name")
}
# Set defaults
output_name = name
forward_variables_from(language, "*", [ "fuzzers" ])
# Allow overrides
forward_variables_from(fuzzer,
"*",
[
"label",
"name",
])
},
]
}
}
# Step 3: Assign each fuzzer to one or more of the following by matching the fuzzer scope against
# the currently selected toolchain variants:
# - A fuzzers package, either as binaries or unit tests
# - A set of host fuzzer tools
# - A set of host unit tests for the host fuzzer tools
fuzzed = []
tested = []
host_fuzzed = []
host_tested = []
foreach(fuzzer, normalized) {
fuzz = false
# Compare each selected variant with a profile's supported sanitizers. Unselected fuzzers are
# built as uninstrumented unit tests instead; see `test("${fuzzer_name}_test")` above.
foreach(selector, select_variant_canonical) {
foreach(sanitizer, fuzzer.sanitizers) {
if (!fuzz && selector.variant == "${sanitizer}-fuzzer") {
fuzz =
(defined(selector.target_type) &&
selector.target_type == [ "fuzzed_executable" ]) ||
(defined(selector.output_name) &&
selector.output_name == [ fuzzer.output_name ]) ||
(defined(selector.label) && selector.label == [ fuzzer.label ]) ||
(defined(selector.name) && selector.name == [ fuzzer.name ])
}
}
}
# Add the fuzzers and tests from this profile to the aggregated lists.
if (!fuzzer.host_only) {
if (fuzz) {
fuzzed += [ fuzzer ]
} else {
tested += [ fuzzer ]
}
}
if (fuzzer.fuzz_host || fuzzer.host_only) {
if (fuzz) {
host_fuzzed += [ fuzzer ]
} else {
host_tested += [ fuzzer ]
}
}
}
# Fuzzers imported via manifest are always selected.
forward_variables_from(invoker, [ "fuzzers_manifest" ])
prebuilt_fuzzed = []
if (defined(fuzzers_manifest)) {
prebuilt_fuzzed += read_file(fuzzers_manifest.output, "json")
}
# Step 4: Assemble a Fuchsia package of fuzzers and/or unit tests. Many of the package parameter
# values need to match the '*_target' and '*_output' values in the `fuzzer` template above.
# Ideally this would use `get_target_outputs` to only need the former, but fuzzers and packages
# are not required to be defined in the same GN file.
if (fuzzed != [] || prebuilt_fuzzed != [] || tested != []) {
package_target_name = target_name + "_pkg"
package(package_target_name) {
package_name = group_target_name
testonly = true
visibility = [ ":*" ]
# Allow callers to set most package parameters.
meta = []
binaries = []
tests = []
resources = []
extra = []
deps = []
forward_variables_from(invoker,
"*",
[
"fuzzers",
"fuzzers_manifest",
"fuzzer_profiles",
"metadata",
"sanitizers",
"testonly",
"visibility",
])
# Add selected fuzzers.
foreach(fuzzer, fuzzed) {
fuzzer_path = get_label_info(fuzzer.label, "target_gen_dir")
meta += [
{
path = "${fuzzer_path}/${fuzzer.output_name}.cmx"
dest = "${fuzzer.output_name}.cmx"
},
]
binaries += [
{
name = "${fuzzer.output_name}"
},
]
extra += [ "${fuzzer_path}/${fuzzer.output_name}/corpus.manifest" ]
resources += [
{
path = "${fuzzer_path}/${fuzzer.output_name}/dictionary"
dest = "${fuzzer.output_name}/dictionary"
},
{
path = "${fuzzer_path}/${fuzzer.output_name}/options"
dest = "${fuzzer.output_name}/options"
},
]
deps += [
"${fuzzer.label}",
"${fuzzer.label}_cmx",
"${fuzzer.label}_corpus",
"${fuzzer.label}_dictionary",
"${fuzzer.label}_options",
]
}
# Add fuzzer tests for those not selected.
foreach(fuzzer, tested) {
fuzzer_path = get_label_info(fuzzer.label, "target_gen_dir")
meta += [
{
path = "${fuzzer_path}/${fuzzer.output_name}_test.cmx"
dest = "${fuzzer.output_name}_test.cmx"
},
]
tests += [
{
name = "${fuzzer.output_name}_test"
# Fuzzers may produce errors so long as they do not crash.
log_settings = {
max_severity = "ERROR"
}
},
]
extra += [ "${fuzzer_path}/${fuzzer.output_name}/corpus.manifest" ]
deps += [
"${fuzzer.label}_corpus",
"${fuzzer.label}_test",
"${fuzzer.label}_test_cmx",
]
}
if (defined(fuzzers_manifest)) {
deps += [ fuzzers_manifest.target ]
extra += get_target_outputs(fuzzers_manifest.target)
}
# 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 = []
foreach(fuzzer, fuzzed + tested + prebuilt_fuzzed) {
fuzz_spec += [
{
label = fuzzer.label
package = package_name
package_url = "fuchsia-pkg://fuchsia.com/$package_name"
},
]
}
}
}
group_deps += [ ":$package_target_name" ]
}
# Step 5: 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 (host_fuzzed != []) {
host_fuzzers_target_name = target_name + "_host"
install_host_tools(host_fuzzers_target_name) {
testonly = true
visibility = [ ":*" ]
deps = []
outputs = []
foreach(fuzzer, host_fuzzed) {
deps += [
"${fuzzer.label}",
"${fuzzer.label}_corpus",
]
outputs += [ fuzzer.output_name ]
}
}
group_deps += [ ":$host_fuzzers_target_name" ]
}
# Step 6: For host fuzzer tests, ensure the deps build with the correct toolchain.
if (host_tested != []) {
host_tests_target_name = target_name + "_host_tests"
group(host_tests_target_name) {
testonly = true
visibility = [ ":*" ]
deps = []
foreach(fuzzer, host_tested) {
deps += [ "${fuzzer.label}_test($host_toolchain)" ]
}
}
group_deps += [ ":$host_tests_target_name" ]
}
# Step 7: Include everything in the group
group(group_target_name) {
forward_variables_from(invoker, [ "visibility" ])
testonly = true
deps = group_deps
}
}