use std::borrow::Borrow;
use std::marker::PhantomData;
use std::path::Path;
use std::result::Result;
use std::time::Duration;
use fuzzcheck_common::arg::{
options_parser, Arguments, ArgumentsError, FuzzerCommand, COMMAND_FUZZ, COMMAND_MINIFY_INPUT, INPUT_FILE_FLAG,
};
use crate::code_coverage_sensor::CodeCoverageSensor;
use crate::fuzzer::{Fuzzer, FuzzingResult};
use crate::sensors_and_pools::{
AndPool, DifferentObservations, MaximiseEachCounterPool, MaximiseObservationPool, MostNDiversePool,
SameObservations, SimplestToActivateCounterPool, WrapperSensor,
};
#[cfg(feature = "serde_json_serializer")]
use crate::SerdeSerializer;
use crate::{
split_string_by_whitespace, CompatibleWithObservations, DefaultMutator, Mutator, PoolExt, Sensor, SensorExt,
Serializer,
};
pub trait FuzzTestFunction<T, FT: ?Sized, ImplId> {
type NormalizedFunction: for<'a> Fn(&'a T) -> bool;
fn test_function(self) -> Self::NormalizedFunction;
}
pub enum ReturnBool {}
pub enum ReturnVoid {}
pub enum ReturnResult {}
impl<T, FT: ?Sized, F> FuzzTestFunction<T, FT, ReturnBool> for F
where
T: Borrow<FT>,
F: Fn(&FT) -> bool,
{
type NormalizedFunction = impl Fn(&T) -> bool;
#[no_coverage]
fn test_function(self) -> Self::NormalizedFunction {
#[no_coverage]
move |x| (self)(x.borrow())
}
}
impl<T, FT: ?Sized, F> FuzzTestFunction<T, FT, ReturnVoid> for F
where
T: Borrow<FT>,
F: Fn(&FT),
{
type NormalizedFunction = impl Fn(&T) -> bool;
#[no_coverage]
fn test_function(self) -> Self::NormalizedFunction {
#[no_coverage]
move |x| {
self(x.borrow());
true
}
}
}
impl<T, FT: ?Sized, F, S, E> FuzzTestFunction<T, FT, ReturnResult> for F
where
T: Borrow<FT>,
F: Fn(&FT) -> Result<E, S>,
{
type NormalizedFunction = impl Fn(&T) -> bool;
#[no_coverage]
fn test_function(self) -> Self::NormalizedFunction {
move |x| self(x.borrow()).is_ok()
}
}
pub struct FuzzerBuilder1<T, F>
where
T: ?Sized,
F: Fn(&T) -> bool + 'static,
{
test_function: F,
_phantom: PhantomData<*const T>,
}
pub struct FuzzerBuilder2<F, M, V>
where
F: Fn(&V) -> bool + 'static,
V: Clone + 'static,
M: Mutator<V>,
{
test_function: F,
mutator: M,
_phantom: PhantomData<*const V>,
}
pub struct FuzzerBuilder3<F, M, V>
where
F: Fn(&V) -> bool + 'static,
V: Clone + 'static,
M: Mutator<V>,
{
test_function: F,
mutator: M,
serializer: Box<dyn Serializer<Value = V>>,
_phantom: PhantomData<*const V>,
}
pub struct FuzzerBuilder4<F, M, V, Sens, P>
where
F: Fn(&V) -> bool + 'static,
V: Clone + 'static,
M: Mutator<V>,
Sens: Sensor,
P: CompatibleWithObservations<Sens::Observations>,
{
test_function: F,
mutator: M,
serializer: Box<dyn Serializer<Value = V>>,
sensor: Sens,
pool: P,
_phantom: PhantomData<*const V>,
}
pub struct FuzzerBuilder5<F, M, V, Sens, P>
where
F: Fn(&V) -> bool + 'static,
V: Clone + 'static,
M: Mutator<V>,
Sens: Sensor,
P: CompatibleWithObservations<Sens::Observations>,
{
test_function: F,
mutator: M,
serializer: Box<dyn Serializer<Value = V>>,
sensor: Sens,
pool: P,
arguments: Arguments,
_phantom: PhantomData<*const V>,
}
#[no_coverage]
pub fn fuzz_test<T, F, TestFunctionKind>(test_function: F) -> FuzzerBuilder1<T::Owned, F::NormalizedFunction>
where
T: ?Sized + ToOwned + 'static,
T::Owned: Clone,
F: FuzzTestFunction<T::Owned, T, TestFunctionKind>,
{
FuzzerBuilder1 {
test_function: test_function.test_function(),
_phantom: PhantomData,
}
}
#[cfg(feature = "serde_json_serializer")]
impl<T, F> FuzzerBuilder1<T, F>
where
T: ?Sized + ToOwned + 'static,
T::Owned: Clone + serde::Serialize + for<'e> serde::Deserialize<'e> + DefaultMutator,
<T::Owned as DefaultMutator>::Mutator: 'static,
F: Fn(&T) -> bool,
F: FuzzTestFunction<T::Owned, T, ReturnBool>,
{
#[doc(cfg(feature = "serde_json_serializer"))]
#[no_coverage]
pub fn default_options(
self,
) -> FuzzerBuilder5<
F::NormalizedFunction,
<T::Owned as DefaultMutator>::Mutator,
T::Owned,
DiverseAndMaxHitsSensor,
BasicAndDiverseAndMaxHitsPool,
> {
self.mutator(<T::Owned as DefaultMutator>::default_mutator())
.serializer(SerdeSerializer::default())
.default_sensor_and_pool()
.arguments_from_cargo_fuzzcheck()
}
}
impl<T, F> FuzzerBuilder1<T, F>
where
T: ?Sized + ToOwned + 'static,
T::Owned: Clone + DefaultMutator,
<T::Owned as DefaultMutator>::Mutator: 'static,
F: Fn(&T) -> bool,
F: FuzzTestFunction<T::Owned, T, ReturnBool>,
{
#[no_coverage]
pub fn default_mutator(
self,
) -> FuzzerBuilder2<F::NormalizedFunction, <T::Owned as DefaultMutator>::Mutator, T::Owned> {
self.mutator(<T::Owned as DefaultMutator>::default_mutator())
}
}
impl<T, F> FuzzerBuilder1<T, F>
where
T: ?Sized,
F: Fn(&T) -> bool,
{
#[no_coverage]
pub fn mutator<M, V>(self, mutator: M) -> FuzzerBuilder2<F::NormalizedFunction, M, V>
where
V: Clone + Borrow<T>,
F: FuzzTestFunction<V, T, ReturnBool>,
M: Mutator<V>,
{
FuzzerBuilder2 {
test_function: self.test_function.test_function(),
mutator,
_phantom: PhantomData,
}
}
}
impl<F, M, V> FuzzerBuilder2<F, M, V>
where
F: Fn(&V) -> bool,
V: Clone + 'static,
M: Mutator<V>,
{
#[no_coverage]
pub fn serializer<S>(self, serializer: S) -> FuzzerBuilder3<F, M, V>
where
S: Serializer<Value = V> + 'static,
{
FuzzerBuilder3 {
test_function: self.test_function,
mutator: self.mutator,
serializer: Box::new(serializer),
_phantom: PhantomData,
}
}
}
#[cfg(feature = "serde_json_serializer")]
impl<F, M, V> FuzzerBuilder2<F, M, V>
where
F: Fn(&V) -> bool,
V: Clone + serde::Serialize + for<'e> serde::Deserialize<'e> + 'static,
M: Mutator<V>,
{
#[no_coverage]
pub fn serde_serializer(self) -> FuzzerBuilder3<F, M, V> {
FuzzerBuilder3 {
test_function: self.test_function,
mutator: self.mutator,
serializer: Box::new(SerdeSerializer::<V>::default()),
_phantom: PhantomData,
}
}
}
impl<F, M, V> FuzzerBuilder3<F, M, V>
where
F: Fn(&V) -> bool,
V: Clone + 'static,
M: Mutator<V>,
{
#[no_coverage]
pub fn default_sensor_and_pool(
self,
) -> FuzzerBuilder4<F, M, V, DiverseAndMaxHitsSensor, BasicAndDiverseAndMaxHitsPool> {
let (sensor, pool) = default_sensor_and_pool().finish();
FuzzerBuilder4 {
test_function: self.test_function,
mutator: self.mutator,
serializer: self.serializer,
sensor,
pool,
_phantom: PhantomData,
}
}
#[no_coverage]
pub fn sensor_and_pool<Sens: Sensor, P: CompatibleWithObservations<Sens::Observations>>(
self,
sensor: Sens,
pool: P,
) -> FuzzerBuilder4<F, M, V, Sens, P> {
FuzzerBuilder4 {
test_function: self.test_function,
mutator: self.mutator,
serializer: self.serializer,
sensor,
pool,
_phantom: PhantomData,
}
}
}
impl<F, M, V, Sens, P> FuzzerBuilder4<F, M, V, Sens, P>
where
F: Fn(&V) -> bool,
V: Clone + 'static,
M: Mutator<V>,
Sens: Sensor,
P: CompatibleWithObservations<Sens::Observations>,
{
#[no_coverage]
pub fn arguments(self, arguments: Arguments) -> FuzzerBuilder5<F, M, V, Sens, P> {
FuzzerBuilder5 {
test_function: self.test_function,
mutator: self.mutator,
serializer: self.serializer,
sensor: self.sensor,
pool: self.pool,
arguments,
_phantom: self._phantom,
}
}
#[no_coverage]
pub fn arguments_from_cargo_fuzzcheck(self) -> FuzzerBuilder5<F, M, V, Sens, P> {
let parser = options_parser();
let mut help = format!(
r#""
fuzzcheck <SUBCOMMAND> [OPTIONS]
SUBCOMMANDS:
{fuzz} Run the fuzz test
{minify} Minify a crashing test input, requires --{input_file}
"#,
fuzz = COMMAND_FUZZ,
minify = COMMAND_MINIFY_INPUT,
input_file = INPUT_FILE_FLAG,
);
help += parser.usage("").as_str();
help += format!(
r#""
## Examples:
fuzzcheck {fuzz}
Launch the fuzzer with default options.
fuzzcheck {minify} --{input_file} "artifacts/crash.json"
Minify the test input defined in the file "artifacts/crash.json".
It will put minified inputs in the folder artifacts/crash.minified/
and name them {{complexity}}-{{hash}}.json.
For example, artifacts/crash.minified/4213--8cd7777109b57b8c.json
is a minified input of complexity 42.13.
"#,
fuzz = COMMAND_FUZZ,
minify = COMMAND_MINIFY_INPUT,
input_file = INPUT_FILE_FLAG,
)
.as_str();
let arguments = std::env::var("FUZZCHECK_ARGS").unwrap();
let arguments = split_string_by_whitespace(&arguments);
let matches = parser.parse(arguments).map_err(ArgumentsError::from);
let arguments = match matches.and_then(
#[no_coverage]
|matches| Arguments::from_matches(&matches, false),
) {
Ok(r) => r,
Err(e) => {
println!("{}\n\n{}", e, help);
std::process::exit(1);
}
};
FuzzerBuilder5 {
test_function: self.test_function,
mutator: self.mutator,
serializer: self.serializer,
sensor: self.sensor,
pool: self.pool,
arguments,
_phantom: PhantomData,
}
}
}
impl<F, M, V, Sens, P> FuzzerBuilder5<F, M, V, Sens, P>
where
F: Fn(&V) -> bool + 'static,
V: Clone + 'static,
M: Mutator<V>,
Sens: Sensor + 'static,
P: CompatibleWithObservations<Sens::Observations> + 'static,
Fuzzer<V, M>: 'static,
{
#[must_use]
#[no_coverage]
pub fn command(self, command: FuzzerCommand) -> Self {
let mut x = self;
x.arguments.command = command;
x
}
#[must_use]
#[no_coverage]
pub fn in_corpus(self, path: Option<&Path>) -> Self {
let mut x = self;
x.arguments.corpus_in = path.map(Path::to_path_buf);
x
}
#[must_use]
#[no_coverage]
pub fn out_corpus(self, path: Option<&Path>) -> Self {
let mut x = self;
x.arguments.corpus_out = path.map(Path::to_path_buf);
x
}
#[must_use]
#[no_coverage]
pub fn artifacts_folder(self, path: Option<&Path>) -> Self {
let mut x = self;
x.arguments.artifacts_folder = path.map(Path::to_path_buf);
x
}
#[must_use]
#[no_coverage]
pub fn maximum_complexity(self, max_input_cplx: f64) -> Self {
let mut x = self;
x.arguments.max_input_cplx = max_input_cplx;
x
}
#[must_use]
#[no_coverage]
pub fn stop_after_iterations(self, number_of_iterations: usize) -> Self {
let mut x = self;
x.arguments.maximum_iterations = number_of_iterations;
x
}
#[must_use]
#[no_coverage]
pub fn stop_after_duration(self, duration: Duration) -> Self {
let mut x = self;
x.arguments.maximum_duration = duration;
x
}
#[must_use]
#[no_coverage]
pub fn stop_after_first_test_failure(self, stop_after_first_test_failure: bool) -> Self {
let mut x = self;
x.arguments.stop_after_first_failure = stop_after_first_test_failure;
x
}
#[no_coverage]
pub fn launch(self) -> FuzzingResult<V> {
let FuzzerBuilder5 {
test_function,
mutator,
serializer,
pool,
sensor,
arguments,
_phantom,
} = self;
crate::fuzzer::launch(
Box::new(test_function),
mutator,
serializer,
Box::new((sensor, pool)),
arguments,
)
}
}
pub type BasicSensor = CodeCoverageSensor;
pub type DiverseSensor = impl WrapperSensor<
Wrapped = CodeCoverageSensor,
Observations = (<CodeCoverageSensor as Sensor>::Observations, usize),
>;
pub type MaxHitsSensor = impl WrapperSensor<
Wrapped = CodeCoverageSensor,
Observations = (<CodeCoverageSensor as Sensor>::Observations, u64),
>;
pub type BasicAndMaxHitsSensor = impl WrapperSensor<
Wrapped = CodeCoverageSensor,
Observations = (<CodeCoverageSensor as Sensor>::Observations, u64),
>;
pub type DiverseAndMaxHitsSensor =
impl Sensor<Observations = (<CodeCoverageSensor as Sensor>::Observations, (usize, u64))>;
pub type BasicPool = SimplestToActivateCounterPool;
pub type DiversePool = AndPool<MostNDiversePool, MaximiseObservationPool<u64>, DifferentObservations>;
pub type MaxHitsPool = AndPool<MaximiseEachCounterPool, MaximiseObservationPool<u64>, DifferentObservations>;
pub type BasicAndDiversePool = AndPool<
AndPool<SimplestToActivateCounterPool, MostNDiversePool, SameObservations>,
MaximiseObservationPool<usize>,
DifferentObservations,
>;
pub type BasicAndMaxHitsPool = AndPool<
AndPool<SimplestToActivateCounterPool, MaximiseEachCounterPool, SameObservations>,
MaximiseObservationPool<u64>,
DifferentObservations,
>;
pub type BasicAndDiverseAndMaxHitsPool = AndPool<
AndPool<
AndPool<SimplestToActivateCounterPool, MostNDiversePool, SameObservations>,
MaximiseEachCounterPool,
SameObservations,
>,
AndPool<MaximiseObservationPool<usize>, MaximiseObservationPool<u64>, DifferentObservations>,
DifferentObservations,
>;
#[no_coverage]
pub fn max_cov_hits_sensor_and_pool() -> SensorAndPoolBuilder<MaxHitsSensor, MaxHitsPool> {
let sensor = CodeCoverageSensor::observing_only_files_from_current_dir();
let nbr_counters = sensor.count_instrumented;
let sensor = sensor.map(
#[no_coverage]
|o| {
let sum = o
.iter()
.map(
#[no_coverage]
|(_, count)| count,
)
.sum::<u64>();
(o, sum)
},
);
let pool = MaximiseEachCounterPool::new("max_each_cov_hits", nbr_counters).and(
MaximiseObservationPool::new("max_total_cov_hits"),
Some(0.1),
DifferentObservations,
);
SensorAndPoolBuilder { sensor, pool }
}
#[no_coverage]
pub fn basic_sensor_and_pool() -> SensorAndPoolBuilder<BasicSensor, BasicPool> {
let sensor = CodeCoverageSensor::observing_only_files_from_current_dir();
let nbr_counters = sensor.count_instrumented;
SensorAndPoolBuilder {
sensor,
pool: SimplestToActivateCounterPool::new("simplest_cov", nbr_counters),
}
}
#[no_coverage]
pub fn default_sensor_and_pool() -> SensorAndPoolBuilder<DiverseAndMaxHitsSensor, BasicAndDiverseAndMaxHitsPool> {
basic_sensor_and_pool()
.find_most_diverse_set_of_test_cases(20)
.find_test_cases_repeatedly_hitting_coverage_counters()
}
pub struct SensorAndPoolBuilder<S, P>
where
S: Sensor,
P: CompatibleWithObservations<S::Observations>,
{
sensor: S,
pool: P,
}
impl<S, P> SensorAndPoolBuilder<S, P>
where
S: Sensor,
P: CompatibleWithObservations<S::Observations>,
{
#[no_coverage]
pub fn finish(self) -> (S, P) {
(self.sensor, self.pool)
}
}
impl SensorAndPoolBuilder<BasicSensor, BasicPool> {
#[no_coverage]
pub fn find_most_diverse_set_of_test_cases(
self,
size: usize,
) -> SensorAndPoolBuilder<DiverseSensor, BasicAndDiversePool> {
let nbr_counters = self.sensor.count_instrumented;
let sensor = self.sensor.map(
#[no_coverage]
|o| {
let len = o.len();
(o, len)
},
);
let pool = self
.pool
.and(
MostNDiversePool::new(&format!("diverse_cov_{}", size), size, nbr_counters),
Some(0.1),
SameObservations,
)
.and(
MaximiseObservationPool::<usize>::new("diverse_cov_1"),
Some(0.01),
DifferentObservations,
);
SensorAndPoolBuilder { sensor, pool }
}
#[no_coverage]
pub fn find_test_cases_repeatedly_hitting_coverage_counters(
self,
) -> SensorAndPoolBuilder<BasicAndMaxHitsSensor, BasicAndMaxHitsPool> {
let nbr_counters = self.sensor.count_instrumented;
let sensor = self.sensor.map(
#[no_coverage]
|o| {
let sum = o
.iter()
.map(
#[no_coverage]
|(_, count)| count,
)
.sum::<u64>();
(o, sum)
},
);
let pool = self
.pool
.and(
MaximiseEachCounterPool::new("max_each_cov_hits", nbr_counters),
Some(0.1),
SameObservations,
)
.and(
MaximiseObservationPool::<u64>::new("max_total_cov_hits"),
Some(0.01),
DifferentObservations,
);
SensorAndPoolBuilder { sensor, pool }
}
}
impl SensorAndPoolBuilder<DiverseSensor, BasicAndDiversePool> {
#[no_coverage]
pub fn find_test_cases_repeatedly_hitting_coverage_counters(
self,
) -> SensorAndPoolBuilder<DiverseAndMaxHitsSensor, BasicAndDiverseAndMaxHitsPool> {
let nbr_counters = self.sensor.wrapped().count_instrumented;
let sensor = self.sensor.map(
#[no_coverage]
|o| {
let sum =
o.0.iter()
.map(
#[no_coverage]
|(_, count)| count,
)
.sum::<u64>();
(o.0, (o.1, sum))
},
);
let pool = self
.pool
.p1
.and(
MaximiseEachCounterPool::new("max_each_cov_hits", nbr_counters),
Some(0.1),
SameObservations,
)
.and(
self.pool.p2.and(
MaximiseObservationPool::<u64>::new("max_total_cov_hits"),
Some(0.01),
DifferentObservations,
),
None,
DifferentObservations,
);
SensorAndPoolBuilder { sensor, pool }
}
}