blob: 24e094f2aa950fb3ce2c2146a0b92cec2a83bfc5 [file] [log] [blame]
// Copyright 2022 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
//
// http://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.
#include "./fuzztest/internal/runtime.h"
#if !defined(_WIN32) && !defined(__Fuchsia__)
#define FUZZTEST_HAS_RUSAGE
#include <sys/resource.h>
#endif
#include <signal.h>
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <filesystem> // NOLINT
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <thread> // NOLINT
#include <utility>
#include <vector>
#include "absl/functional/bind_front.h"
#include "absl/functional/function_ref.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/random/discrete_distribution.h"
#include "absl/random/random.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "./common/bazel.h"
#include "./common/logging.h"
#include "./fuzztest/internal/configuration.h"
#include "./fuzztest/internal/corpus_database.h"
#include "./fuzztest/internal/coverage.h"
#include "./fuzztest/internal/domains/mutation_metadata.h"
#include "./fuzztest/internal/fixture_driver.h"
#include "./fuzztest/internal/flag_name.h"
#include "./fuzztest/internal/io.h"
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/printer.h"
#include "./fuzztest/internal/serialization.h"
#include "./fuzztest/internal/status.h"
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)
#define FUZZTEST_HAS_SANITIZER
#include <sanitizer/common_interface_defs.h>
#include "./fuzztest/internal/sanitizer_interface.h"
#endif
#ifndef TRAP_PERF
inline constexpr int TRAP_PERF = 6;
#endif
namespace fuzztest::internal {
namespace {
using ::fuzztest::domain_implementor::MutationMetadata;
using ::fuzztest::domain_implementor::PrintMode;
using ::fuzztest::domain_implementor::RawSink;
constexpr size_t kValueMaxPrintLength = 2048;
constexpr absl::string_view kTrimIndicator = " ...<value too long>";
constexpr absl::string_view kReproducerDirName = "fuzztest_repro";
std::string GetFilterForCrashingInput(absl::string_view test_name,
absl::string_view crashing_input_path) {
return absl::StrCat(test_name, "/Regression/", Basename(crashing_input_path));
}
// Returns a reproduction command for replaying
// `configuration.crashing_input_to_reproduce` or `reproducer_path` from a
// command line, using the `configuration.reproduction_command_template`.
std::string GetReproductionCommand(const Configuration* configuration,
absl::string_view reproducer_path,
absl::string_view test_name) {
const bool is_reproducer_in_corpus_db =
configuration && configuration->crashing_input_to_reproduce;
FUZZTEST_CHECK(!reproducer_path.empty() || is_reproducer_in_corpus_db);
if (!configuration || !configuration->reproduction_command_template) {
absl::string_view reproducer =
is_reproducer_in_corpus_db ? *configuration->crashing_input_to_reproduce
: reproducer_path;
return absl::StrFormat(
"Replay by adding:\n\n"
"--test_filter=%s "
"--test_env=FUZZTEST_REPLAY=%s\n\n"
"after `bazel test` in your original invocation.\n",
test_name, reproducer);
}
const std::string command_template =
*configuration->reproduction_command_template;
FUZZTEST_CHECK(absl::StrContains(command_template, kTestFilterPlaceholder));
FUZZTEST_CHECK(absl::StrContains(command_template, kExtraArgsPlaceholder));
if (is_reproducer_in_corpus_db) {
absl::string_view corpus_db = configuration->corpus_database;
auto test_srcdir = absl::NullSafeStringView(getenv("TEST_SRCDIR"));
if (!test_srcdir.empty()) {
const std::string prefix = absl::StrCat(test_srcdir, "/");
absl::ConsumePrefix(&corpus_db, prefix);
}
std::vector<std::string> extra_args = {absl::StrCat(
"--test_arg=--", FUZZTEST_FLAG_PREFIX, "corpus_database=", corpus_db)};
return absl::StrReplaceAll(
command_template,
{{kTestFilterPlaceholder,
GetFilterForCrashingInput(
test_name, *configuration->crashing_input_to_reproduce)},
{kExtraArgsPlaceholder, absl::StrJoin(extra_args, " ")}});
} else {
return absl::StrReplaceAll(
command_template,
{{kTestFilterPlaceholder, test_name},
{kExtraArgsPlaceholder,
absl::StrCat("--test_env=FUZZTEST_REPLAY=", reproducer_path,
" --test_strategy=local --test_output=streamed")}});
}
}
void PrintReproductionInstructionsForUndeclaredOutputs(
RawSink out, absl::string_view test_name,
absl::string_view reproducer_path) {
absl::string_view file_name = Basename(reproducer_path);
absl::Format(out,
"Reproducer file was dumped under "
"TEST_UNDECLARED_OUTPUTS_DIR.\n");
absl::Format(out,
"Make a copy of it with:\n\n"
"mkdir -p /tmp/%s && \\\ncp -f %s /tmp/%s/%s\n\n",
kReproducerDirName, reproducer_path, kReproducerDirName,
file_name);
}
absl::string_view GetSeparator() {
return "\n================================================================="
"\n";
}
#if defined(FUZZTEST_HAS_SANITIZER)
// clang-format off
extern "C" void __attribute__((visibility("default")))
__sanitizer_report_error_summary(const char* error_summary) {
// clang-format on
absl::StatusOr<std::string> crash_type =
ParseCrashTypeFromSanitizerSummary(error_summary);
FUZZTEST_LOG_IF(ERROR, !crash_type.ok())
<< "Failed to extract sanitizer crash type: " << crash_type.status();
Runtime::instance().SetCrashTypeIfUnset(
std::move(crash_type).value_or("Sanitizer crash"));
}
#endif
} // namespace
ReproducerOutputLocation GetReproducerOutputLocation() {
#ifdef FUZZTEST_USE_CENTIPEDE
if (std::getenv("CENTIPEDE_RUNNER_FLAGS") != nullptr) {
return ReproducerOutputLocation{
"", ReproducerOutputLocation::Type::kReportToController};
}
#endif
auto env = absl::NullSafeStringView(getenv("FUZZTEST_REPRODUCERS_OUT_DIR"));
if (!env.empty()) {
return ReproducerOutputLocation{
std::string(env), ReproducerOutputLocation::Type::kUserSpecified};
}
env = absl::NullSafeStringView(getenv("TEST_UNDECLARED_OUTPUTS_DIR"));
if (!env.empty()) {
auto path = std::filesystem::path(std::string(env)) /
std::string(kReproducerDirName);
return ReproducerOutputLocation{
path.string(), ReproducerOutputLocation::Type::kTestUndeclaredOutputs};
}
return ReproducerOutputLocation{"",
ReproducerOutputLocation::Type::kUnspecified};
}
void PrintReproducerIfRequested(RawSink out, const FuzzTest& test,
const Configuration* configuration,
std::string reproducer_path) {
const bool is_reproducer_in_corpus_db =
configuration && configuration->crashing_input_to_reproduce;
if (!is_reproducer_in_corpus_db) {
if (reproducer_path.empty()) {
return;
}
}
if (configuration && configuration->reproduction_command_template) {
absl::Format(out, "%s=== Reproduction command\n\n", GetSeparator());
} else {
absl::Format(out, "%s=== Reproducer\n\n", GetSeparator());
}
const std::string test_name =
absl::StrCat(test.suite_name(), ".", test.test_name());
if (!is_reproducer_in_corpus_db) {
const auto out_location = GetReproducerOutputLocation();
switch (out_location.type) {
case ReproducerOutputLocation::Type::kUserSpecified:
absl::Format(out, "Reproducer file was dumped at:\n%s\n",
reproducer_path);
break;
case ReproducerOutputLocation::Type::kTestUndeclaredOutputs:
PrintReproductionInstructionsForUndeclaredOutputs(out, test_name,
reproducer_path);
reproducer_path = absl::StrCat("/tmp/", kReproducerDirName, "/",
Basename(reproducer_path));
break;
default:
FUZZTEST_LOG(FATAL) << "unsupported reproducer output location type "
"to print reproduction command for";
}
}
absl::Format(
out, "%s\n\n",
GetReproductionCommand(configuration, reproducer_path, test_name));
}
void (*crash_handler_hook)();
Runtime::Runtime() {
if (const char* crash_metadata_path =
std::getenv("FUZZTEST_CRASH_METADATA_PATH");
crash_metadata_path != nullptr) {
RegisterCrashMetadataListener(
[=](absl::string_view crash_type,
absl::Span<const std::string> /*stack_frames*/) {
WriteFile(crash_metadata_path, crash_type);
});
}
}
// TODO(lszekeres): Return absl::StatusOr when WriteDataToDir returns StatusOr.
std::string Runtime::DumpReproducer() const {
const auto out_location = GetReproducerOutputLocation();
if (out_location.type == ReproducerOutputLocation::Type::kUnspecified) {
absl::FPrintF(GetStderr(),
"[.] No reproducer output location specified - not writing "
"the reproducer file.\n");
return "";
}
if (out_location.type ==
ReproducerOutputLocation::Type::kReportToController) {
absl::FPrintF(GetStderr(),
"[.] Reproducer will be reported to the FuzzTest controller "
"- not writing the reproducer file.\n");
return "";
}
FUZZTEST_CHECK(!out_location.dir_path.empty())
<< "Reproducer output directory must not be empty if "
"not reporting to controller.";
const std::string content = SerializeIRObject(
current_args_->domain.SerializeCorpus(current_args_->corpus_value));
std::string path = WriteDataToDir(content, out_location.dir_path);
if (path.empty()) {
absl::FPrintF(GetStderr(), "[!] Failed to write reproducer file!\n");
}
return path;
}
void Runtime::PrintFinalStats(RawSink out) const {
absl::Format(out, "%s=== Fuzzing stats\n\n", GetSeparator());
const absl::Duration fuzzing_time = clock_fn_() - stats_->start_time;
absl::Format(out, "Elapsed time: %s\n", absl::FormatDuration(fuzzing_time));
absl::Format(out, "Total runs: %d\n", stats_->runs);
#ifndef FUZZTEST_USE_CENTIPEDE
absl::Format(out, "Edges covered: %d\n", stats_->edges_covered);
absl::Format(out, "Total edges: %d\n", stats_->total_edges);
absl::Format(out, "Corpus size: %d\n", stats_->useful_inputs);
absl::Format(out, "Max stack used: %d\n", stats_->max_stack_used);
#endif
}
void Runtime::PrintReport(RawSink out) const {
// We don't want to try and print a fuzz report when we are not running a fuzz
// test, even if we got a crash.
if (!reporter_enabled_) return;
if (auto* coverage = GetExecutionCoverage()) {
// Turn off tracing to avoid having the report trigger more problems during
// tracing, potentially leading to stack overflow.
coverage->SetIsTracing(false);
}
if (crash_handler_hook) crash_handler_hook();
for (CrashMetadataListenerRef listener : crash_metadata_listeners_) {
const std::string final_crash_type =
absl::StrCat(current_args_ == nullptr ? "SETUP FAILURE: " : "",
crash_type_.value_or("Generic crash"));
listener(final_crash_type, {});
}
if (run_mode() != RunMode::kUnitTest) {
PrintFinalStats(out);
}
if (current_args_ != nullptr) {
absl::Format(out, "%s=== BUG FOUND!\n\n", GetSeparator());
absl::Format(out, "%s:%d: Counterexample found for %s.%s.\n",
current_test_->file(), current_test_->line(),
current_test_->suite_name(), current_test_->test_name());
auto printer = current_args_->domain.GetPrinter();
printer.PrintFormattedAggregateValue(
current_args_->corpus_value, out, PrintMode::kHumanReadable,
/*prefix=*/"The test fails with input:", /*suffix=*/"\n",
/*element_formatter=*/
[](RawSink out, size_t idx, absl::string_view element) {
bool trim = element.size() > kValueMaxPrintLength;
absl::Format(out, "\nargument %d: %s%s", idx,
trim ? element.substr(0, kValueMaxPrintLength) : element,
trim ? kTrimIndicator : "");
});
// There doesn't seem to be a good way to generate a reproducer test when
// the test uses a fixture (see b/241271658).
if (!current_test_->uses_fixture()) {
absl::Format(out, "%s=== Regression test draft\n\n", GetSeparator());
absl::Format(out, "TEST(%1$s, %2$sRegression) {\n %2$s(\n",
current_test_->suite_name(), current_test_->test_name());
printer.PrintFormattedAggregateValue(
current_args_->corpus_value, out, PrintMode::kSourceCode,
/*prefix=*/"", /*suffix=*/"",
/*element_formatter=*/
[](RawSink out, size_t idx, absl::string_view element) {
if (idx != 0) absl::Format(out, ",\n");
bool trim = element.size() > kValueMaxPrintLength;
absl::Format(
out, " %s%s",
trim ? element.substr(0, kValueMaxPrintLength) : element,
trim ? kTrimIndicator : "");
});
absl::Format(out, "\n );\n");
absl::Format(out, "}\n");
absl::Format(out,
"\nPlease note that the code generated above is best effort "
"and is intended\n"
"to be used as a draft regression test.\n"
"For reproducing findings please rely on file based "
"reproduction.\n");
}
const std::string reproducer_path = DumpReproducer();
PrintReproducerIfRequested(out, *current_test_, current_configuration_,
reproducer_path);
} else {
absl::Format(out, "%s=== SETUP FAILURE!\n\n", GetSeparator());
absl::Format(out, "%s:%d: There was a problem with %s.%s.",
current_test_->file(), current_test_->line(),
current_test_->suite_name(), current_test_->test_name());
if (test_abort_message != nullptr) {
absl::Format(out, "%s", *test_abort_message);
}
}
absl::Format(out, "%s", GetSeparator());
}
#ifndef FUZZTEST_USE_CENTIPEDE
class Runtime::Watchdog {
public:
explicit Watchdog(Runtime& runtime) : runtime_{runtime} {
watchdog_thread_ =
std::thread(absl::bind_front(&Runtime::Watchdog::WatchdogLoop, this));
while (!watchdog_thread_started_) std::this_thread::yield();
}
~Watchdog() {
stop_requested_ = true;
watchdog_thread_.join();
}
private:
void WatchdogLoop() {
watchdog_thread_started_ = true;
while (!stop_requested_) {
runtime_.CheckWatchdogLimits();
runtime_.watchdog_spinlock_.Lock();
if (runtime_.watchdog_limit_exceeded_) {
runtime_.watchdog_spinlock_.Unlock();
break;
}
runtime_.watchdog_spinlock_.Unlock();
absl::SleepFor(absl::Seconds(1));
}
}
std::atomic<bool> watchdog_thread_started_ = false;
std::atomic<bool> stop_requested_ = false;
std::thread watchdog_thread_;
Runtime& runtime_;
};
#else // FUZZTEST_USE_CENTIPEDE
// Centipede runner has its own watchdog.
class Runtime::Watchdog {
public:
explicit Watchdog(Runtime&) {}
};
#endif // FUZZTEST_USE_CENTIPEDE
Runtime::Watchdog Runtime::CreateWatchdog() { return Watchdog{*this}; }
void Runtime::Spinlock::Lock() {
while (locked_.test_and_set(std::memory_order_acq_rel)) {
std::this_thread::yield();
}
}
void Runtime::Spinlock::Unlock() { locked_.clear(std::memory_order_release); }
static size_t GetPeakRSSBytes() {
#ifndef FUZZTEST_HAS_RUSAGE
return 0;
#else
struct rusage usage = {};
if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
// On Linux, ru_maxrss is in KiB
return usage.ru_maxrss * 1024;
#endif
}
void Runtime::CheckWatchdogLimits() {
// Centipede runner has its own watchdog.
#ifndef FUZZTEST_USE_CENTIPEDE
watchdog_spinlock_.Lock();
if (current_configuration_ == nullptr || !test_iteration_started_ ||
watchdog_limit_exceeded_) {
watchdog_spinlock_.Unlock();
return;
}
const absl::Duration run_duration =
clock_fn_() - current_iteration_start_time_;
if (current_configuration_->time_limit_per_input > absl::ZeroDuration() &&
run_duration > current_configuration_->time_limit_per_input) {
absl::FPrintF(
GetStderr(), "[!] Per-input timeout exceeded: %s > %s - aborting\n",
absl::FormatDuration(run_duration),
absl::FormatDuration(current_configuration_->time_limit_per_input));
watchdog_limit_exceeded_ = true;
watchdog_spinlock_.Unlock();
std::abort();
}
const size_t rss_usage = GetPeakRSSBytes();
if (current_configuration_->rss_limit > 0 &&
rss_usage > current_configuration_->rss_limit) {
absl::FPrintF(GetStderr(),
"[!] RSS limit exceeded: %zu > %zu (bytes) - aborting\n",
rss_usage, current_configuration_->rss_limit);
watchdog_limit_exceeded_ = true;
watchdog_spinlock_.Unlock();
std::abort();
}
watchdog_spinlock_.Unlock();
#endif // FUZZTEST_USE_CENTIPEDE
}
void Runtime::SetCurrentTest(const FuzzTest* test,
const Configuration* configuration) {
FUZZTEST_CHECK((test != nullptr) == (configuration != nullptr));
current_test_ = test;
current_configuration_ = configuration;
if (configuration == nullptr) return;
if (const auto test_time_limit = configuration->GetTimeLimitPerTest();
test_time_limit < absl::InfiniteDuration()) {
const absl::Status has_enough_time =
fuzztest::internal::VerifyBazelHasEnoughTimeToRunTest(
creation_time_, test_time_limit, test_counter_,
configuration->fuzz_tests.size());
FUZZTEST_PRECONDITION(has_enough_time.ok())
<< "Not enough time for running the fuzz test " << test->full_name()
<< " for " << test_time_limit << ": " << has_enough_time;
}
++test_counter_;
}
void Runtime::OnTestIterationStart(const absl::Time& start_time) {
watchdog_spinlock_.Lock();
current_iteration_start_time_ = start_time;
test_iteration_started_ = true;
watchdog_spinlock_.Unlock();
}
void Runtime::OnTestIterationEnd() {
CheckWatchdogLimits();
watchdog_spinlock_.Lock();
test_iteration_started_ = false;
watchdog_spinlock_.Unlock();
}
#if defined(__linux__) || defined(__APPLE__)
struct OldSignalHandler {
int signum;
absl::string_view signame;
struct sigaction action;
};
static FILE* signal_out;
struct FILESink {
friend void AbslFormatFlush(FILESink*, absl::string_view v) {
fprintf(signal_out, "%.*s", static_cast<int>(v.size()), v.data());
fflush(signal_out);
}
};
static FILESink signal_out_sink;
static OldSignalHandler crash_handlers[] = {
{SIGILL, "SIGILL"}, {SIGFPE, "SIGFPE"}, {SIGSEGV, "SIGSEGV"},
{SIGBUS, "SIGBUS"}, {SIGTRAP, "SIGTRAP"}, {SIGABRT, "SIGABRT"}};
static OldSignalHandler termination_handlers[] = {
{SIGHUP, "SIGHUP"}, {SIGINT, "SIGINT"}, {SIGTERM, "SIGTERM"}};
static bool HasCustomHandler(const struct sigaction& sigaction) {
return (sigaction.sa_flags & SA_SIGINFO) ? sigaction.sa_sigaction != nullptr
: (sigaction.sa_handler != nullptr &&
sigaction.sa_handler != SIG_DFL &&
sigaction.sa_handler != SIG_IGN);
}
static void HandleCrash(int signum, siginfo_t* info, void* ucontext) {
// Find the old signal handler.
auto it =
std::find_if(std::begin(crash_handlers), std::end(crash_handlers),
[signum](const auto& h) { return h.signum == signum; });
if (it == std::end(crash_handlers)) {
// signum should never be SIGABRT at this point, but branching on it for
// sanity.
absl::Format(&signal_out_sink,
"[!] HandleCrash called for non-crashing signal %d. This "
"indicates an internal bug! %s",
signum, signum == SIGABRT ? "Exiting" : "Aborting");
if (signum == SIGABRT)
std::_Exit(1);
else
std::abort();
}
Runtime& runtime = Runtime::instance();
runtime.SetCrashTypeIfUnset(std::string(it->signame));
const bool has_old_handler = HasCustomHandler(it->action);
// SIGTRAP generated by perf_event_open(sigtrap=1) may be used by
// debugging/analysis tools, so don't consider these as a crash.
if (!has_old_handler || signum != SIGTRAP ||
(info->si_code != TRAP_PERF && info->si_code != SI_TIMER)) {
// Dump our info first.
runtime.PrintReport(&signal_out_sink);
// The old signal handler might print important messages (e.g., strack
// trace) to the original file descriptors, therefore we restore them before
// calling them.
if (IsSilenceTargetEnabled()) RestoreTargetStdoutAndStderr();
}
if (!has_old_handler) {
// Unblock the signal and invoke the default action if there is no old
// handler.
//
// Note that we treat SIG_IGN as same as SIG_DFL at this point since we
// already reported the crash - if we wanted to ignore the signal we should
// return without reporting.
sigset_t set;
sigemptyset(&set);
sigaddset(&set, signum);
signal(signum, SIG_DFL);
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
raise(signum);
absl::Format(&signal_out_sink,
"[!] The default action of crashing signal %d did not crash - "
"aborting",
signum);
// At this point abort should be fine even if signum == SIGABRT.
std::abort();
}
if (it->action.sa_flags & SA_SIGINFO) {
it->action.sa_sigaction(signum, info, ucontext);
return;
}
it->action.sa_handler(signum);
}
static void HandleTermination(int, siginfo_t*, void*) {
Runtime::instance().SetTerminationRequested();
}
static void SetNewSigAction(int signum, void (*handler)(int, siginfo_t*, void*),
struct sigaction* old_sigact) {
struct sigaction new_sigact = {};
sigemptyset(&new_sigact.sa_mask);
new_sigact.sa_sigaction = handler;
// We make use of the SA_ONSTACK flag so that signal handlers are executed on
// a separate stack. This is needed to properly handle cases where stack space
// is limited and the delivery of a signal needs to be properly handled.
new_sigact.sa_flags = SA_SIGINFO | SA_ONSTACK;
if (sigaction(signum, &new_sigact, old_sigact) == -1) {
fprintf(GetStderr(), "Error installing signal handler: %s\n",
strerror(errno));
exit(1);
}
}
void InstallSignalHandlers(FILE* out) {
if (signal_out != nullptr) {
// Already installed. Noop.
return;
}
signal_out = out;
#if defined(FUZZTEST_HAS_SANITIZER)
// An ASan failure might come without a signal.
// Eg a divide by zero is intercepted by ASan and it terminates the process
// after printing its output. This handler helps us print our output
// afterwards.
__sanitizer_set_death_callback(
[](auto...) { Runtime::instance().PrintReport(&signal_out_sink); });
#endif
for (OldSignalHandler& h : crash_handlers) {
SetNewSigAction(h.signum, &HandleCrash, &h.action);
}
for (OldSignalHandler& h : termination_handlers) {
SetNewSigAction(h.signum, &HandleTermination, nullptr);
}
}
void Runtime::PrintFinalStatsOnDefaultSink() const {
PrintFinalStats(&signal_out_sink);
}
void Runtime::PrintReportOnDefaultSink() const {
PrintReport(&signal_out_sink);
}
#else // __linux__ || __APPLE__
// TODO(sbenzaquen): We should still install signal handlers in other systems.
void InstallSignalHandlers(FILE* out) {}
void Runtime::PrintFinalStatsOnDefaultSink() const {}
void Runtime::PrintReportOnDefaultSink() const {}
#endif // __linux__ || __APPLE__
void InstallUnexpectedExitHandler() {
#ifndef FUZZTEST_COMPATIBILITY_MODE
// In compatibility mode we don't install the handler because libFuzzer
// always explicitly exits, so every run would finish with an unexpected
// exit.
// https://github.com/llvm/llvm-project/blob/fa511cde48ea218dadfa8b35658ac06368f34607/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L930
[[maybe_unused]] const bool installed = [] {
std::atexit([] { Runtime::instance().HandleUnexpectedExit(); });
return true;
}();
#endif
}
void Runtime::HandleUnexpectedExit() {
if (!reporter_enabled_) return;
SetCrashTypeIfUnset("unexpected-exit");
std::abort();
}
using corpus_type = GenericDomainCorpusType;
FuzzTestFuzzerImpl::FuzzTestFuzzerImpl(
const FuzzTest& test, std::unique_ptr<UntypedFixtureDriver> fixture_driver)
: test_(test),
fixture_driver_(std::move(fixture_driver)),
params_domain_(fixture_driver_->GetDomains()),
execution_coverage_(internal::GetExecutionCoverage()),
corpus_coverage_(execution_coverage_ != nullptr
? execution_coverage_->GetCounterMap().size()
: 0) {
FUZZTEST_PRECONDITION(fixture_driver_ != nullptr)
<< "Invalid fixture driver!";
stats_.start_time = absl::Now();
const char* corpus_out_dir_chars = getenv("FUZZTEST_TESTSUITE_OUT_DIR");
if (corpus_out_dir_chars) corpus_out_dir_ = corpus_out_dir_chars;
std::vector<double> weights = {100.};
corpus_distribution_ =
absl::discrete_distribution<>(weights.begin(), weights.end());
}
FuzzTestFuzzerImpl::~FuzzTestFuzzerImpl() {
Runtime::instance().DisableReporter();
}
absl::StatusOr<corpus_type> FuzzTestFuzzerImpl::TryParse(
absl::string_view data) {
auto ir_value = ParseIRObject(data);
if (!ir_value) {
return absl::InvalidArgumentError("Unexpected file format");
}
auto corpus_value = params_domain_.ParseCorpus(*ir_value);
if (!corpus_value) {
return absl::InvalidArgumentError("Unexpected intermediate representation");
}
absl::Status is_valid = params_domain_.ValidateCorpusValue(*corpus_value);
if (!is_valid.ok()) {
return Prefix(is_valid, "Invalid corpus value");
}
return *corpus_value;
}
void FuzzTestFuzzerImpl::ReplayInput(absl::string_view file_path,
std::optional<int> blob_idx,
const Input& input) {
if (blob_idx.has_value()) {
absl::FPrintF(GetStderr(), "[.] Replaying input at index %d in %s\n",
*blob_idx, file_path);
} else {
absl::FPrintF(GetStderr(), "[.] Replaying %s\n", file_path);
}
RunOneInput(input);
}
bool FuzzTestFuzzerImpl::ReplayInputsIfAvailable(
const Configuration& configuration) {
// Crashing inputs are discovered in fuzzing mode. To increase the chance of
// reproducing the crash, fuzzing mode should be used.
runtime_.SetRunMode(RunMode::kFuzz);
auto replay_input = absl::bind_front(&FuzzTestFuzzerImpl::ReplayInput, this);
if (const auto file_paths = GetFilesToReplay()) {
ForEachInput(*file_paths, replay_input);
return true;
}
if (configuration.crashing_input_to_reproduce.has_value()) {
configuration.preprocess_crash_reproducing();
ForEachInput({*configuration.crashing_input_to_reproduce}, replay_input);
return true;
}
if (const auto to_minimize = ReadReproducerToMinimize()) {
absl::FPrintF(GetStderr(),
"[!] Looking for smaller mutations indefinitely: please "
"terminate the process manually (Ctrl-C) after some time.\n");
PRNG prng(seed_sequence_);
const auto original_serialized =
SerializeIRObject(params_domain_.SerializeCorpus(*to_minimize));
// In minimize mode we keep mutating the given reproducer value with
// `only_shrink=true` until we crash. We drop mutations that don't
// actually change the value.
// That way any crash is a smaller reproduction case.
// We start with a lot of mutations which can speed up minimization.
// We reduce the number of mutations if we can't find a crash, similar to
// simulated annealing.
int num_mutations = 20;
int counter = 0;
while (!ShouldStop()) {
auto copy = *to_minimize;
for (int i = 0; i < num_mutations; ++i) {
params_domain_.Mutate(copy, prng, {}, true);
}
num_mutations = std::max(1, num_mutations - 1);
// We compare the serialized version. Not very efficient but works for
// now.
if (SerializeIRObject(params_domain_.SerializeCorpus(copy)) ==
original_serialized) {
continue;
}
RunOneInput({std::move(copy)});
counter++;
if (counter == 100000) {
absl::FPrintF(GetStderr(), ".");
counter = 0;
}
}
std::exit(130); // Exit code termination.
}
return false;
}
std::optional<std::vector<std::string>> FuzzTestFuzzerImpl::GetFilesToReplay() {
auto file_or_dir = absl::NullSafeStringView(getenv("FUZZTEST_REPLAY"));
if (file_or_dir.empty()) return std::nullopt;
// Try as a directory path first.
std::vector<std::string> files = ListDirectory(std::string(file_or_dir));
// If not, consider it a file path.
if (files.empty()) {
files.push_back(std::string(file_or_dir));
}
std::sort(files.begin(), files.end());
return files;
}
std::optional<corpus_type> FuzzTestFuzzerImpl::ReadReproducerToMinimize() {
auto file = absl::NullSafeStringView(getenv("FUZZTEST_MINIMIZE_REPRODUCER"));
if (file.empty()) return std::nullopt;
absl::FPrintF(GetStderr(), "[*] Minimizing reproducer: %s\n", file);
std::optional<corpus_type> reproducer;
ForEachInput({std::string(file)},
[&](absl::string_view, std::optional<int>, Input input) {
FUZZTEST_CHECK(!reproducer.has_value())
<< "Multiple inputs found in " << file;
reproducer = std::move(input.args);
});
FUZZTEST_CHECK(reproducer.has_value()) << "Failed to read minimizer file!";
return *reproducer;
}
void FuzzTestFuzzerImpl::MutateValue(Input& input, absl::BitGenRef prng,
const MutationMetadata& metadata) {
// Do a random number of mutations on the value at once, skewed
// towards 1 and decreasing probability as we go up.
// Doing multiple smaller mutations at once allows reaching states that
// require larger mutations.
// The current implementation with a Poisson distribution has
// probabilities:
// - 1 mutation: 0.368
// - 2 mutations: 0.368
// - 3 mutations: 0.184
// - 4 mutations: 0.061
// - 5 mutations: 0.015
// - 6 mutations: 0.003
// ...
// The distribution and parameters have not been benchmarked or
// optimized in any significant way.
for (int mutations_at_once = absl::Poisson<int>(prng) + 1;
mutations_at_once > 0; --mutations_at_once) {
params_domain_.Mutate(input.args, prng, metadata, /*only_shrink=*/false);
}
}
void FuzzTestFuzzerImpl::UpdateCorpusDistribution() {
std::vector<double> weights(corpus_.size());
absl::Duration average_time = absl::ZeroDuration();
for (const Input& i : corpus_) {
average_time += i.run_time;
}
average_time /= corpus_.size();
// Prefer faster inputs than slower inputs, the maximum bias
// is 30x. These weights are dynamic and won't make slow-but-interesting
// inputs neglected: As more and more "slow but touched new coverage"
// inputs come in, the average execution time will be larger and slow input
// will get higher weights.
for (size_t i = 0; i < corpus_.size(); ++i) {
weights[i] = 100;
if (corpus_[i].run_time > average_time * 10)
weights[i] = 10;
else if (corpus_[i].run_time > average_time * 4)
weights[i] = 25;
else if (corpus_[i].run_time > average_time * 2)
weights[i] = 50;
else if (corpus_[i].run_time * 3 > average_time * 4)
weights[i] = 75;
else if (corpus_[i].run_time * 4 < average_time)
weights[i] = 300;
else if (corpus_[i].run_time * 3 < average_time)
weights[i] = 200;
else if (corpus_[i].run_time * 2 < average_time)
weights[i] = 150;
}
corpus_distribution_ =
absl::discrete_distribution<>(weights.begin(), weights.end());
}
FuzzTestFuzzerImpl::RunResult FuzzTestFuzzerImpl::TrySample(
const Input& sample, bool write_to_file) {
RunResult run_result = RunOneInput(sample);
if (runtime_.external_failure_detected()) {
// We detected a non fatal failure. Record it separately to minimize it
// locally.
minimal_non_fatal_counterexample_ = sample;
}
if (!run_result.new_coverage) return run_result;
if (write_to_file) TryWriteCorpusFile(sample);
++stats_.useful_inputs;
stats_.edges_covered = corpus_coverage_.GetNumberOfCoveredEdges();
const absl::Duration fuzzing_time = absl::Now() - stats_.start_time;
const int runs_per_sec =
static_cast<int>(stats_.runs / absl::ToDoubleSeconds(fuzzing_time));
absl::FPrintF(GetStderr(),
"[*] Corpus size: %5d | Edges covered: %6d | "
"Fuzzing time: %16s | Total runs: %1.2e | Runs/secs: %5d | "
"Max stack usage: %8d\n",
stats_.useful_inputs, stats_.edges_covered,
absl::FormatDuration(fuzzing_time), stats_.runs, runs_per_sec,
stats_.max_stack_used);
return run_result;
}
void FuzzTestFuzzerImpl::TrySampleAndUpdateInMemoryCorpus(Input sample,
bool write_to_file) {
auto [new_coverage, run_time] = TrySample(sample, write_to_file);
if (execution_coverage_ != nullptr &&
(stats_.runs % 4096 == 0 || new_coverage)) {
auto* coverage = GetExecutionCoverage();
params_domain_.UpdateMemoryDictionary(
sample.args,
coverage == nullptr ? nullptr : &coverage->GetTablesOfRecentCompares());
}
if (!new_coverage) return;
// New coverage, update corpus and weights.
sample.run_time = run_time;
corpus_.push_back(std::move(sample));
UpdateCorpusDistribution();
}
void FuzzTestFuzzerImpl::ForEachInput(
absl::Span<const std::string> files,
absl::FunctionRef<void(absl::string_view, std::optional<int>, Input)>
consume,
absl::Duration timeout) {
ForEachSerializedInput(
files,
[this, consume](absl::string_view file_path, std::optional<int> blob_idx,
std::string data) {
absl::StatusOr<corpus_type> corpus_value = TryParse(data);
if (!corpus_value.ok()) return corpus_value.status();
consume(file_path, blob_idx, Input{*std::move(corpus_value)});
return absl::OkStatus();
},
timeout);
}
bool FuzzTestFuzzerImpl::MinimizeCorpusIfInMinimizationMode(
absl::BitGenRef prng) {
auto inputdir =
absl::NullSafeStringView(getenv("FUZZTEST_MINIMIZE_TESTSUITE_DIR"));
if (inputdir.empty()) return false;
std::vector<std::string> files = ListDirectory(std::string(inputdir));
// Shuffle to potentially improve previously minimized corpus.
std::shuffle(files.begin(), files.end(), prng);
ForEachInput(files, [this](absl::string_view /*file_path*/,
std::optional<int> /*blob_idx*/, Input input) {
TrySample(input, /*write_to_file=*/true);
});
return true;
}
std::vector<FuzzTestFuzzerImpl::Input>
FuzzTestFuzzerImpl::TryReadCorpusFromFiles() {
std::vector<Input> inputs;
auto inputdir = absl::NullSafeStringView(getenv("FUZZTEST_TESTSUITE_IN_DIR"));
if (inputdir.empty()) return inputs;
std::vector<std::string> files = ListDirectory(std::string(inputdir));
ForEachInput(files, [&inputs](absl::string_view /*file_path*/,
std::optional<int> /*blob_idx*/, Input input) {
inputs.push_back(std::move(input));
});
return inputs;
}
void FuzzTestFuzzerImpl::TryWriteCorpusFile(const Input& input) {
if (corpus_out_dir_.empty()) return;
if (WriteDataToDir(
SerializeIRObject(params_domain_.SerializeCorpus(input.args)),
corpus_out_dir_)
.empty()) {
absl::FPrintF(GetStderr(), "[!] Failed to write corpus file.\n");
}
}
void FuzzTestFuzzerImpl::InitializeCorpus(absl::BitGenRef prng) {
std::vector<Input> inputs = TryReadCorpusFromFiles();
// Since inputs processed earlier have the adventage of increasing coverage
// and being kept in corpus, shuffle the input order to make it fair.
std::shuffle(inputs.begin(), inputs.end(), prng);
for (auto& input : inputs) {
TrySampleAndUpdateInMemoryCorpus(std::move(input),
/*write_to_file=*/false);
}
if (corpus_.empty()) {
TrySampleAndUpdateInMemoryCorpus(Input{params_domain_.Init(prng)});
}
}
bool FuzzTestFuzzerImpl::ShouldStop() {
if (runs_limit_.has_value() && stats_.runs >= *runs_limit_) return true;
if (time_limit_ != absl::InfiniteFuture() && absl::Now() > time_limit_)
return true;
return runtime_.termination_requested();
}
void FuzzTestFuzzerImpl::PopulateFromSeeds(
const std::vector<std::string>& corpus_files) {
for (const auto& seed : fixture_driver_->GetSeeds()) {
TrySampleAndUpdateInMemoryCorpus(
Input{seed},
// Dump the seed to the corpus so that it is present when the corpus is
// used in minimization or coverage replay.
/*write_to_file=*/true);
}
ForEachInput(corpus_files,
[this](absl::string_view /*file_path*/,
std::optional<int> /*blob_idx*/, Input input) {
TrySampleAndUpdateInMemoryCorpus(
std::move(input),
// Dump the seed to the corpus so that it is present when
// the corpus is used in minimization or coverage replay.
/*write_to_file=*/true);
});
}
void PopulateLimits(const Configuration& configuration,
ExecutionCoverage* execution_coverage) {
// centipede_adaptor would populate the limits to Centipede.
#ifndef FUZZTEST_USE_CENTIPEDE
if (execution_coverage)
execution_coverage->SetStackLimit(configuration.stack_limit);
#endif
}
bool FuzzTestFuzzerImpl::RunInUnitTestMode(const Configuration& configuration) {
runtime_.SetCurrentTest(&test_, &configuration);
runtime_.EnableReporter(&stats_, [] { return absl::Now(); });
runtime_.SetSkippingRequested(false);
fixture_driver_->RunFuzzTest([&] {
if (runtime_.skipping_requested()) {
absl::FPrintF(GetStderr(),
"[.] Skipping %s per request from the test setup.\n",
test_.full_name());
return;
}
[[maybe_unused]] auto watchdog = runtime_.CreateWatchdog();
PopulateLimits(configuration, execution_coverage_);
// TODO(sbenzaquen): Currently, some infrastructure code assumes that replay
// works in unit test mode, so we support it. However, we would like to
// limit replaying to fuzzing mode only, where we can guarantee that only
// a single FUZZ_TEST is selected to run. Once we make sure that no
// existing infra tries to replay in unit test mode, we can remove this.
if (ReplayInputsIfAvailable(configuration)) {
// If ReplayInputs returns, it means the replay didn't crash.
// In replay mode, we only replay.
return;
}
CorpusDatabase corpus_database(configuration);
auto replay_input =
absl::bind_front(&FuzzTestFuzzerImpl::ReplayInput, this);
ForEachInput(corpus_database.GetRegressionInputs(test_.full_name()),
replay_input);
std::vector<std::string> coverage_inputs =
corpus_database.GetCoverageInputsIfAny(test_.full_name());
// Replay a random subset of the coverage input until reach the timeout.
PRNG prng(seed_sequence_);
std::shuffle(coverage_inputs.begin(), coverage_inputs.end(), prng);
ForEachInput(coverage_inputs, replay_input,
configuration.GetTimeLimitPerTest());
runtime_.SetRunMode(RunMode::kUnitTest);
// If crashing inputs are reported, there's no need for a smoke test.
if (corpus_database.use_crashing_inputs()) return;
PopulateFromSeeds(/*corpus_files=*/{});
auto duration = absl::Seconds(1);
const auto fuzz_for = absl::NullSafeStringView(getenv("FUZZTEST_FUZZ_FOR"));
if (!fuzz_for.empty()) {
FUZZTEST_CHECK(absl::ParseDuration(fuzz_for, &duration))
<< "Could not parse duration in FUZZTEST_FUZZ_FOR=" << fuzz_for;
}
const auto time_limit = stats_.start_time + duration;
Input mutation{params_domain_.Init(prng)};
const size_t max_iterations = duration == absl::ZeroDuration() ? 0 : 10000;
for (int i = 0; i < max_iterations; ++i) {
runtime_.SetExternalFailureDetected(false);
RunOneInput(mutation);
if (runtime_.external_failure_detected()) {
break;
}
// We mutate the value, except that every num_mutations_per_value we
// generate a new one through Init.
constexpr size_t num_mutations_per_value = 100;
if (i % num_mutations_per_value < num_mutations_per_value - 1) {
MutateValue(mutation, prng, {});
} else {
mutation.args = params_domain_.Init(prng);
}
if (absl::Now() > time_limit) {
// Break the test after 1 second of running to avoid time outs on
// unittests when the fuzz test is doing a lot of work in a single
// iteration.
break;
}
}
});
runtime_.DisableReporter();
runtime_.SetCurrentTest(nullptr, nullptr);
return true;
}
FuzzTestFuzzerImpl::RunResult FuzzTestFuzzerImpl::RunOneInput(
const Input& input) {
++stats_.runs;
auto untyped_args = params_domain_.GetValue(input.args);
Runtime::Args debug_args{input.args, params_domain_};
runtime_.SetCurrentArgs(&debug_args);
// Reset and observe the coverage map and start tracing in
// the tightest scope possible. In particular, we can't include the call
// to GetValue in the scope as it will run user code.
if (execution_coverage_ != nullptr) {
execution_coverage_->ResetState();
}
absl::Time start = absl::Now();
runtime_.OnTestIterationStart(start);
// Set tracing after absl::Now(), otherwise it will make
// FuzzingModeTest.MinimizesDuplicatedCorpustest flaky because
// randomness in absl::Now() being traced by cmp coverage.
if (execution_coverage_ != nullptr) {
execution_coverage_->SetIsTracing(true);
}
runtime_.SetSkippingRequested(false);
fixture_driver_->RunFuzzTestIteration([&] {
if (!runtime_.skipping_requested()) {
fixture_driver_->Test(std::move(untyped_args));
}
});
if (execution_coverage_ != nullptr) {
execution_coverage_->SetIsTracing(false);
}
const absl::Duration run_time = absl::Now() - start;
bool new_coverage = false;
if (execution_coverage_ != nullptr && !runtime_.skipping_requested()) {
new_coverage = corpus_coverage_.Update(execution_coverage_);
stats_.max_stack_used =
std::max(stats_.max_stack_used, execution_coverage_->MaxStackUsed());
}
runtime_.OnTestIterationEnd();
runtime_.UnsetCurrentArgs();
return {new_coverage, run_time};
}
void FuzzTestFuzzerImpl::MinimizeNonFatalFailureLocally(absl::BitGenRef prng) {
// We try to minimize the counterexample until we reach a point where no new
// failures are found.
// We stop when run kMaxTriedWithoutFailure consecutive runs without finding a
// smaller failure, but also add a time limit in case each iteration takes too
// long.
const absl::Time deadline =
std::min(absl::Now() + absl::Minutes(1), time_limit_);
int tries_without_failure = 0;
constexpr int kMaxTriedWithoutFailure = 10000;
while (tries_without_failure < kMaxTriedWithoutFailure &&
absl::Now() < deadline) {
FUZZTEST_CHECK(minimal_non_fatal_counterexample_.has_value())
<< "Caller didn't populate minimal_non_fatal_counterexample_";
auto copy = *minimal_non_fatal_counterexample_;
// Mutate a random number of times, in case one is not enough to
// reach another failure, but prefer a low number of mutations (thus Zipf).
for (int num_mutations = absl::Zipf(prng, 10); num_mutations >= 0;
--num_mutations) {
params_domain_.Mutate(copy.args, prng, {}, true);
}
// Only run it if it actually is different. Random mutations might
// not actually change the value, or we have reached a minimum that can't be
// minimized anymore.
if (SerializeIRObject(params_domain_.SerializeCorpus(
minimal_non_fatal_counterexample_->args)) !=
SerializeIRObject(params_domain_.SerializeCorpus(copy.args))) {
runtime_.SetExternalFailureDetected(false);
RunOneInput(copy);
if (runtime_.external_failure_detected()) {
// Found a smaller one, record it and reset the counter.
minimal_non_fatal_counterexample_ = std::move(copy);
tries_without_failure = 0;
continue;
}
}
++tries_without_failure;
}
}
bool FuzzTestFuzzerImpl::RunInFuzzingMode(int* /*argc*/, char*** /*argv*/,
const Configuration& configuration) {
if (IsSilenceTargetEnabled()) SilenceTargetStdoutAndStderr();
runtime_.SetCurrentTest(&test_, &configuration);
runtime_.EnableReporter(&stats_, [] { return absl::Now(); });
runtime_.SetSkippingRequested(false);
bool success = false;
fixture_driver_->RunFuzzTest([&] {
if (runtime_.skipping_requested()) {
absl::FPrintF(GetStderr(),
"[.] Skipping %s per request from the test setup.\n",
test_.full_name());
success = true;
return;
}
[[maybe_unused]] auto watchdog = runtime_.CreateWatchdog();
PopulateLimits(configuration, execution_coverage_);
runtime_.SetRunMode(RunMode::kFuzz);
if (ReplayInputsIfAvailable(configuration)) {
// If ReplayInputs returns, it means the replay didn't crash.
// We don't want to actually run the fuzzer so exit now.
success = true;
return;
}
if (execution_coverage_ == nullptr) {
absl::FPrintF(
GetStderr(),
"\n\n[!] To fuzz, please build with --config=fuzztest.\n\n\n");
return;
}
stats_.total_edges = execution_coverage_->GetCounterMap().size();
PRNG prng(seed_sequence_);
if (MinimizeCorpusIfInMinimizationMode(prng)) {
absl::FPrintF(
GetStderr(),
"[*] Selected %d corpus inputs in minimization mode - exiting.\n",
stats_.useful_inputs);
success = true;
return;
}
CorpusDatabase corpus_database(configuration);
PopulateFromSeeds(
corpus_database.GetCoverageInputsIfAny(test_.full_name()));
InitializeCorpus(prng);
FUZZTEST_CHECK(!corpus_.empty())
<< "No seed input coverage registered. Test function "
"might be uninstrumented?";
constexpr size_t kRunsPerInit = 32000;
size_t next_init = kRunsPerInit;
if (const char* max_fuzzing_runs_env =
getenv("FUZZTEST_MAX_FUZZING_RUNS")) {
if (size_t max_fuzzing_runs;
absl::SimpleAtoi(max_fuzzing_runs_env, &max_fuzzing_runs)) {
absl::FPrintF(GetStderr(), "[.] Limiting to %d fuzzing runs.\n",
max_fuzzing_runs);
runs_limit_ = stats_.runs + max_fuzzing_runs;
} else {
absl::FPrintF(GetStderr(),
"[!] Failed to parse FUZZTEST_MAX_FUZZING_RUNS as "
"non-negative integer - will not limit fuzzing runs.\n");
}
}
const auto time_limit_per_test = configuration.GetTimeLimitPerTest();
if (time_limit_per_test != absl::InfiniteDuration()) {
absl::FPrintF(GetStderr(), "[.] Fuzzing timeout set to: %s\n",
absl::FormatDuration(time_limit_per_test));
time_limit_ = stats_.start_time + time_limit_per_test;
}
runtime_.SetShouldTerminateOnNonFatalFailure(false);
auto try_input_and_process_counterexample = [&](Input input) -> void {
TrySampleAndUpdateInMemoryCorpus(std::move(input));
if (minimal_non_fatal_counterexample_.has_value()) {
// We found a failure, let's minimize it here.
MinimizeNonFatalFailureLocally(prng);
// Once we have minimized enough, let it crash with the best sample we
// got.
// TODO(sbenzaquen): Consider a different approach where we don't retry
// the failing sample to force a crash. Instead, we could store the
// information from the first failure and generate a report manually.
runtime_.SetShouldTerminateOnNonFatalFailure(true);
runtime_.SetExternalFailureDetected(false);
RunOneInput(*minimal_non_fatal_counterexample_);
}
};
// First briefly try the initial values to account for seeded domains and
// possible special values.
constexpr int kInitialValuesToTry = 32;
for (int i = 0; i < kInitialValuesToTry && !ShouldStop(); ++i) {
try_input_and_process_counterexample({params_domain_.Init(prng)});
}
MutationMetadata mutation_metadata;
if (auto* coverage = GetExecutionCoverage(); coverage != nullptr) {
mutation_metadata.cmp_tables = &coverage->GetTablesOfRecentCompares();
}
// Fuzz corpus elements in round robin fashion.
while (!ShouldStop()) {
Input input_to_mutate = [&]() -> Input {
// Try a brand new random element every now and then.
// Otherwise, go to next corpus element in queue.
if (stats_.runs > next_init) {
next_init = stats_.runs + kRunsPerInit;
return {params_domain_.Init(prng)};
} else {
size_t idx = static_cast<size_t>(corpus_distribution_(prng));
FUZZTEST_CHECK(0 <= idx && idx < corpus_.size())
<< "Corpus input weights are outdated!\n";
return corpus_[idx];
}
}();
constexpr int kMutationsPerInput = 32;
for (int i = 0; i < kMutationsPerInput; ++i) {
if (ShouldStop()) break;
Input mutation = input_to_mutate;
MutateValue(mutation, prng, mutation_metadata);
try_input_and_process_counterexample(std::move(mutation));
}
}
absl::FPrintF(GetStderr(), "\n[.] Fuzzing was terminated.\n");
runtime_.PrintFinalStatsOnDefaultSink();
absl::FPrintF(GetStderr(), "\n");
success = true;
});
runtime_.DisableReporter();
runtime_.SetCurrentTest(nullptr, nullptr);
return success;
}
} // namespace fuzztest::internal