| # 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") |
| |
| # TODO(aarongreen): SEC-224. Add tests to catch fuzzer building/packaging |
| # regressions. |
| |
| # Defines a fuzz target component |
| # |
| # The fuzz_target template is used to create components containing binaries |
| # which leverage LLVM's libFuzzer to perform fuzz testing. |
| # |
| # Parameters |
| # |
| # sources (optional) |
| # Usual GN meaning. If present, the list must include a file defining |
| # `LLVMFuzzerTestOneInput`. |
| # |
| # binary (optional) |
| # [path] The path to the primary binary for the component. This can be |
| # used to indicate a fuzz_target binary built in a different phase of the |
| # build, e.g. in zircon. |
| # |
| # NOTE: Exactly one of sources or binary must be set. |
| # |
| # 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. |
| # |
| template("fuzz_target") { |
| assert( |
| (defined(invoker.binary) && !defined(invoker.sources)) || |
| (!defined(invoker.binary) || defined(invoker.sources)), |
| "Exactly one of `binary` or `sources` must be defined for $target_name") |
| fuzz_target_name = target_name |
| |
| # If not specified, generate the component binary |
| if (!defined(invoker.binary)) { |
| executable(fuzz_target_name) { |
| # 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, |
| "*", |
| [ |
| "dictionary", |
| "options", |
| "visibility", |
| ]) |
| testonly = true |
| _target_type = "fuzzed_executable" |
| } |
| } |
| |
| # Generate the component manifest |
| fuzz_target_cmx = "${target_gen_dir}/${fuzz_target_name}.cmx" |
| action("${fuzz_target_name}_cmx") { |
| script = "//build/fuzzing/gen_fuzzer_manifest.py" |
| outputs = [ |
| fuzz_target_cmx, |
| ] |
| args = [ |
| "--out", |
| rebase_path(outputs[0]), |
| "--bin", |
| ] |
| if (defined(invoker.binary)) { |
| args += [ invoker.binary ] |
| } else { |
| args += [ fuzz_target_name ] |
| } |
| if (defined(invoker.cmx)) { |
| args += [ |
| "--cmx", |
| rebase_path(invoker.cmx), |
| ] |
| } |
| } |
| |
| # Generate data files needed at runtime |
| output_dictionary = "${target_gen_dir}/${fuzz_target_name}/dictionary" |
| if (defined(invoker.dictionary)) { |
| copy("${fuzz_target_name}_dictionary") { |
| sources = [ |
| invoker.dictionary, |
| ] |
| outputs = [ |
| output_dictionary, |
| ] |
| } |
| } else { |
| generated_file("${fuzz_target_name}_dictionary") { |
| contents = [] |
| outputs = [ |
| output_dictionary, |
| ] |
| } |
| } |
| generated_file("${fuzz_target_name}_options") { |
| contents = [] |
| if (defined(invoker.options)) { |
| contents = invoker.options |
| } |
| outputs = [ |
| "${target_gen_dir}/${fuzz_target_name}/options", |
| ] |
| } |
| |
| # Create the fuzz_target component |
| fuchsia_component("${fuzz_target_name}_component") { |
| testonly = true |
| manifest = fuzz_target_cmx |
| if (defined(invoker.binary)) { |
| binary = invoker.binary |
| } else { |
| binary = "${fuzz_target_name}" |
| } |
| resources = [ |
| { |
| path = "${target_gen_dir}/${fuzz_target_name}/dictionary" |
| dest = "${fuzz_target_name}/dictionary" |
| }, |
| { |
| path = "${target_gen_dir}/${fuzz_target_name}/options" |
| dest = "${fuzz_target_name}/options" |
| }, |
| ] |
| deps = [] |
| forward_variables_from(invoker, [ "visibility" ]) |
| if (!defined(invoker.binary)) { |
| # deps, public_deps, and data_deps are forwarded to ${fuzz_target_name} |
| deps += [ ":${fuzz_target_name}" ] |
| } else { |
| forward_variables_from(invoker, |
| [ |
| "deps", |
| "public_deps", |
| "data_deps", |
| ]) |
| } |
| deps += [ |
| ":${fuzz_target_name}_cmx", |
| ":${fuzz_target_name}_dictionary", |
| ":${fuzz_target_name}_options", |
| ] |
| } |
| } |
| |
| set_defaults("fuzz_target") { |
| configs = default_executable_configs + |
| [ "//build/fuzzing:fuzzing_build_mode_unsafe_for_production" ] |
| } |
| |
| # Defines a package of fuzz target components |
| # |
| # 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 (optional) |
| # [list of variants] A set of sanitizer variants. The resulting package |
| # will contain binaries for each sanitizer/target combination. Defaults to |
| # [ "asan", "ubsan" ] |
| # |
| # fuzz_host (optional) |
| # [boolean] Indicates whether to also build fuzzer binaries on host. |
| # Defaults to false. |
| # |
| # 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("fuzz_package") { |
| assert(defined(invoker.targets), "targets must be defined for $target_name}") |
| |
| if (defined(invoker.sanitizers)) { |
| sanitizers = invoker.sanitizers |
| } else { |
| sanitizers = [ |
| "asan", |
| "ubsan", |
| ] |
| } |
| |
| # Collect the selected fuzz targets listed in this package based on the |
| # variants selected in args.gn and filtered by the package's list of |
| # supported sanitizers. |
| selected_targets = [] |
| foreach(fuzz_target, invoker.targets) { |
| selected = false |
| 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 == [ fuzz_target ]) || |
| (defined(selector.output_name) && |
| selector.output_name == [ fuzz_target ]))) { |
| selected = true |
| selected_targets += [ fuzz_target ] |
| } |
| } |
| } |
| |
| # When no variants are present, `selected` is unused. |
| not_needed([ "selected" ]) |
| } |
| |
| # Assemble the Fuchsia package |
| if (selected_targets != []) { |
| package(target_name) { |
| metadata = { |
| fuzz_spec = [ |
| { |
| fuzz_package = target_name |
| fuzz_targets = [] |
| foreach(selected_target, selected_targets) { |
| fuzz_targets += [ get_label_info(selected_target, "name") ] |
| } |
| }, |
| ] |
| if (defined(invoker.metadata)) { |
| forward_variables_from(invoker.metadata, "*", [ "fuzz_spec" ]) |
| } |
| } |
| components = [] |
| forward_variables_from(invoker, [ "visibility" ]) |
| forward_variables_from(invoker, |
| "*", |
| [ |
| "targets", |
| "sanitizers", |
| "visibility", |
| "fuzz_host", |
| ]) |
| testonly = true |
| foreach(selected_target, selected_targets) { |
| components += [ "${selected_target}_component" ] |
| } |
| } |
| } else { |
| # Dummy package for non-fuzzed builds |
| group(target_name) { |
| } |
| not_needed(invoker, "*") |
| } |
| |
| # Create a (possibly empty) target for host fuzzers |
| group("host_${target_name}") { |
| testonly = true |
| if (defined(invoker.fuzz_host) && invoker.fuzz_host) { |
| deps = selected_targets |
| } |
| } |
| } |