blob: 541ffb0e10432711e85bfa0d17e402171389288d [file] [log] [blame] [edit]
// 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.
#ifndef FUZZTEST_FUZZTEST_INTERNAL_RUNTIME_H_
#define FUZZTEST_FUZZTEST_INTERNAL_RUNTIME_H_
#include <atomic>
#include <cstddef>
#include <cstdio>
#include <deque>
#include <iostream>
#include <memory>
#include <optional>
#include <random>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "absl/functional/function_ref.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/random/discrete_distribution.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 "absl/types/span.h"
#include "./fuzztest/domain.h"
#include "./fuzztest/internal/configuration.h"
#include "./fuzztest/internal/coverage.h"
#include "./fuzztest/internal/fixture_driver.h"
#include "./fuzztest/internal/io.h"
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/registration.h"
#include "./fuzztest/internal/seed_seq.h"
#include "./fuzztest/internal/serialization.h"
#include "./fuzztest/internal/type_support.h"
namespace fuzztest {
// The mode in which we are running the fuzz tests.
enum class RunMode {
// Run without instrumentation and coverage-guidance for a short time.
kUnitTest,
// Run coverage-guided fuzzing until a failure is detected or the test is
// manually terminated.
kFuzz
};
namespace internal {
class FuzzTestFuzzer {
public:
virtual ~FuzzTestFuzzer() = default;
virtual void RunInUnitTestMode(const Configuration& configuration) = 0;
// Returns fuzzing mode's exit code. Zero indicates success.
virtual int RunInFuzzingMode(int* argc, char*** argv,
const Configuration& configuration) = 0;
};
class FuzzTest;
using FuzzTestFuzzerFactory =
absl::AnyInvocable<std::unique_ptr<FuzzTestFuzzer>(const FuzzTest&) const>;
class FuzzTest {
public:
FuzzTest(BasicTestInfo test_info, FuzzTestFuzzerFactory factory)
: test_info_(std::move(test_info)), make_(std::move(factory)) {}
const std::string& suite_name() const { return test_info_.suite_name; }
const std::string& test_name() const { return test_info_.test_name; }
std::string full_name() const {
return absl::StrCat(test_info_.suite_name, ".", test_info_.test_name);
}
const std::string& file() const { return test_info_.file; }
int line() const { return test_info_.line; }
bool uses_fixture() const { return test_info_.uses_fixture; }
auto make() const { return make_(*this); }
private:
BasicTestInfo test_info_;
FuzzTestFuzzerFactory make_;
};
struct RuntimeStats {
absl::Time start_time;
size_t runs;
size_t edges_covered;
size_t total_edges;
// Number of executed inputs that increase coverage.
size_t useful_inputs;
size_t max_stack_used;
};
void InstallSignalHandlers(FILE* report_out);
// This class encapsulates the runtime state that is global by necessity.
// The state is accessed by calling `Runtime::instance()`, which handles the
// necessary initialization steps.
class Runtime {
public:
static Runtime& instance() {
static auto* runtime = new Runtime();
return *runtime;
}
void SetExternalFailureDetected(bool v) {
external_failure_was_detected_.store(v, std::memory_order_relaxed);
}
bool external_failure_detected() const {
return external_failure_was_detected_.load(std::memory_order_relaxed);
}
void SetShouldTerminateOnNonFatalFailure(bool v) {
should_terminate_on_non_fatal_failure_ = v;
}
bool should_terminate_on_non_fatal_failure() const {
return should_terminate_on_non_fatal_failure_;
}
void SetTerminationRequested() {
termination_requested_.store(true, std::memory_order_relaxed);
}
bool termination_requested() const {
return termination_requested_.load(std::memory_order_relaxed);
}
void SetRunMode(RunMode run_mode) { run_mode_ = run_mode; }
RunMode run_mode() const { return run_mode_; }
void SetFuzzTimeLimit(absl::Duration fuzz_time_limit) {
fuzz_time_limit_ = fuzz_time_limit;
}
absl::Duration fuzz_time_limit() const { return fuzz_time_limit_; }
void EnableReporter(const RuntimeStats* stats, absl::Time (*clock_fn)()) {
reporter_enabled_ = true;
stats_ = stats;
clock_fn_ = clock_fn;
// In case we have not installed them yet, do so now.
InstallSignalHandlers(GetStderr());
}
void DisableReporter() { reporter_enabled_ = false; }
struct Args {
const GenericDomainCorpusType& corpus_value;
UntypedDomainInterface& domain;
};
void SetCurrentTest(const FuzzTest* test) { current_test_ = test; }
void SetCurrentArgs(Args* args) { current_args_ = args; }
void UnsetCurrentArgs() { current_args_ = nullptr; }
void PrintFinalStats(absl::FormatRawSink out) const;
void PrintFinalStatsOnDefaultSink() const;
void PrintReport(absl::FormatRawSink out) const;
void PrintReportOnDefaultSink() const;
private:
Runtime() = default;
void DumpReproducer(absl::string_view outdir) const;
// Some failures are not necessarily detected by signal handlers or by
// sanitizers. For example, we could have test framework failures like
// `EXPECT_EQ` failures from GoogleTest.
// If such a failure is detected, the external system can set
// `external_failure_was_detected` to true to bubble it up.
// Note: Even though failures should happen within the code under test, they
// could be set from other threads at any moment. We make it an atomic to
// avoid a race condition.
std::atomic<bool> external_failure_was_detected_{false};
// To support in-process minimization for non-fatal failures we signal
// suppress termination until we believe minimization is complete.
bool should_terminate_on_non_fatal_failure_ = true;
// If true, fuzzing should terminate as soon as possible.
// Atomic because it is set from signal handlers.
std::atomic<bool> termination_requested_{false};
RunMode run_mode_ = RunMode::kUnitTest;
absl::Duration fuzz_time_limit_ = absl::InfiniteDuration();
bool reporter_enabled_ = false;
Args* current_args_ = nullptr;
const FuzzTest* current_test_ = nullptr;
const RuntimeStats* stats_ = nullptr;
absl::Time (*clock_fn_)() = nullptr;
};
extern void (*crash_handler_hook)();
template <typename Arg, size_t I, typename Tuple>
decltype(auto) GetDomainOrArbitrary(const Tuple& t) {
if constexpr (I < std::tuple_size_v<Tuple>) {
return std::get<I>(t);
} else {
return Arbitrary<std::decay_t<Arg>>();
}
}
class FuzzTestExternalEngineAdaptor;
class FuzzTestFuzzerImpl : public FuzzTestFuzzer {
public:
explicit FuzzTestFuzzerImpl(
const FuzzTest& test,
std::unique_ptr<UntypedFixtureDriver> fixture_driver);
~FuzzTestFuzzerImpl();
private:
// TODO(fniksic): Refactor to reduce code complexity and improve readability.
void RunInUnitTestMode(const Configuration& configuration) override;
// TODO(fniksic): Refactor to reduce code complexity and improve readability.
int RunInFuzzingMode(int* argc, char*** argv,
const Configuration& configuration) override;
// Use the standard PRNG instead of absl::BitGen because Abseil doesn't
// guarantee seed stability
// (https://abseil.io/docs/cpp/guides/random#seed-stability).
using PRNG = std::mt19937;
using corpus_type = GenericDomainCorpusType;
struct Input {
corpus_type args;
size_t depth = 0;
absl::Duration run_time = absl::ZeroDuration();
};
struct RunResult {
bool new_coverage;
absl::Duration run_time;
};
void PopulateFromSeeds(const std::vector<std::string>& corpus_files);
bool ReplayInputsIfAvailable(const Configuration& configuration);
std::optional<std::vector<std::string>> GetFilesToReplay();
std::optional<corpus_type> ReadReproducerToMinimize();
std::optional<corpus_type> TryParse(absl::string_view data);
void MutateValue(Input& input, absl::BitGenRef prng);
void UpdateCorpusDistribution();
void MinimizeNonFatalFailureLocally(absl::BitGenRef prng);
// Runs on `sample` and returns new coverage and run time. If there's new
// coverage, outputs updated runtime stats. Additionally, if `write_to_file`
// is true, tries to write the sample to a file.
RunResult TrySample(const Input& sample, bool write_to_file = true);
// Runs on `sample` and records it into the in-memory corpus if it finds new
// coverage. If `write_to_file` is set, tries to write the corpus data to a
// file when recording it. Updates the memory dictionary on new coverage, and
// occasionally even if there is no new coverage.
void TrySampleAndUpdateInMemoryCorpus(Input sample,
bool write_to_file = true);
void ForEachInputFile(absl::Span<const std::string> files,
absl::FunctionRef<void(Input&&)> consume);
// Returns true if we're in minimization mode.
bool MinimizeCorpusIfInMinimizationMode(absl::BitGenRef prng);
std::vector<Input> TryReadCorpusFromFiles();
void TryWriteCorpusFile(const Input& input);
void InitializeCorpus(absl::BitGenRef prng);
RunResult RunOneInput(const Input& input);
bool ShouldStop();
std::optional<GenericDomainCorpusType> GetCorpusValueFromFile(
const std::string& path);
void ReplayInput(const std::string& path);
const FuzzTest& test_;
std::unique_ptr<UntypedFixtureDriver> fixture_driver_;
std::unique_ptr<UntypedDomainInterface> params_domain_;
std::seed_seq seed_sequence_ = GetFromEnvOrMakeSeedSeq(std::cerr);
ExecutionCoverage* execution_coverage_;
CorpusCoverage corpus_coverage_;
std::deque<Input> corpus_;
// Corpus distribution is only used in Fuzzing mode.
absl::discrete_distribution<> corpus_distribution_;
absl::string_view corpus_out_dir_;
RuntimeStats stats_{};
std::optional<size_t> runs_limit_;
absl::Time time_limit_ = absl::InfiniteFuture();
std::optional<Input> minimal_non_fatal_counterexample_;
Runtime& runtime_ = Runtime::instance();
#ifdef FUZZTEST_COMPATIBILITY_MODE
friend class FuzzTestExternalEngineAdaptor;
#endif // FUZZTEST_COMPATIBILITY_MODE
// Defined in centipede_adaptor.cc
friend class CentipedeFuzzerAdaptor;
friend class CentipedeAdaptorRunnerCallbacks;
friend class CentipedeAdaptorEngineCallbacks;
};
} // namespace internal
} // namespace fuzztest
#endif // FUZZTEST_FUZZTEST_INTERNAL_RUNTIME_H_