Add test case minimising stage (tmin) (#735)

* add test case minimising stage

* general purpose minimiser impl, with fuzzer example

* reorganise, document, and other cleanup

* correct python API return value

* correct some docs

* nit: versioning in fuzzers

* ise -> ize
This commit is contained in:
Addison Crump 2022-08-29 06:37:55 -05:00 committed by GitHub
parent 556bdc828c
commit d6e72560dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 740 additions and 90 deletions

View File

@ -0,0 +1,3 @@
corpus
minimized
solutions

View File

@ -0,0 +1,23 @@
[package]
name = "baby_fuzzer_minimizing"
version = "0.8.1"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>", "Addison Crump <research@addisoncrump.info>"]
edition = "2021"
[features]
default = ["std"]
tui = []
std = []
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3
debug = true
[dependencies]
libafl = { path = "../../libafl/" }

View File

@ -0,0 +1,9 @@
# Baby fuzzer
This is a minimalistic example about how to create a libafl based fuzzer which leverages minimisation.
The fuzzer steps until a crash occurs, minimising each corpus entry as it is discovered. Then, once a
solution is found, it attempts to minimise that as well.
The tested program is a simple Rust function without any instrumentation.
For real fuzzing, you will want to add some sort to add coverage or other feedback.

View File

@ -0,0 +1,142 @@
use std::path::PathBuf;
#[cfg(windows)]
use std::ptr::write_volatile;
use libafl::prelude::*;
/// Coverage map with explicit assignments due to the lack of instrumentation
static mut SIGNALS: [u8; 16] = [0; 16];
/// Assign a signal to the signals map
fn signals_set(idx: usize) {
unsafe { SIGNALS[idx] = 1 };
}
#[allow(clippy::similar_names)]
pub fn main() -> Result<(), Error> {
// The closure that we want to fuzz
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target.as_slice();
signals_set(0);
if !buf.is_empty() && buf[0] == b'a' {
signals_set(1);
if buf.len() > 1 && buf[1] == b'b' {
signals_set(2);
if buf.len() > 2 && buf[2] == b'c' {
return ExitKind::Crash;
}
}
}
ExitKind::Ok
};
// Create an observation channel using the signals map
let observer =
unsafe { StdMapObserver::new_from_ptr("signals", SIGNALS.as_mut_ptr(), SIGNALS.len()) };
let factory = MapEqualityFactory::new_from_observer(&observer);
// Feedback to rate the interestingness of an input
let mut feedback = MaxMapFeedback::new(&observer);
// A feedback to choose if an input is a solution or not
let mut objective = CrashFeedback::new();
// The Monitor trait define how the fuzzer stats are displayed to the user
let mon = SimpleMonitor::new(|s| println!("{}", s));
let mut mgr = SimpleEventManager::new(mon);
let corpus_dir = PathBuf::from("./corpus");
let solution_dir = PathBuf::from("./solutions");
// create a State from scratch
let mut state = StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
OnDiskCorpus::new(&corpus_dir).unwrap(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(&solution_dir).unwrap(),
// States of the feedbacks.
// The feedbacks can report the data that should persist in the State.
&mut feedback,
// Same for objective feedbacks
&mut objective,
)
.unwrap();
// A queue policy to get testcasess from the corpus
let scheduler = QueueScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// Create the executor for an in-process function with just one observer
let mut executor = InProcessExecutor::new(
&mut harness,
tuple_list!(observer),
&mut fuzzer,
&mut state,
&mut mgr,
)
.expect("Failed to create the Executor");
// Generator of printable bytearrays of max size 32
let mut generator = RandPrintablesGenerator::new(32);
// Generate 8 initial inputs
state
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let minimizer = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(
StdMutationalStage::new(mutator),
StdTMinMutationalStage::new(minimizer, factory, 128)
);
while state.solutions().is_empty() {
fuzzer.fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr)?;
}
let minimized_dir = PathBuf::from("./minimized");
let mut state = StdState::new(
StdRand::with_seed(current_nanos()),
OnDiskCorpus::new(&minimized_dir).unwrap(),
InMemoryCorpus::new(),
&mut (),
&mut (),
)
.unwrap();
// The Monitor trait define how the fuzzer stats are displayed to the user
let mon = SimpleMonitor::new(|s| println!("{}", s));
let mut mgr = SimpleEventManager::new(mon);
let minimizer = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdTMinMutationalStage::new(
minimizer,
CrashFeedbackFactory::default(),
1 << 10
));
let scheduler = QueueScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, (), ());
// Create the executor for an in-process function with just one observer
let mut executor = InProcessExecutor::new(&mut harness, (), &mut fuzzer, &mut state, &mut mgr)?;
state.load_initial_inputs_forced(&mut fuzzer, &mut executor, &mut mgr, &[solution_dir])?;
stages.perform_all(&mut fuzzer, &mut executor, &mut state, &mut mgr, 0)?;
Ok(())
}

View File

@ -46,7 +46,7 @@ where
/// Replaces the testcase at the given idx
#[inline]
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<(), Error> {
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
// TODO finish
self.inner.replace(idx, testcase)
}

View File

@ -41,12 +41,11 @@ where
/// Replaces the testcase at the given idx
#[inline]
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<(), Error> {
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
if idx >= self.entries.len() {
return Err(Error::key_not_found(format!("Index {} out of bounds", idx)));
}
self.entries[idx] = RefCell::new(testcase);
Ok(())
Ok(self.entries[idx].replace(testcase))
}
/// Removes an entry from the corpus, returning it if it was present.

View File

@ -36,8 +36,8 @@ where
/// Add an entry to the corpus and return its index
fn add(&mut self, testcase: Testcase<I>) -> Result<usize, Error>;
/// Replaces the testcase at the given idx
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<(), Error>;
/// Replaces the testcase at the given idx, returning the existing.
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<Testcase<I>, Error>;
/// Removes an entry from the corpus, returning it if it was present.
fn remove(&mut self, idx: usize) -> Result<Option<Testcase<I>>, Error>;
@ -177,7 +177,11 @@ pub mod pybind {
}
#[inline]
fn replace(&mut self, idx: usize, testcase: Testcase<BytesInput>) -> Result<(), Error> {
fn replace(
&mut self,
idx: usize,
testcase: Testcase<BytesInput>,
) -> Result<Testcase<BytesInput>, Error> {
unwrap_me_mut!(self.wrapper, c, { c.replace(idx, testcase) })
}

View File

@ -67,6 +67,92 @@ where
/// Add an entry to the corpus and return its index
#[inline]
fn add(&mut self, mut testcase: Testcase<I>) -> Result<usize, Error> {
self.save_testcase(&mut testcase)?;
self.entries.push(RefCell::new(testcase));
Ok(self.entries.len() - 1)
}
/// Replaces the testcase at the given idx
#[inline]
fn replace(&mut self, idx: usize, mut testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
if idx >= self.entries.len() {
return Err(Error::key_not_found(format!("Index {} out of bounds", idx)));
}
self.save_testcase(&mut testcase)?;
let previous = self.entries[idx].replace(testcase);
self.remove_testcase(&previous)?;
Ok(previous)
}
/// Removes an entry from the corpus, returning it if it was present.
#[inline]
fn remove(&mut self, idx: usize) -> Result<Option<Testcase<I>>, Error> {
if idx >= self.entries.len() {
Ok(None)
} else {
let prev = self.entries.remove(idx).into_inner();
self.remove_testcase(&prev)?;
Ok(Some(prev))
}
}
/// Get by id
#[inline]
fn get(&self, idx: usize) -> Result<&RefCell<Testcase<I>>, Error> {
Ok(&self.entries[idx])
}
/// Current testcase scheduled
#[inline]
fn current(&self) -> &Option<usize> {
&self.current
}
/// Current testcase scheduled (mutable)
#[inline]
fn current_mut(&mut self) -> &mut Option<usize> {
&mut self.current
}
}
impl<I> OnDiskCorpus<I>
where
I: Input,
{
/// Creates the [`OnDiskCorpus`].
/// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`.
pub fn new<P>(dir_path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
fn new<I: Input>(dir_path: PathBuf) -> Result<OnDiskCorpus<I>, Error> {
fs::create_dir_all(&dir_path)?;
Ok(OnDiskCorpus {
entries: vec![],
current: None,
dir_path,
meta_format: None,
})
}
new(dir_path.as_ref().to_path_buf())
}
/// Creates the [`OnDiskCorpus`] specifying the type of `Metadata` to be saved to disk.
/// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`.
pub fn new_save_meta(
dir_path: PathBuf,
meta_format: Option<OnDiskMetadataFormat>,
) -> Result<Self, Error> {
fs::create_dir_all(&dir_path)?;
Ok(Self {
entries: vec![],
current: None,
dir_path,
meta_format,
})
}
fn save_testcase(&mut self, testcase: &mut Testcase<I>) -> Result<(), Error> {
if testcase.filename().is_none() {
// TODO walk entry metadata to ask for pieces of filename (e.g. :havoc in AFL)
let file_orig = testcase
@ -128,84 +214,22 @@ where
testcase
.store_input()
.expect("Could not save testcase to disk");
self.entries.push(RefCell::new(testcase));
Ok(self.entries.len() - 1)
}
/// Replaces the testcase at the given idx
#[inline]
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<(), Error> {
if idx >= self.entries.len() {
return Err(Error::key_not_found(format!("Index {} out of bounds", idx)));
}
self.entries[idx] = RefCell::new(testcase);
Ok(())
}
/// Removes an entry from the corpus, returning it if it was present.
#[inline]
fn remove(&mut self, idx: usize) -> Result<Option<Testcase<I>>, Error> {
if idx >= self.entries.len() {
Ok(None)
} else {
Ok(Some(self.entries.remove(idx).into_inner()))
fn remove_testcase(&mut self, testcase: &Testcase<I>) -> Result<(), Error> {
if let Some(filename) = testcase.filename() {
fs::remove_file(filename)?;
}
if self.meta_format.is_some() {
let mut filename = PathBuf::from(testcase.filename().as_ref().unwrap());
filename.set_file_name(format!(
".{}.metadata",
filename.file_name().unwrap().to_string_lossy()
));
fs::remove_file(filename)?;
}
/// Get by id
#[inline]
fn get(&self, idx: usize) -> Result<&RefCell<Testcase<I>>, Error> {
Ok(&self.entries[idx])
}
/// Current testcase scheduled
#[inline]
fn current(&self) -> &Option<usize> {
&self.current
}
/// Current testcase scheduled (mutable)
#[inline]
fn current_mut(&mut self) -> &mut Option<usize> {
&mut self.current
}
}
impl<I> OnDiskCorpus<I>
where
I: Input,
{
/// Creates the [`OnDiskCorpus`].
/// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`.
pub fn new<P>(dir_path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
fn new<I: Input>(dir_path: PathBuf) -> Result<OnDiskCorpus<I>, Error> {
fs::create_dir_all(&dir_path)?;
Ok(OnDiskCorpus {
entries: vec![],
current: None,
dir_path,
meta_format: None,
})
}
new(dir_path.as_ref().to_path_buf())
}
/// Creates the [`OnDiskCorpus`] specifying the type of `Metadata` to be saved to disk.
/// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`.
pub fn new_save_meta(
dir_path: PathBuf,
meta_format: Option<OnDiskMetadataFormat>,
) -> Result<Self, Error> {
fs::create_dir_all(&dir_path)?;
Ok(Self {
entries: vec![],
current: None,
dir_path,
meta_format,
})
Ok(())
}
}
#[cfg(feature = "python")]

View File

@ -296,6 +296,61 @@ where
OT: ObserversTuple<I, S>;
}
/// Factory for feedbacks which should be sensitive to an existing context, e.g. observer(s) from a
/// specific execution
pub trait FeedbackFactory<F, I, S, T>
where
F: Feedback<I, S>,
I: Input,
S: HasClientPerfMonitor,
{
/// Create the feedback from the provided context
fn create_feedback(&self, ctx: &T) -> F;
}
impl<FE, FU, I, S, T> FeedbackFactory<FE, I, S, T> for FU
where
FU: Fn(&T) -> FE,
FE: Feedback<I, S>,
I: Input,
S: HasClientPerfMonitor,
{
fn create_feedback(&self, ctx: &T) -> FE {
self(ctx)
}
}
/// A feedback factory which merely invokes `::default()` for the feedback type provided
#[derive(Default, Debug, Copy, Clone)]
pub struct DefaultFeedbackFactory<F>
where
F: Default,
{
phantom: PhantomData<F>,
}
impl<F> DefaultFeedbackFactory<F>
where
F: Default,
{
/// Create the feedback factory
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl<F, I, S, T> FeedbackFactory<F, I, S, T> for DefaultFeedbackFactory<F>
where
F: Feedback<I, S> + Default,
I: Input,
S: HasClientPerfMonitor,
{
fn create_feedback(&self, _ctx: &T) -> F {
F::default()
}
}
/// Eager `OR` combination of two feedbacks
#[derive(Debug, Clone)]
pub struct LogicEagerOr {}
@ -776,6 +831,9 @@ impl Default for CrashFeedback {
}
}
/// A feedback factory for crash feedbacks
pub type CrashFeedbackFactory = DefaultFeedbackFactory<CrashFeedback>;
/// A [`TimeoutFeedback`] reduces the timeout value of a run.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TimeoutFeedback {}
@ -827,6 +885,9 @@ impl Default for TimeoutFeedback {
}
}
/// A feedback factory for timeout feedbacks
pub type TimeoutFeedbackFactory = DefaultFeedbackFactory<TimeoutFeedback>;
/// Nop feedback that annotates execution time in the new testcase, if any
/// for this Feedback, the testcase is never interesting (use with an OR).
/// It decides, if the given [`TimeObserver`] value of a run is interesting.

View File

@ -62,7 +62,6 @@ pub trait Input: Clone + Serialize + serde::de::DeserializeOwned + Debug {
}
/// Load the content of this input from a file
#[cfg(feature = "std")]
fn from_file<P>(path: P) -> Result<Self, Error>
where
P: AsRef<Path>,

View File

@ -39,22 +39,17 @@ pub trait Scheduler<I, S>
where
I: Input,
{
/// Add an entry to the corpus and return its index
/// Added an entry to the corpus at the given index
fn on_add(&self, _state: &mut S, _idx: usize) -> Result<(), Error> {
Ok(())
}
/// Replaces the testcase at the given idx
fn on_replace(
&self,
_state: &mut S,
_idx: usize,
_testcase: &Testcase<I>,
) -> Result<(), Error> {
/// Replaced the given testcase at the given idx
fn on_replace(&self, _state: &mut S, _idx: usize, _prev: &Testcase<I>) -> Result<(), Error> {
Ok(())
}
/// Removes an entry from the corpus, returning it if it was present.
/// Removed the given entry from the corpus at the given index
fn on_remove(
&self,
_state: &mut S,

View File

@ -8,6 +8,11 @@ Other stages may enrich [`crate::corpus::Testcase`]s with metadata.
pub mod mutational;
pub use mutational::{MutationalStage, StdMutationalStage};
pub mod tmin;
pub use tmin::{
MapEqualityFactory, MapEqualityFeedback, StdTMinMutationalStage, TMinMutationalStage,
};
pub mod push;
pub mod tracing;

386
libafl/src/stages/tmin.rs Normal file
View File

@ -0,0 +1,386 @@
//! The [`TMinMutationalStage`] is a stage which will attempt to minimize corpus entries.
use alloc::string::{String, ToString};
use core::{
fmt::Debug,
hash::{Hash, Hasher},
marker::PhantomData,
};
use ahash::AHasher;
#[cfg(feature = "introspection")]
use crate::monitors::PerfFeature;
use crate::{
bolts::{tuples::Named, HasLen},
corpus::{Corpus, Testcase},
events::EventFirer,
executors::{Executor, ExitKind, HasObservers},
feedbacks::{Feedback, FeedbackFactory, HasObserverName},
inputs::Input,
mark_feature_time,
mutators::Mutator,
observers::{MapObserver, ObserversTuple},
schedulers::Scheduler,
stages::Stage,
start_timer,
state::{HasClientPerfMonitor, HasCorpus, HasExecutions, HasMaxSize},
Error, ExecutesInput, ExecutionProcessor, HasFeedback, HasScheduler,
};
/// Mutational stage which minimizes corpus entries.
///
/// You must provide at least one mutator that actually reduces size.
pub trait TMinMutationalStage<CS, E, EM, F1, F2, I, M, OT, S, Z>:
Stage<E, EM, S, Z> + FeedbackFactory<F2, I, S, OT>
where
CS: Scheduler<I, S>,
E: Executor<EM, I, S, Z> + HasObservers<I, OT, S>,
EM: EventFirer<I>,
F1: Feedback<I, S>,
F2: Feedback<I, S>,
I: Input + Hash + HasLen,
M: Mutator<I, S>,
OT: ObserversTuple<I, S>,
S: HasClientPerfMonitor + HasCorpus<I> + HasExecutions + HasMaxSize,
Z: ExecutionProcessor<I, OT, S>
+ ExecutesInput<I, OT, S, Z>
+ HasFeedback<F1, I, S>
+ HasScheduler<CS, I, S>,
{
/// The mutator registered for this stage
fn mutator(&self) -> &M;
/// The mutator registered for this stage (mutable)
fn mutator_mut(&mut self) -> &mut M;
/// Gets the number of iterations this mutator should run for.
fn iterations(&self, state: &mut S, corpus_idx: usize) -> Result<usize, Error>;
/// Runs this (mutational) stage for new objectives
#[allow(clippy::cast_possible_wrap)] // more than i32 stages on 32 bit system - highly unlikely...
fn perform_minification(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut S,
manager: &mut EM,
base_corpus_idx: usize,
) -> Result<(), Error> {
let orig_max_size = state.max_size();
// basically copy-pasted from mutational.rs
let num = self.iterations(state, base_corpus_idx)?;
start_timer!(state);
let mut base = state
.corpus()
.get(base_corpus_idx)?
.borrow_mut()
.load_input()?
.clone();
let mut hasher = AHasher::new_with_keys(0, 0);
base.hash(&mut hasher);
let base_hash = hasher.finish();
mark_feature_time!(state, PerfFeature::GetInputFromCorpus);
fuzzer.execute_input(state, executor, manager, &base)?;
let observers = executor.observers();
let mut feedback = self.create_feedback(observers);
let mut i = 0;
loop {
if i >= num {
break;
}
let mut next_i = i + 1;
let mut input = base.clone();
let before_len = input.len();
state.set_max_size(before_len);
start_timer!(state);
self.mutator_mut().mutate(state, &mut input, i as i32)?;
mark_feature_time!(state, PerfFeature::Mutate);
let corpus_idx = if input.len() < before_len {
// run the input
let exit_kind = fuzzer.execute_input(state, executor, manager, &input)?;
let observers = executor.observers();
// let the fuzzer process this execution -- it's possible that we find something
// interesting, or even a solution
let (_, corpus_idx) = fuzzer.process_execution(
state,
manager,
input.clone(),
observers,
&exit_kind,
false,
)?;
if feedback.is_interesting(state, manager, &input, observers, &exit_kind)? {
// we found a reduced corpus entry! use the smaller base
base = input;
// do more runs! maybe we can minify further
next_i = 0;
}
corpus_idx
} else {
// we can't guarantee that the mutators provided will necessarily reduce size, so
// skip any mutations that actually increase size so we don't waste eval time
None
};
start_timer!(state);
self.mutator_mut().post_exec(state, i as i32, corpus_idx)?;
mark_feature_time!(state, PerfFeature::MutatePostExec);
i = next_i;
}
let mut hasher = AHasher::new_with_keys(0, 0);
base.hash(&mut hasher);
let new_hash = hasher.finish();
if base_hash != new_hash {
let mut testcase = Testcase::with_executions(base, *state.executions());
fuzzer
.feedback_mut()
.append_metadata(state, &mut testcase)?;
let prev = state.corpus_mut().replace(base_corpus_idx, testcase)?;
fuzzer
.scheduler_mut()
.on_replace(state, base_corpus_idx, &prev)?;
}
state.set_max_size(orig_max_size);
Ok(())
}
}
/// The default corpus entry minimising mutational stage
#[derive(Clone, Debug)]
pub struct StdTMinMutationalStage<CS, E, EM, F1, F2, FF, I, M, S, T, Z>
where
I: Input + HasLen,
M: Mutator<I, S>,
{
mutator: M,
factory: FF,
runs: usize,
#[allow(clippy::type_complexity)]
phantom: PhantomData<(CS, E, EM, F1, F2, I, S, T, Z)>,
}
impl<CS, E, EM, F1, F2, FF, I, M, OT, S, Z> Stage<E, EM, S, Z>
for StdTMinMutationalStage<CS, E, EM, F1, F2, FF, I, M, S, OT, Z>
where
CS: Scheduler<I, S>,
E: Executor<EM, I, S, Z> + HasObservers<I, OT, S>,
EM: EventFirer<I>,
F1: Feedback<I, S>,
F2: Feedback<I, S>,
FF: FeedbackFactory<F2, I, S, OT>,
I: Input + Hash + HasLen,
M: Mutator<I, S>,
OT: ObserversTuple<I, S>,
S: HasClientPerfMonitor + HasCorpus<I> + HasExecutions + HasMaxSize,
Z: ExecutionProcessor<I, OT, S>
+ ExecutesInput<I, OT, S, Z>
+ HasFeedback<F1, I, S>
+ HasScheduler<CS, I, S>,
{
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut S,
manager: &mut EM,
corpus_idx: usize,
) -> Result<(), Error> {
self.perform_minification(fuzzer, executor, state, manager, corpus_idx)?;
#[cfg(feature = "introspection")]
state.introspection_monitor_mut().finish_stage();
Ok(())
}
}
impl<CS, E, EM, F1, F2, FF, I, M, S, T, Z> FeedbackFactory<F2, I, S, T>
for StdTMinMutationalStage<CS, E, EM, F1, F2, FF, I, M, S, T, Z>
where
F2: Feedback<I, S>,
FF: FeedbackFactory<F2, I, S, T>,
I: Input + HasLen,
M: Mutator<I, S>,
S: HasClientPerfMonitor,
{
fn create_feedback(&self, ctx: &T) -> F2 {
self.factory.create_feedback(ctx)
}
}
impl<CS, E, EM, F1, F2, FF, I, M, OT, S, Z> TMinMutationalStage<CS, E, EM, F1, F2, I, M, OT, S, Z>
for StdTMinMutationalStage<CS, E, EM, F1, F2, FF, I, M, S, OT, Z>
where
CS: Scheduler<I, S>,
E: HasObservers<I, OT, S> + Executor<EM, I, S, Z>,
EM: EventFirer<I>,
F1: Feedback<I, S>,
F2: Feedback<I, S>,
FF: FeedbackFactory<F2, I, S, OT>,
I: Input + HasLen + Hash,
M: Mutator<I, S>,
OT: ObserversTuple<I, S>,
S: HasClientPerfMonitor + HasCorpus<I> + HasExecutions + HasMaxSize,
Z: ExecutionProcessor<I, OT, S>
+ ExecutesInput<I, OT, S, Z>
+ HasFeedback<F1, I, S>
+ HasScheduler<CS, I, S>,
{
/// The mutator, added to this stage
#[inline]
fn mutator(&self) -> &M {
&self.mutator
}
/// The list of mutators, added to this stage (as mutable ref)
#[inline]
fn mutator_mut(&mut self) -> &mut M {
&mut self.mutator
}
/// Gets the number of iterations from a fixed number of runs
fn iterations(&self, _state: &mut S, _corpus_idx: usize) -> Result<usize, Error> {
Ok(self.runs)
}
}
impl<CS, E, EM, F1, F2, FF, I, M, S, T, Z>
StdTMinMutationalStage<CS, E, EM, F1, F2, FF, I, M, S, T, Z>
where
I: Input + HasLen,
M: Mutator<I, S>,
{
/// Creates a new minimising mutational stage that will minimize provided corpus entries
pub fn new(mutator: M, factory: FF, runs: usize) -> Self {
Self {
mutator,
factory,
runs,
phantom: PhantomData,
}
}
}
/// A feedback which checks if the hash of the currently observed map is equal to the original hash
/// provided
#[derive(Clone, Debug)]
pub struct MapEqualityFeedback<M> {
name: String,
obs_name: String,
orig_hash: u64,
phantom: PhantomData<M>,
}
impl<M> MapEqualityFeedback<M> {
/// Create a new map equality feedback -- can be used with feedback logic
#[must_use]
pub fn new(name: &str, obs_name: &str, orig_hash: u64) -> Self {
MapEqualityFeedback {
name: name.to_string(),
obs_name: obs_name.to_string(),
orig_hash,
phantom: PhantomData,
}
}
}
impl<M> Named for MapEqualityFeedback<M> {
fn name(&self) -> &str {
&self.name
}
}
impl<M> HasObserverName for MapEqualityFeedback<M> {
fn observer_name(&self) -> &str {
&self.obs_name
}
}
impl<I, M, S> Feedback<I, S> for MapEqualityFeedback<M>
where
I: Input,
M: MapObserver,
S: HasClientPerfMonitor,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S>,
{
let obs = observers
.match_name::<M>(self.observer_name())
.expect("Should have been provided valid observer name.");
Ok(obs.hash() == self.orig_hash)
}
}
/// A feedback factory for ensuring that the maps for minimized inputs are the same
#[derive(Debug, Clone)]
pub struct MapEqualityFactory<M> {
obs_name: String,
phantom: PhantomData<M>,
}
impl<M> MapEqualityFactory<M>
where
M: MapObserver,
{
/// Creates a new map equality feedback for the given observer
pub fn new_from_observer(obs: &M) -> Self {
Self {
obs_name: obs.name().to_string(),
phantom: PhantomData,
}
}
}
impl<M> HasObserverName for MapEqualityFactory<M> {
fn observer_name(&self) -> &str {
&self.obs_name
}
}
impl<I, M, OT, S> FeedbackFactory<MapEqualityFeedback<M>, I, S, OT> for MapEqualityFactory<M>
where
I: Input,
M: MapObserver,
OT: ObserversTuple<I, S>,
S: HasClientPerfMonitor,
{
fn create_feedback(&self, observers: &OT) -> MapEqualityFeedback<M> {
let obs = observers
.match_name::<M>(self.observer_name())
.expect("Should have been provided valid observer name.");
MapEqualityFeedback {
name: "MapEq".to_string(),
obs_name: self.obs_name.clone(),
orig_hash: obs.hash(),
phantom: PhantomData,
}
}
}