blob: 96f9c74b5301d25adca4b38cd212b71d35e87412 [file] [log] [blame]
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The implementation of the {cc, java}_fuzz_test rules."""
load("@rules_cc//cc:defs.bzl", "cc_binary")
# FIXME: Including this leads to a Stardoc error since defs.bzl is not visible. As a workaround, use native.java_binary.
#load("@rules_java//java:defs.bzl", "java_binary")
load("//fuzzing/private:common.bzl", "fuzzing_corpus", "fuzzing_dictionary", "fuzzing_launcher")
load("//fuzzing/private:binary.bzl", "fuzzing_binary", "fuzzing_binary_uninstrumented")
load("//fuzzing/private:java_utils.bzl", "determine_primary_class", "jazzer_fuzz_binary")
load("//fuzzing/private:regression.bzl", "fuzzing_regression_test")
load("//fuzzing/private/oss_fuzz:package.bzl", "oss_fuzz_package")
def fuzzing_decoration(
name,
raw_binary,
engine,
corpus = None,
dicts = None,
instrument_binary = True,
define_regression_test = True,
test_tags = None):
"""Generates the standard targets associated to a fuzz test.
This macro can be used to define custom fuzz test rules in case the default
`cc_fuzz_test` macro is not adequate. Refer to the `cc_fuzz_test` macro
documentation for the set of targets generated.
Args:
name: The name prefix of the generated targets. It is normally the
fuzz test name in the BUILD file.
raw_binary: The label of the cc_binary or cc_test of fuzz test
executable.
engine: The label of the fuzzing engine used to build the binary.
corpus: A list of corpus files.
dicts: A list of fuzzing dictionary files.
instrument_binary: **(Experimental, may be removed in the future.)**
By default, the generated targets depend on `raw_binary` through
a Bazel configuration using flags from the `@rules_fuzzing//fuzzing`
package to determine the fuzzing build mode, engine, and sanitizer
instrumentation.
When this argument is false, the targets assume that `raw_binary` is
already built in the proper configuration and will not apply the
transition.
Most users should not need to change this argument. If you think the
default instrumentation mode does not work for your use case, please
file a Github issue to discuss.
define_regression_test: If true, generate a regression test rule.
test_tags: Tags set on the fuzzing regression test.
"""
# We tag all non-test targets as "manual" in order to optimize the build
# size output of test runs in RBE mode. Otherwise, "bazel test" commands
# build all the non-test targets by default and, in remote builds, all these
# targets and their runfiles would be transferred from the remote cache to
# the local machine, ballooning the size of the output.
instrum_binary_name = name + "_bin"
launcher_name = name + "_run"
corpus_name = name + "_corpus"
dict_name = name + "_dict"
if instrument_binary:
fuzzing_binary(
name = instrum_binary_name,
binary = raw_binary,
engine = engine,
corpus = corpus_name,
dictionary = dict_name if dicts else None,
testonly = True,
tags = ["manual"],
)
else:
fuzzing_binary_uninstrumented(
name = instrum_binary_name,
binary = raw_binary,
engine = engine,
corpus = corpus_name,
dictionary = dict_name if dicts else None,
testonly = True,
tags = ["manual"],
)
fuzzing_corpus(
name = corpus_name,
srcs = corpus,
testonly = True,
)
if dicts:
fuzzing_dictionary(
name = dict_name,
dicts = dicts,
output = name + ".dict",
testonly = True,
)
fuzzing_launcher(
name = launcher_name,
binary = instrum_binary_name,
testonly = True,
tags = ["manual"],
)
if define_regression_test:
fuzzing_regression_test(
name = name,
binary = instrum_binary_name,
tags = test_tags,
)
oss_fuzz_package(
name = name + "_oss_fuzz",
base_name = name,
binary = instrum_binary_name,
testonly = True,
tags = ["manual"],
)
def cc_fuzz_test(
name,
corpus = None,
dicts = None,
engine = "@rules_fuzzing//fuzzing:cc_engine",
tags = None,
**binary_kwargs):
"""Defines a C++ fuzz test and a few associated tools and metadata.
For each fuzz test `<name>`, this macro defines a number of targets. The
most relevant ones are:
* `<name>`: A test that executes the fuzzer binary against the seed corpus
(or on an empty input if no corpus is specified).
* `<name>_bin`: The instrumented fuzz test executable. Use this target
for debugging or for accessing the complete command line interface of the
fuzzing engine. Most developers should only need to use this target
rarely.
* `<name>_run`: An executable target used to launch the fuzz test using a
simpler, engine-agnostic command line interface.
* `<name>_oss_fuzz`: Generates a `<name>_oss_fuzz.tar` archive containing
the fuzz target executable and its associated resources (corpus,
dictionary, etc.) in a format suitable for unpacking in the $OUT/
directory of an OSS-Fuzz build. This target can be used inside the
`build.sh` script of an OSS-Fuzz project.
Args:
name: A unique name for this target. Required.
corpus: A list containing corpus files.
dicts: A list containing dictionaries.
engine: A label pointing to the fuzzing engine to use.
tags: Tags set on the fuzzing regression test.
**binary_kwargs: Keyword arguments directly forwarded to the fuzz test
binary rule.
"""
# Append the '_' suffix to the raw target to dissuade users from referencing
# this target directly. Instead, the binary should be built through the
# instrumented configuration.
raw_binary_name = name + "_raw_"
binary_kwargs.setdefault("deps", [])
# Use += rather than append to allow users to pass in select() expressions for
# deps, which only support concatenation with +.
# Workaround for https://github.com/bazelbuild/bazel/issues/14157.
# buildifier: disable=list-append
binary_kwargs["deps"] += [engine]
# tags is not configurable and can thus use append.
binary_kwargs.setdefault("tags", []).append("manual")
cc_binary(
name = raw_binary_name,
**binary_kwargs
)
fuzzing_decoration(
name = name,
raw_binary = raw_binary_name,
engine = engine,
corpus = corpus,
dicts = dicts,
test_tags = (tags or []) + [
"fuzz-test",
],
)
def java_fuzz_test(
name,
srcs = None,
target_class = None,
corpus = None,
dicts = None,
engine = "@rules_fuzzing//fuzzing:java_engine",
tags = None,
**binary_kwargs):
"""Defines a Java fuzz test and a few associated tools and metadata.
For each fuzz test `<name>`, this macro defines a number of targets. The
most relevant ones are:
* `<name>`: A test that executes the fuzzer binary against the seed corpus
(or on an empty input if no corpus is specified).
* `<name>_bin`: The instrumented fuzz test executable. Use this target
for debugging or for accessing the complete command line interface of the
fuzzing engine. Most developers should only need to use this target
rarely.
* `<name>_run`: An executable target used to launch the fuzz test using a
simpler, engine-agnostic command line interface.
* `<name>_oss_fuzz`: Generates a `<name>_oss_fuzz.tar` archive containing
the fuzz target executable and its associated resources (corpus,
dictionary, etc.) in a format suitable for unpacking in the $OUT/
directory of an OSS-Fuzz build. This target can be used inside the
`build.sh` script of an OSS-Fuzz project.
Args:
name: A unique name for this target. Required.
srcs: A list of source files of the target.
target_class: The class that contains the static fuzzerTestOneInput
method. Defaults to the same class main_class would.
corpus: A list containing corpus files.
dicts: A list containing dictionaries.
engine: A label pointing to the fuzzing engine to use.
tags: Tags set on the fuzzing regression test.
**binary_kwargs: Keyword arguments directly forwarded to the fuzz test
binary rule.
"""
# Append the '_' suffix to the raw target to dissuade users from referencing
# this target directly. Instead, the binary should be built through the
# instrumented configuration.
raw_target_name = name + "_target_"
# Determine a value for target_class heuristically using the same rules as
# those used by Bazel internally for main_class.
# FIXME: This operates on the raw unresolved srcs list entries and thus
# cannot handle labels.
if not target_class:
target_class = determine_primary_class(srcs, name)
if not target_class:
fail(("Unable to determine fuzz target class for java_fuzz_test {name}" +
", specify target_class.").format(
name = name,
))
target_class_manifest_line = "Jazzer-Fuzz-Target-Class: %s" % target_class
binary_kwargs.setdefault("deps", [])
# Use += rather than append to allow users to pass in select() expressions for
# deps, which only support concatenation with +.
# Workaround for https://github.com/bazelbuild/bazel/issues/14157.
# buildifier: disable=list-append
binary_kwargs["deps"] += [engine]
binary_kwargs.setdefault("deploy_manifest_lines", [])
# buildifier: disable=list-append
binary_kwargs["deploy_manifest_lines"] += [target_class_manifest_line]
# tags is not configurable and can thus use append.
binary_kwargs.setdefault("tags", []).append("manual")
native.java_binary(
name = raw_target_name,
srcs = srcs,
create_executable = False,
**binary_kwargs
)
raw_binary_name = name + "_raw_"
jazzer_fuzz_binary(
name = raw_binary_name,
agent = select({
"@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_agent_deploy.jar",
"//conditions:default": "@jazzer//agent:jazzer_agent_deploy.jar",
}),
# Since the choice of sanitizer is explicit for local fuzzing, we also
# let it apply to projects with no native dependencies.
driver_java_only = select({
"@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_driver",
"@rules_fuzzing//fuzzing/private:use_sanitizer_none": "@jazzer//driver:jazzer_driver",
"@rules_fuzzing//fuzzing/private:use_sanitizer_asan": "@jazzer//driver:jazzer_driver_asan",
"@rules_fuzzing//fuzzing/private:use_sanitizer_ubsan": "@jazzer//driver:jazzer_driver_ubsan",
}, no_match_error = "Jazzer only supports the sanitizer settings: \"none\", \"asan\", \"ubsan\""),
driver_with_native = select({
"@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_driver_with_sanitizer",
"@rules_fuzzing//fuzzing/private:use_sanitizer_none": "@jazzer//driver:jazzer_driver",
"@rules_fuzzing//fuzzing/private:use_sanitizer_asan": "@jazzer//driver:jazzer_driver_asan",
"@rules_fuzzing//fuzzing/private:use_sanitizer_ubsan": "@jazzer//driver:jazzer_driver_ubsan",
}, no_match_error = "Jazzer only supports the sanitizer settings: \"none\", \"asan\", \"ubsan\""),
sanitizer_options = select({
"@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing//fuzzing/private:oss_fuzz_jazzer_sanitizer_options.sh",
"//conditions:default": "@rules_fuzzing//fuzzing/private:local_jazzer_sanitizer_options.sh",
}),
tags = ["manual"],
target = raw_target_name,
target_deploy_jar = raw_target_name + "_deploy.jar",
)
fuzzing_decoration(
name = name,
raw_binary = raw_binary_name,
# jazzer_fuzz_binary already instrumented the native dependencies.
instrument_binary = False,
engine = engine,
corpus = corpus,
dicts = dicts,
test_tags = (tags or []) + [
"fuzz-test",
],
)