blob: 1797780cfb4df65600c7178da464dacaf2805cac [file] [log] [blame] [edit]
#include "./fuzztest/init_fuzztest.h"
#include <cstdlib>
#include <iostream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
#include "absl/flags/flag.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "./fuzztest/internal/googletest_adaptor.h"
#include "./fuzztest/internal/io.h"
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/registry.h"
#include "./fuzztest/internal/runtime.h"
#define FUZZTEST_FLAG_PREFIX ""
#define FUZZTEST_FLAG_NAME(name) name
#define FUZZTEST_FLAG(name) FLAGS_##name
#define FUZZTEST_DEFINE_FLAG(type, name, default_value, description) \
ABSL_FLAG(type, FUZZTEST_FLAG_NAME(name), default_value, description)
FUZZTEST_DEFINE_FLAG(
bool, list_fuzz_tests, false,
"Prints (to stdout) the list of all available FUZZ_TEST-s in the "
"binary and exits. I.e., prints the test names that can be run with "
"the flag `--" FUZZTEST_FLAG_PREFIX "fuzz=<test name>`.");
static constexpr absl::string_view kUnspecified = "<unspecified>";
FUZZTEST_DEFINE_FLAG(
std::string, fuzz, std::string(kUnspecified),
"Runs a single FUZZ_TEST in continuous fuzzing mode. "
"E.g., `--" FUZZTEST_FLAG_PREFIX
"fuzz=MySuite.MyFuzzTest` runs the given FUZZ_TEST in "
"fuzzing mode. You can also provide just a part of the name, e.g., "
"`--" FUZZTEST_FLAG_PREFIX
"fuzz=MyFuzz`, if it matches only a single FUZZ_TEST. "
"If you have only one fuzz test in your binary, you can also use "
"`--" FUZZTEST_FLAG_PREFIX
"fuzz=` to run it in fuzzing mode (i.e., by setting the "
"flag to empty string). "
"In fuzzing mode the selected test runs until a bug is found or "
"until manually stopped. Fuzzing mode uses coverage feedback to "
"iteratively build up a corpus of inputs that maximize coverage and "
"to reach deep bugs. Note that the binary must be compiled with "
"`--config=fuzztest` for this to work, as it needs coverage "
"instrumentation.");
FUZZTEST_DEFINE_FLAG(
absl::Duration, fuzz_for, absl::InfiniteDuration(),
"Runs all fuzz tests in fuzzing mode for the specified duration. Can "
"be combined with --" FUZZTEST_FLAG_PREFIX
"fuzz to select a single fuzz tests, or "
"with --" FUZZTEST_FLAG_PREFIX
"filter to select a subset of fuzz tests. Recommended "
"to use with test sharding.");
namespace fuzztest {
std::vector<std::string> ListRegisteredTests() {
std::vector<std::string> result;
internal::ForEachTest(
[&](const auto& test) { result.push_back(test.full_name()); });
return result;
}
std::string GetMatchingFuzzTestOrExit(std::string_view name) {
const std::string partial_name(name);
const std::vector<std::string> full_names = ListRegisteredTests();
std::vector<const std::string*> matches;
for (const std::string& full_name : full_names) {
if (absl::StrContains(full_name, partial_name)) {
if (full_name == partial_name) {
// In case of an exact match, we end the search and use it. This is to
// handle the case when we want to select `MySuite.MyTest`, but the
// binary has both `MySuite.MyTest` and `MySuite.MyTestX`.
return full_name;
} else {
matches.push_back(&full_name);
}
}
}
if (matches.empty()) {
absl::FPrintF(stderr, "\n\nNo FUZZ_TEST matches the name: %s\n\n", name);
absl::FPrintF(stderr, "Valid tests:\n");
for (const std::string& full_name : full_names) {
absl::FPrintF(stderr, " %s\n", full_name);
}
exit(1);
} else if (matches.size() > 1) {
absl::FPrintF(stderr, "\n\nMultiple FUZZ_TESTs match the name: %s\n\n",
name);
absl::FPrintF(stderr, "Please select one. Matching tests:\n");
for (const std::string* full_name : matches) {
absl::FPrintF(stderr, " %s\n", *full_name);
}
exit(1);
}
return *matches[0];
}
void RunSpecifiedFuzzTest(std::string_view name) {
const std::string matching_fuzz_test = GetMatchingFuzzTestOrExit(name);
internal::ForEachTest([&](auto& test) {
if (test.full_name() == matching_fuzz_test) {
exit(std::move(test).make()->RunInFuzzingMode(/*argc=*/nullptr,
/*argv=*/nullptr));
}
});
}
void InitFuzzTest(int* argc, char*** argv) {
const bool is_listing = absl::GetFlag(FUZZTEST_FLAG(list_fuzz_tests));
if (is_listing) {
for (const auto& name : ListRegisteredTests()) {
std::cout << "[*] Fuzz test: " << name << '\n';
}
std::exit(0);
}
FUZZTEST_INTERNAL_CHECK(
internal::Runtime::instance().files_to_replay().empty(),
"You cannot manually set corpus files");
std::vector<std::string> corpus_files =
internal::GetFileOrFilesInDir(absl::StrCat(
"security/laser/sundew/blaze/corpora/", *argv[0], "/minimized"));
if (!corpus_files.empty()) {
internal::Runtime::instance().SetFilesToReplay(corpus_files);
}
const auto test_to_fuzz = absl::GetFlag(FUZZTEST_FLAG(fuzz));
const bool is_test_to_fuzz_specified = test_to_fuzz != kUnspecified;
if (is_test_to_fuzz_specified) {
const std::string matching_fuzz_test =
GetMatchingFuzzTestOrExit(test_to_fuzz);
// Delegate the test to GoogleTest.
GTEST_FLAG_SET(filter, matching_fuzz_test);
}
const auto duration = absl::GetFlag(FUZZTEST_FLAG(fuzz_for));
const bool is_duration_specified =
absl::ZeroDuration() < duration && duration < absl::InfiniteDuration();
if (is_duration_specified) {
internal::Runtime::instance().SetFuzzTimeLimit(duration);
}
std::vector<std::string> crashing_files;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
crashing_files = internal::GetFileOrFilesInDir(absl::StrCat(
"security/laser/sundew/blaze/corpora/", *argv[0], "/crashing_inputs"));
#endif
internal::RegisterFuzzTestsAsGoogleTests(argc, argv, crashing_files);
const RunMode run_mode = is_test_to_fuzz_specified || is_duration_specified
? RunMode::kFuzz
: RunMode::kUnitTest;
internal::Runtime::instance().SetRunMode(run_mode);
}
} // namespace fuzztest