# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//mojo/public/tools/bindings/mojom.gni")
import("//testing/libfuzzer/fuzzer_test.gni")
import("//third_party/jinja2/jinja2.gni")
import("//third_party/protobuf/proto_library.gni")
# Generate a MojoLPM-based fuzzer test.
#
# This rule will copy the proto file defining the fuzzer testcases into the
# output directory so that it can be compiled against the generated MojoLPM
# protos. It then adds a rule to compile that proto, and finally a fuzzer
# test target which uses the compiled proto.
#
# Optionally it can also handle converting a seed corpus of text protos into
# a binary corpus as part of the build.
#
# Parameters:
# sources
# List of source .cc files to compile.
#
# deps
# List of dependencies to compile this target.
#
# proto_source
# Single source .proto file defining the structure of a testcase.
#
# proto_deps
# List of additional dependencies for compiling proto_source.
#
# proto_in_dir
# See documentation at //third_party/protobuf/proto_library.gni.
#
# exclude_main
# See documentation at //testing/libfuzzer/fuzzer_test.gni.
#
# proto_out_dir (optional)
# See documentation at //third_party/protobuf/proto_library.gni.
#
# testcase_proto_kind (optional, required if seed_corpus_sources provided)
# Name of proto message type representing a testcase.
#
# seed_corpus_sources (optional)
# List of source .textproto files used to build a seed corpus.
#
# Example:
# mojolpm_fuzzer_test("foo_mojolpm_fuzzer") {
# sources = [ "foo_mojolpm_fuzzer.cc" ]
#
# deps = [
# "//content/browser/foo:foo_mojolpm_fuzzer_proto",
# "//content/browser:for_content_tests",
# "//content/public/browser:browser_sources",
# "//content/test:test_support",
# "//mojo/core/embedder",
# "//mojo/public/tools/fuzzers:mojolpm",
# "//third_party/libprotobuf-mutator",
# ]
#
# proto_deps = [
# "//content/browser/bar/mojom:mojom_mojolpm","
# ]
#
# testcase_proto = "foo_mojolpm_fuzzer.proto"
# testcase_proto_kind = "foo.mojolpm.proto.Testcase"
#
# seed_corpus_sources = [
# "foo_mojolpm_fuzzer_corpus/seed_one.textproto",
# "foo_mojolpm_fuzzer_corpus/seed_two.textproto",
# ]
# }
template("mojolpm_fuzzer_test") {
assert(defined(invoker.sources) && defined(invoker.proto_source),
"\"sources\" and \"proto_source\" must be defined for $target_name")
assert(
!defined(invoker.seed_corpus_sources) ||
defined(invoker.testcase_proto_kind),
"\"testcase_proto_kind\" must be defined for $target_name since \"seed_corpus_sources\" is defined.")
if (enable_mojom_fuzzer) {
proto_target_name = "${target_name}_proto"
proto_library(proto_target_name) {
# Work relative to src (//) instead of (by default) the BUILD file.
if (defined(invoker.proto_in_dir)) {
proto_in_dir = invoker.proto_in_dir
} else {
proto_in_dir = "//"
}
if (defined(invoker.proto_out_dir)) {
proto_out_dir = invoker.proto_out_dir
}
sources = [ invoker.proto_source ]
generate_python = false
proto_deps = []
import_dirs = [
root_gen_dir,
"//",
]
link_deps = []
if (defined(invoker.proto_deps)) {
proto_deps += invoker.proto_deps
link_deps += invoker.proto_deps
}
testonly = true
}
if (defined(invoker.seed_corpus_sources)) {
protoc_convert_target_name = "${target_name}_protoc_convert"
seed_corpus_path = "${target_gen_dir}/${target_name}_seed_corpus"
protoc_convert(protoc_convert_target_name) {
sources = invoker.seed_corpus_sources
inputs = [ invoker.proto_source ]
output_pattern = "${seed_corpus_path}/{{source_name_part}}.binarypb"
args = [
"--encode=${invoker.testcase_proto_kind}",
"-I",
rebase_path(root_gen_dir),
"-I",
rebase_path("//"),
rebase_path(inputs[0]),
]
deps = []
if (defined(invoker.proto_deps)) {
deps += invoker.proto_deps
}
testonly = true
}
}
fuzzer_test(target_name) {
forward_variables_from(invoker,
"*",
[
"sources",
"deps",
"seed_corpus_sources",
"exclude_main",
])
sources = invoker.sources
deps = [
":${proto_target_name}",
"//mojo/core/embedder",
"//mojo/public/tools/fuzzers:mojolpm",
"//third_party/libprotobuf-mutator",
]
if (defined(invoker.deps)) {
deps += invoker.deps
}
if (defined(invoker.seed_corpus_sources)) {
seed_corpus = seed_corpus_path
seed_corpus_deps = [ ":${protoc_convert_target_name}" ]
}
if (defined(invoker.exclude_main)) {
exclude_main = invoker.exclude_main
}
}
} else {
not_needed(invoker, "*")
}
}
# Generate a MojoLPM-based fuzzer test.
#
# This rule will copy the proto file defining the fuzzer testcases into the
# output directory so that it can be compiled against the generated MojoLPM
# protos. It then adds a rule to compile that proto, and finally a fuzzer
# test target which uses the compiled proto.
#
# Optionally it can also handle converting a seed corpus of text protos into
# a binary corpus as part of the build.
#
# Parameters:
# sources
# List of source .cc files to compile.
#
# interfaces
# List of [interface name, mojom source file, remote type] 3-element
# lists.
# The remote type must be either "Remote" or "AssociatedRemote".
#
# interface_file
# JSON file containing the list of interfaces to generate.
# Format:
# {
# "interfaces": {
# ["InterfaceName", "//path/to/interface.mojom", "Remote"],
# ["AnotherInterface", "//path/to/another.mojom", "AssociatedRemote" ],
# ...
# }
# }
#
# deps
# List of dependencies to compile this target.
#
# proto_deps
# List of additional dependencies for compiling proto_source.
#
# seed_corpus_sources (optional)
# List of source .textproto files used to build a seed corpus.
#
# ensure_remote (optional)
# Ensures that for ever listed remotes, the "new" action is called before
# any other actions related to this remote. This ensures the remote
# always exists.
#
# Example:
# mojolpm_generated_fuzzer("foo_mojolpm_fuzzer") {
# sources = [ "foo_mojolpm_fuzzer.cc" ]
#
# interfaces = [
# ["InterfaceName", "//path/to/interface.mojom", "Remote"],
# ["AnotherInterface", "//path/to/another.mojom", "AssociatedRemote" ],
# ]
#
# deps = [
# "//content/browser/foo:foo_mojolpm_fuzzer_proto",
# "//content/browser:for_content_tests",
# "//content/public/browser:browser_sources",
# "//content/test:test_support",
# "//mojo/core/embedder",
# "//mojo/public/tools/fuzzers:mojolpm",
# "//third_party/libprotobuf-mutator",
# ]
#
# proto_deps = [
# "//content/browser/bar/mojom:mojom_mojolpm","
# ]
#
# seed_corpus_sources = [
# "foo_mojolpm_fuzzer_corpus/seed_one.textproto",
# "foo_mojolpm_fuzzer_corpus/seed_two.textproto",
# ]
# }
template("mojolpm_generated_fuzzer") {
assert(defined(invoker.sources),
"\"sources\" must be defined for $target_name")
assert(
defined(invoker.interfaces) || defined(invoker.interface_file),
"\"interfaces\" or \"interface_file\" must be defined for $target_name")
if (enable_mojom_fuzzer) {
_target_name = target_name
_generate_target_name = _target_name + "_mojolpm_generator_generate"
if (defined(invoker.interfaces)) {
# Generates the correct argument format give invoker.interfaces.
_script_inputs = []
foreach(elt, invoker.interfaces) {
_script_inputs +=
[ rebase_path("$root_gen_dir/" + elt[0] + "-module",
root_build_dir) + ":" + elt[1] + ":" + elt[2] ]
}
}
action(_generate_target_name) {
testonly = true
script = "//mojo/public/tools/fuzzers/mojolpm_generator.py"
inputs = jinja2_sources
deps = []
if (defined(invoker.proto_deps)) {
deps += invoker.proto_deps
}
args = []
if (defined(invoker.interfaces)) {
args += [ "--input" ]
args += _script_inputs
} else if (defined(invoker.interface_file)) {
args += [
"-f",
rebase_path(invoker.interface_file, root_build_dir),
]
}
args += [
"--output_file_format",
rebase_path("${target_gen_dir}/${_target_name}", root_build_dir),
]
if (defined(invoker.ensure_remote) && invoker.ensure_remote) {
args += [ "--ensure-remote" ]
}
outputs = [
"${target_gen_dir}/${_target_name}.h",
"${target_gen_dir}/${_target_name}.proto",
]
}
mojolpm_fuzzer_test(target_name) {
proto_deps = [ ":${_generate_target_name}" ]
deps = [ ":${_generate_target_name}" ]
proto_source = "${target_gen_dir}/${_target_name}.proto"
proto_in_dir = "${root_gen_dir}"
proto_out_dir = "."
if (defined(invoker.proto_deps)) {
proto_deps += invoker.proto_deps
}
if (defined(invoker.deps)) {
deps += invoker.deps
}
if (defined(invoker.seed_corpus_sources)) {
testcase_proto_kind = "mojolpmgenerator.${_target_name}.Testcase"
}
forward_variables_from(invoker,
"*",
[
"deps",
"proto_deps",
"proto_source",
])
}
} else {
not_needed("*")
not_needed(invoker, "*")
}
}