blob: a5cb42f77231d2cf15cc3ed2d8bc67ce5846ddb7 [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/components.gni")
import("//build/host.gni")
import("//build/testing/test_spec.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 `fuzzer_package` template is used to bundle several fuzzers and their associated data into a
# single Fuchsia package. See //examples/fuzzers/BUILD.gn for examples.
#
# Parameters
#
# package_name (optional)
# [string] The name of the package. Defaults to the target name.
#
# 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`.
#
# 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] Indicates whether to also build fuzzer binaries on host. Defaults to false.
#
# host_only (optional)
# [boolean] Indicates whether to omit this fuzzer from the Fuchsia fuzzers package.
# Implies `fuzz_host`. Defaults to false.
#
# visibility (optional)
# [list] Usual meaning.
#
# TODO(fxbug.dev/60168): Deprecated parameters. These will be removed when consumers have updated.
#
# fuzzers (optional)
# [list] Same as `cpp_fuzzers`.
#
# fuzz_host (optional)
# [boolean] Sets `fuzz_host` for all fuzzer scopes, unless overridden.
#
# host_only (optional)
# [boolean] Sets `host_only` for all fuzzer scopes, unless overridden.
#
template("fuzzer_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_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
# TODO(fxbug.dev/60168): Update consumers to use `cpp_fuzzers`, then drop `fuzzers`.
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")
}
dir = get_label_info(label, "dir")
# 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.
#
# IMPORTANT: Keep this in sync with the selection mechanism implemented in the variant_target()
# template defined in //build/config/BUILDCONFIG.gn
fuzz = false
foreach(selector, select_variant_canonical) {
if (!fuzz) {
foreach(sanitizer, fuzzer.sanitizers) {
if (!fuzz && selector.variant == "${sanitizer}-fuzzer") {
if (!fuzz && defined(selector.target_type)) {
foreach(candidate, selector.target_type) {
if (candidate == "fuzzed_executable") {
fuzz = true
}
}
}
if (!fuzz && defined(selector.output_name)) {
foreach(candidate, selector.output_name) {
if (candidate == fuzzer.output_name) {
fuzz = true
}
}
}
if (!fuzz && defined(selector.label)) {
foreach(candidate, selector.label) {
if (candidate == fuzzer.label) {
fuzz = true
}
}
}
if (!fuzz && defined(selector.name)) {
foreach(candidate, selector.name) {
if (candidate == fuzzer.name) {
fuzz = true
}
}
}
if (!fuzz && defined(selector.dir)) {
foreach(candidate, selector.dir) {
if (candidate == fuzzer.dir) {
fuzz = true
}
}
}
}
}
}
}
# 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 ]
}
}
}
# 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.
# TODO(fxbug.dev/60168): Update consumers, then drop this automatic conversion.
if (fuzzed != [] || tested != []) {
if (defined(invoker.package_name)) {
_package_name = string_replace(invoker.package_name, "_", "-")
} else {
_package_name = string_replace(invoker.target_name, "_", "-")
}
_package_url = "fuchsia-pkg://fuchsia.com/$_package_name"
package_target_name = target_name + "-pkg"
test_deps = []
foreach(fuzzer, tested) {
test_spec_target = "${target_name}_${fuzzer.name}_test_spec"
test_spec(test_spec_target) {
target = get_label_info(fuzzer.label, "label_with_toolchain")
build_rule = "fuzzer_package"
package_url = "$_package_url#meta/${fuzzer.output_name}_test.cm"
package_label =
get_label_info(":${invoker.target_name}", "label_with_toolchain")
package_manifests = [ rebase_path(
"$target_out_dir/$package_target_name/package_manifest.json",
root_build_dir) ]
if (is_fuchsia) {
# Fuzzer tests may produce errors so long as they do not crash.
log_settings = {
max_severity = "ERROR"
}
}
}
test_deps += [ ":$test_spec_target" ]
}
# The component names in this section MUST match those in `fuzzer`.
fuchsia_package(package_target_name) {
testonly = true
visibility = [ ":*" ]
package_name = _package_name
deps = test_deps
foreach(fuzzer, tested) {
deps += [ "${fuzzer.label}_test_component" ]
}
foreach(fuzzer, fuzzed) {
deps += [ "${fuzzer.label}_component" ]
}
}
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}_executable" ]
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_executable($host_toolchain)" ]
}
}
group_deps += [ ":$host_tests_target_name" ]
}
# Step 7: Include everything in the group
group(target_name) {
testonly = true
forward_variables_from(invoker, [ "visibility" ])
deps = group_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 = []
foreach(fuzzer, fuzzed + tested) {
fuzz_spec += [
{
label = fuzzer.label
package = _package_name
package_url = _package_url
},
]
}
}
}
}
# TODO(fxbug.dev/60168): Update consumers to use `fuzzer_package` instead.
template("fuzzers_package") {
fuzzer_package(target_name) {
forward_variables_from(invoker, "*")
}
}