| # 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 |
| } |
| } |