Introduce disabled testcases for splicing (#1932)

* introduce disabled field to Testcase

* separate executor's processing of execution (adding to corpus/solution/discarding) and execution of input

* introduce add_disabled_input function

* enable splicing mutators to fetch disabled inputs

* reset modified example

* clean up

* update docs

* update docs for count_with_disabled

* fix random_corpus_id for splicing mutator not considering disabled entries

* fmt

* update docs

* clippy

* fix corpus_btreemap not working

* fix clippy warnings

* fix python bindings

* typo in count_with_disabled implementations

* fix certain splicing mutators not considering disabled inputs

* rename count_with_disabled to count_all

* introduce count_disabled function

* update docs for count_all, count_disabled and count

* * introduce get_from_all and nth_from_all for corpus implementations so get() and nth() do not silently fetch disabled entries.
* remove boolean flag from random_corpus_id which allowed inclusion of disabled ids and make it into a new function random_corpus_id_with_disabled
* update docs

* remove boolean is_disabled flag from corpus::insert and make it into a separate function insert_disabled

* rename do_insert to _insert

* make get_from_all inline for cached and inmemory

* add missing functions implementation for PythonCorpus
prevent writing feedback when adding disabled testcases

* fix nth_from_all overfetching enabled corpus entries

* fix clippy & rename execute_with_res to execute_no_process

* refactor _insert for corpus_btreemap

* make LibfuzzerCorpus and ArtifactCorpus to accomodate disabled entries

* fix typo

* fix missing docs for map field

* fix clippy

* test

* (hopefully) fix CachedOnDiskCorpus using incorrect corpus when caching testcase

* fix typo in inmemory_ondisk leading to fetching disabled entry from enabled corpus

---------

Co-authored-by: aarnav <aarnav@srlabs.de>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Aarnav 2024-04-10 01:03:00 +02:00 committed by GitHub
parent d3b3d5d462
commit 47c41c2925
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 644 additions and 204 deletions

View File

@ -37,22 +37,75 @@ where
type Input = I; type Input = I;
} }
impl<I> CachedOnDiskCorpus<I>
where
I: Input,
{
fn cache_testcase<'a>(
&'a self,
testcase: &'a RefCell<Testcase<I>>,
idx: CorpusId,
is_disabled: bool,
) -> Result<(), Error> {
if testcase.borrow().input().is_none() {
self.load_input_into(&mut testcase.borrow_mut())?;
let mut borrowed_num = 0;
while self.cached_indexes.borrow().len() >= self.cache_max_len {
let removed = self.cached_indexes.borrow_mut().pop_front().unwrap();
if let Ok(mut borrowed) = if is_disabled {
self.inner.get_from_all(removed)
} else {
self.inner.get(removed)
}?
.try_borrow_mut()
{
*borrowed.input_mut() = None;
} else {
self.cached_indexes.borrow_mut().push_back(removed);
borrowed_num += 1;
if self.cache_max_len == borrowed_num {
break;
}
}
}
self.cached_indexes.borrow_mut().push_back(idx);
}
Ok(())
}
}
impl<I> Corpus for CachedOnDiskCorpus<I> impl<I> Corpus for CachedOnDiskCorpus<I>
where where
I: Input, I: Input,
{ {
/// Returns the number of elements /// Returns the number of all enabled entries
#[inline] #[inline]
fn count(&self) -> usize { fn count(&self) -> usize {
self.inner.count() self.inner.count()
} }
/// Add an entry to the corpus and return its index /// Returns the number of all disabled entries
fn count_disabled(&self) -> usize {
self.inner.count_disabled()
}
/// Returns the number of elements including disabled entries
#[inline]
fn count_all(&self) -> usize {
self.inner.count_all()
}
/// Add an enabled testcase to the corpus and return its index
#[inline] #[inline]
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> { fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
self.inner.add(testcase) self.inner.add(testcase)
} }
/// Add a disabled testcase to the corpus and return its index
#[inline]
fn add_disabled(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
self.inner.add_disabled(testcase)
}
/// Replaces the testcase at the given idx /// Replaces the testcase at the given idx
#[inline] #[inline]
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> { fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
@ -68,27 +121,18 @@ where
Ok(testcase) Ok(testcase)
} }
/// Get by id /// Get by id; considers only enabled testcases
#[inline] #[inline]
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> { fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
let testcase = { self.inner.get(idx)? }; let testcase = { self.inner.get(idx)? };
if testcase.borrow().input().is_none() { self.cache_testcase(testcase, idx, false)?;
self.load_input_into(&mut testcase.borrow_mut())?; Ok(testcase)
let mut borrowed_num = 0; }
while self.cached_indexes.borrow().len() >= self.cache_max_len { /// Get by id; considers both enabled and disabled testcases
let removed = self.cached_indexes.borrow_mut().pop_front().unwrap(); #[inline]
if let Ok(mut borrowed) = self.inner.get(removed)?.try_borrow_mut() { fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
*borrowed.input_mut() = None; let testcase = { self.inner.get_from_all(idx)? };
} else { self.cache_testcase(testcase, idx, true)?;
self.cached_indexes.borrow_mut().push_back(removed);
borrowed_num += 1;
if self.cache_max_len == borrowed_num {
break;
}
}
}
self.cached_indexes.borrow_mut().push_back(idx);
}
Ok(testcase) Ok(testcase)
} }
@ -124,10 +168,16 @@ where
self.inner.last() self.inner.last()
} }
/// Get the nth corpus id; considers only enabled testcases
#[inline] #[inline]
fn nth(&self, nth: usize) -> CorpusId { fn nth(&self, nth: usize) -> CorpusId {
self.inner.nth(nth) self.inner.nth(nth)
} }
/// Get the nth corpus id; considers both enabled and disabled testcases
#[inline]
fn nth_from_all(&self, nth: usize) -> CorpusId {
self.inner.nth_from_all(nth)
}
#[inline] #[inline]
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> { fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {

View File

@ -28,28 +28,21 @@ where
pub next: Option<CorpusId>, pub next: Option<CorpusId>,
} }
#[cfg(not(feature = "corpus_btreemap"))]
/// The map type in which testcases are stored (enable the feature `corpus_btreemap` to use a `BTreeMap` instead of `HashMap`)
pub type TestcaseStorageMap<I> = hashbrown::HashMap<CorpusId, TestcaseStorageItem<I>>;
#[cfg(feature = "corpus_btreemap")]
/// The map type in which testcases are stored (disable the feature `corpus_btreemap` to use a `HashMap` instead of `BTreeMap`) /// The map type in which testcases are stored (disable the feature `corpus_btreemap` to use a `HashMap` instead of `BTreeMap`)
pub type TestcaseStorageMap<I> =
alloc::collections::btree_map::BTreeMap<CorpusId, RefCell<Testcase<I>>>;
/// Storage map for the testcases (used in `Corpus` implementations) with an incremental index
#[derive(Default, Serialize, Deserialize, Clone, Debug)] #[derive(Default, Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "I: serde::de::DeserializeOwned")] #[serde(bound = "I: serde::de::DeserializeOwned")]
pub struct TestcaseStorage<I> pub struct TestcaseStorageMap<I>
where where
I: Input, I: Input,
{ {
/// The map in which testcases are stored #[cfg(not(feature = "corpus_btreemap"))]
pub map: TestcaseStorageMap<I>, /// A map of `CorpusId` to `TestcaseStorageItem`
pub map: hashbrown::HashMap<CorpusId, TestcaseStorageItem<I>>,
#[cfg(feature = "corpus_btreemap")]
/// A map of `CorpusId` to `Testcase`.
pub map: alloc::collections::btree_map::BTreeMap<CorpusId, RefCell<Testcase<I>>>,
/// The keys in order (use `Vec::binary_search`) /// The keys in order (use `Vec::binary_search`)
pub keys: Vec<CorpusId>, pub keys: Vec<CorpusId>,
/// The progressive idx
progressive_idx: usize,
/// First inserted idx /// First inserted idx
#[cfg(not(feature = "corpus_btreemap"))] #[cfg(not(feature = "corpus_btreemap"))]
first_idx: Option<CorpusId>, first_idx: Option<CorpusId>,
@ -58,14 +51,7 @@ where
last_idx: Option<CorpusId>, last_idx: Option<CorpusId>,
} }
impl<I> UsesInput for TestcaseStorage<I> impl<I> TestcaseStorageMap<I>
where
I: Input,
{
type Input = I;
}
impl<I> TestcaseStorage<I>
where where
I: Input, I: Input,
{ {
@ -83,43 +69,6 @@ where
} }
} }
/// Insert a testcase assigning a `CorpusId` to it
#[cfg(not(feature = "corpus_btreemap"))]
pub fn insert(&mut self, testcase: RefCell<Testcase<I>>) -> CorpusId {
let idx = CorpusId::from(self.progressive_idx);
self.progressive_idx += 1;
let prev = if let Some(last_idx) = self.last_idx {
self.map.get_mut(&last_idx).unwrap().next = Some(idx);
Some(last_idx)
} else {
None
};
if self.first_idx.is_none() {
self.first_idx = Some(idx);
}
self.last_idx = Some(idx);
self.insert_key(idx);
self.map.insert(
idx,
TestcaseStorageItem {
testcase,
prev,
next: None,
},
);
idx
}
/// Insert a testcase assigning a `CorpusId` to it
#[cfg(feature = "corpus_btreemap")]
pub fn insert(&mut self, testcase: RefCell<Testcase<I>>) -> CorpusId {
let idx = CorpusId::from(self.progressive_idx);
self.progressive_idx += 1;
self.insert_key(idx);
self.map.insert(idx, testcase);
idx
}
/// Replace a testcase given a `CorpusId` /// Replace a testcase given a `CorpusId`
#[cfg(not(feature = "corpus_btreemap"))] #[cfg(not(feature = "corpus_btreemap"))]
pub fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Option<Testcase<I>> { pub fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Option<Testcase<I>> {
@ -270,13 +219,13 @@ where
self.map.iter().next_back().map(|x| *x.0) self.map.iter().next_back().map(|x| *x.0)
} }
/// Create new `TestcaseStorage` fn new() -> Self {
#[must_use]
pub fn new() -> Self {
Self { Self {
map: TestcaseStorageMap::default(), #[cfg(not(feature = "corpus_btreemap"))]
keys: vec![], map: hashbrown::HashMap::default(),
progressive_idx: 0, #[cfg(feature = "corpus_btreemap")]
map: alloc::collections::BTreeMap::default(),
keys: Vec::default(),
#[cfg(not(feature = "corpus_btreemap"))] #[cfg(not(feature = "corpus_btreemap"))]
first_idx: None, first_idx: None,
#[cfg(not(feature = "corpus_btreemap"))] #[cfg(not(feature = "corpus_btreemap"))]
@ -284,6 +233,98 @@ where
} }
} }
} }
/// Storage map for the testcases (used in `Corpus` implementations) with an incremental index
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "I: serde::de::DeserializeOwned")]
pub struct TestcaseStorage<I>
where
I: Input,
{
/// The map in which enabled testcases are stored
pub enabled: TestcaseStorageMap<I>,
/// The map in which disabled testcases are stored
pub disabled: TestcaseStorageMap<I>,
/// The progressive idx for both maps
progressive_idx: usize,
}
impl<I> UsesInput for TestcaseStorage<I>
where
I: Input,
{
type Input = I;
}
impl<I> TestcaseStorage<I>
where
I: Input,
{
/// Insert a testcase assigning a `CorpusId` to it
pub fn insert(&mut self, testcase: RefCell<Testcase<I>>) -> CorpusId {
self._insert(testcase, false)
}
/// Insert a testcase assigning a `CorpusId` to it
pub fn insert_disabled(&mut self, testcase: RefCell<Testcase<I>>) -> CorpusId {
self._insert(testcase, true)
}
/// Insert a testcase assigning a `CorpusId` to it
#[cfg(not(feature = "corpus_btreemap"))]
fn _insert(&mut self, testcase: RefCell<Testcase<I>>, is_disabled: bool) -> CorpusId {
let idx = CorpusId::from(self.progressive_idx);
self.progressive_idx += 1;
let corpus = if is_disabled {
&mut self.disabled
} else {
&mut self.enabled
};
let prev = if let Some(last_idx) = corpus.last_idx {
corpus.map.get_mut(&last_idx).unwrap().next = Some(idx);
Some(last_idx)
} else {
None
};
if corpus.first_idx.is_none() {
corpus.first_idx = Some(idx);
}
corpus.last_idx = Some(idx);
corpus.insert_key(idx);
corpus.map.insert(
idx,
TestcaseStorageItem {
testcase,
prev,
next: None,
},
);
idx
}
/// Insert a testcase assigning a `CorpusId` to it
#[cfg(feature = "corpus_btreemap")]
fn _insert(&mut self, testcase: RefCell<Testcase<I>>, is_disabled: bool) -> CorpusId {
let idx = CorpusId::from(self.progressive_idx);
self.progressive_idx += 1;
let corpus = if is_disabled {
&mut self.disabled
} else {
&mut self.enabled
};
corpus.insert_key(idx);
corpus.map.insert(idx, testcase);
idx
}
/// Create new `TestcaseStorage`
#[must_use]
pub fn new() -> Self {
Self {
enabled: TestcaseStorageMap::new(),
disabled: TestcaseStorageMap::new(),
progressive_idx: 0,
}
}
}
/// A corpus handling all in memory. /// A corpus handling all in memory.
#[derive(Default, Serialize, Deserialize, Clone, Debug)] #[derive(Default, Serialize, Deserialize, Clone, Debug)]
@ -307,22 +348,44 @@ impl<I> Corpus for InMemoryCorpus<I>
where where
I: Input, I: Input,
{ {
/// Returns the number of elements /// Returns the number of all enabled entries
#[inline] #[inline]
fn count(&self) -> usize { fn count(&self) -> usize {
self.storage.map.len() self.storage.enabled.map.len()
} }
/// Add an entry to the corpus and return its index /// Returns the number of all disabled entries
fn count_disabled(&self) -> usize {
self.storage.disabled.map.len()
}
/// Returns the number of elements including disabled entries
#[inline]
fn count_all(&self) -> usize {
self.storage
.enabled
.map
.len()
.saturating_add(self.storage.disabled.map.len())
}
/// Add an enabled testcase to the corpus and return its index
#[inline] #[inline]
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> { fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
Ok(self.storage.insert(RefCell::new(testcase))) Ok(self.storage.insert(RefCell::new(testcase)))
} }
/// Add a disabled testcase to the corpus and return its index
#[inline]
fn add_disabled(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
Ok(self.storage.insert_disabled(RefCell::new(testcase)))
}
/// Replaces the testcase at the given idx /// Replaces the testcase at the given idx
#[inline] #[inline]
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> { fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
self.storage self.storage
.enabled
.replace(idx, testcase) .replace(idx, testcase)
.ok_or_else(|| Error::key_not_found(format!("Index {idx} not found"))) .ok_or_else(|| Error::key_not_found(format!("Index {idx} not found")))
} }
@ -331,18 +394,29 @@ where
#[inline] #[inline]
fn remove(&mut self, idx: CorpusId) -> Result<Testcase<I>, Error> { fn remove(&mut self, idx: CorpusId) -> Result<Testcase<I>, Error> {
self.storage self.storage
.enabled
.remove(idx) .remove(idx)
.map(|x| x.take()) .map(|x| x.take())
.ok_or_else(|| Error::key_not_found(format!("Index {idx} not found"))) .ok_or_else(|| Error::key_not_found(format!("Index {idx} not found")))
} }
/// Get by id /// Get by id; considers only enabled testcases
#[inline] #[inline]
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> { fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
self.storage self.storage
.enabled
.get(idx) .get(idx)
.ok_or_else(|| Error::key_not_found(format!("Index {idx} not found"))) .ok_or_else(|| Error::key_not_found(format!("Index {idx} not found")))
} }
/// Get by id; considers both enabled and disabled testcases
#[inline]
fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
let mut testcase = self.storage.enabled.get(idx);
if testcase.is_none() {
testcase = self.storage.disabled.get(idx);
}
testcase.ok_or_else(|| Error::key_not_found(format!("Index {idx} not found")))
}
/// Current testcase scheduled /// Current testcase scheduled
#[inline] #[inline]
@ -358,27 +432,38 @@ where
#[inline] #[inline]
fn next(&self, idx: CorpusId) -> Option<CorpusId> { fn next(&self, idx: CorpusId) -> Option<CorpusId> {
self.storage.next(idx) self.storage.enabled.next(idx)
} }
#[inline] #[inline]
fn prev(&self, idx: CorpusId) -> Option<CorpusId> { fn prev(&self, idx: CorpusId) -> Option<CorpusId> {
self.storage.prev(idx) self.storage.enabled.prev(idx)
} }
#[inline] #[inline]
fn first(&self) -> Option<CorpusId> { fn first(&self) -> Option<CorpusId> {
self.storage.first() self.storage.enabled.first()
} }
#[inline] #[inline]
fn last(&self) -> Option<CorpusId> { fn last(&self) -> Option<CorpusId> {
self.storage.last() self.storage.enabled.last()
} }
/// Get the nth corpus id; considers only enabled testcases
#[inline] #[inline]
fn nth(&self, nth: usize) -> CorpusId { fn nth(&self, nth: usize) -> CorpusId {
self.storage.keys[nth] self.storage.enabled.keys[nth]
}
/// Get the nth corpus id; considers both enabled and disabled testcases
#[inline]
fn nth_from_all(&self, nth: usize) -> CorpusId {
let enabled_count = self.count();
if nth >= enabled_count {
return self.storage.disabled.keys[nth.saturating_sub(enabled_count)];
}
self.storage.enabled.keys[nth]
} }
#[inline] #[inline]

View File

@ -64,13 +64,24 @@ impl<I> Corpus for InMemoryOnDiskCorpus<I>
where where
I: Input, I: Input,
{ {
/// Returns the number of elements /// Returns the number of all enabled entries
#[inline] #[inline]
fn count(&self) -> usize { fn count(&self) -> usize {
self.inner.count() self.inner.count()
} }
/// Add an entry to the corpus and return its index /// Returns the number of all disabled entries
fn count_disabled(&self) -> usize {
self.inner.count_disabled()
}
/// Returns the number of elements including disabled entries
#[inline]
fn count_all(&self) -> usize {
self.inner.count_all()
}
/// Add an enabled testcase to the corpus and return its index
#[inline] #[inline]
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> { fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
let idx = self.inner.add(testcase)?; let idx = self.inner.add(testcase)?;
@ -80,6 +91,16 @@ where
Ok(idx) Ok(idx)
} }
/// Add a disabled testcase to the corpus and return its index
#[inline]
fn add_disabled(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
let idx = self.inner.add_disabled(testcase)?;
let testcase = &mut self.get_from_all(idx).unwrap().borrow_mut();
self.save_testcase(testcase, idx)?;
*testcase.input_mut() = None;
Ok(idx)
}
/// Replaces the testcase at the given idx /// Replaces the testcase at the given idx
#[inline] #[inline]
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> { fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
@ -99,12 +120,18 @@ where
Ok(entry) Ok(entry)
} }
/// Get by id /// Get by id; considers only enabled testcases
#[inline] #[inline]
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> { fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
self.inner.get(idx) self.inner.get(idx)
} }
/// Get by id; considers both enabled and disabled testcases
#[inline]
fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
self.inner.get_from_all(idx)
}
/// Current testcase scheduled /// Current testcase scheduled
#[inline] #[inline]
fn current(&self) -> &Option<CorpusId> { fn current(&self) -> &Option<CorpusId> {
@ -137,10 +164,16 @@ where
self.inner.last() self.inner.last()
} }
/// Get the nth corpus id; considers only enabled testcases
#[inline] #[inline]
fn nth(&self, nth: usize) -> CorpusId { fn nth(&self, nth: usize) -> CorpusId {
self.inner.nth(nth) self.inner.nth(nth)
} }
/// Get the nth corpus id; considers both enabled and disabled testcases
#[inline]
fn nth_from_all(&self, nth: usize) -> CorpusId {
self.inner.nth_from_all(nth)
}
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> { fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
if testcase.input_mut().is_none() { if testcase.input_mut().is_none() {

View File

@ -63,7 +63,7 @@ impl From<CorpusId> for usize {
} }
} }
/// Utility macro to call `Corpus::random_id` /// Utility macro to call `Corpus::random_id`; fetches only enabled testcases
#[macro_export] #[macro_export]
macro_rules! random_corpus_id { macro_rules! random_corpus_id {
($corpus:expr, $rand:expr) => {{ ($corpus:expr, $rand:expr) => {{
@ -73,19 +73,39 @@ macro_rules! random_corpus_id {
}}; }};
} }
/// Utility macro to call `Corpus::random_id`; fetches both enabled and disabled testcases
/// Note: use `Corpus::get_from_all` as disabled entries are inaccessible from `Corpus::get`
#[macro_export]
macro_rules! random_corpus_id_with_disabled {
($corpus:expr, $rand:expr) => {{
let cnt = $corpus.count_all() as u64;
let nth = $rand.below(cnt) as usize;
$corpus.nth_from_all(nth)
}};
}
/// Corpus with all current [`Testcase`]s, or solutions /// Corpus with all current [`Testcase`]s, or solutions
pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> { pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> {
/// Returns the number of elements /// Returns the number of all enabled entries
fn count(&self) -> usize; fn count(&self) -> usize;
/// Returns the number of all disabled entries
fn count_disabled(&self) -> usize;
/// Returns the number of elements including disabled entries
fn count_all(&self) -> usize;
/// Returns true, if no elements are in this corpus yet /// Returns true, if no elements are in this corpus yet
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.count() == 0 self.count() == 0
} }
/// Add an entry to the corpus and return its index /// Add an enabled testcase to the corpus and return its index
fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error>; fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error>;
/// Add a disabled testcase to the corpus and return its index
fn add_disabled(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error>;
/// Replaces the [`Testcase`] at the given idx, returning the existing. /// Replaces the [`Testcase`] at the given idx, returning the existing.
fn replace( fn replace(
&mut self, &mut self,
@ -96,9 +116,12 @@ pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> {
/// Removes an entry from the corpus, returning it if it was present. /// Removes an entry from the corpus, returning it if it was present.
fn remove(&mut self, id: CorpusId) -> Result<Testcase<Self::Input>, Error>; fn remove(&mut self, id: CorpusId) -> Result<Testcase<Self::Input>, Error>;
/// Get by id /// Get by id; considers only enabled testcases
fn get(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error>; fn get(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error>;
/// Get by id; considers both enabled and disabled testcases
fn get_from_all(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error>;
/// Current testcase scheduled /// Current testcase scheduled
fn current(&self) -> &Option<CorpusId>; fn current(&self) -> &Option<CorpusId>;
@ -126,13 +149,16 @@ pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> {
} }
} }
/// Get the nth corpus id /// Get the nth corpus id; considers only enabled testcases
fn nth(&self, nth: usize) -> CorpusId { fn nth(&self, nth: usize) -> CorpusId {
self.ids() self.ids()
.nth(nth) .nth(nth)
.expect("Failed to get the {nth} CorpusId") .expect("Failed to get the {nth} CorpusId")
} }
/// Get the nth corpus id; considers both enabled and disabled testcases
fn nth_from_all(&self, nth: usize) -> CorpusId;
/// Method to load the input for this [`Testcase`] from persistent storage, /// Method to load the input for this [`Testcase`] from persistent storage,
/// if necessary, and if was not already loaded (`== Some(input)`). /// if necessary, and if was not already loaded (`== Some(input)`).
/// After this call, `testcase.input()` must always return `Some(input)`. /// After this call, `testcase.input()` must always return `Some(input)`.

View File

@ -28,18 +28,35 @@ impl<I> Corpus for NopCorpus<I>
where where
I: Input, I: Input,
{ {
/// Returns the number of elements /// Returns the number of all enabled entries
#[inline] #[inline]
fn count(&self) -> usize { fn count(&self) -> usize {
0 0
} }
/// Add an entry to the corpus and return its index /// Returns the number of all disabled entries
fn count_disabled(&self) -> usize {
0
}
/// Returns the number of all entries
#[inline]
fn count_all(&self) -> usize {
0
}
/// Add an enabled testcase to the corpus and return its index
#[inline] #[inline]
fn add(&mut self, _testcase: Testcase<I>) -> Result<CorpusId, Error> { fn add(&mut self, _testcase: Testcase<I>) -> Result<CorpusId, Error> {
Err(Error::unsupported("Unsupported by NopCorpus")) Err(Error::unsupported("Unsupported by NopCorpus"))
} }
/// Add a disabled testcase to the corpus and return its index
#[inline]
fn add_disabled(&mut self, _testcase: Testcase<I>) -> Result<CorpusId, Error> {
Err(Error::unsupported("Unsupported by NopCorpus"))
}
/// Replaces the testcase at the given idx /// Replaces the testcase at the given idx
#[inline] #[inline]
fn replace(&mut self, _idx: CorpusId, _testcase: Testcase<I>) -> Result<Testcase<I>, Error> { fn replace(&mut self, _idx: CorpusId, _testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
@ -52,12 +69,18 @@ where
Err(Error::unsupported("Unsupported by NopCorpus")) Err(Error::unsupported("Unsupported by NopCorpus"))
} }
/// Get by id /// Get by id; considers only enabled testcases
#[inline] #[inline]
fn get(&self, _idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> { fn get(&self, _idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
Err(Error::unsupported("Unsupported by NopCorpus")) Err(Error::unsupported("Unsupported by NopCorpus"))
} }
/// Get by id; considers both enabled and disabled testcases
#[inline]
fn get_from_all(&self, _idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
Err(Error::unsupported("Unsupported by NopCorpus"))
}
/// Current testcase scheduled /// Current testcase scheduled
#[inline] #[inline]
fn current(&self) -> &Option<CorpusId> { fn current(&self) -> &Option<CorpusId> {
@ -90,11 +113,18 @@ where
None None
} }
/// Get the nth corpus id; considers only enabled testcases
#[inline] #[inline]
fn nth(&self, _nth: usize) -> CorpusId { fn nth(&self, _nth: usize) -> CorpusId {
CorpusId::from(0_usize) CorpusId::from(0_usize)
} }
/// Get the nth corpus id; considers both enabled and disabled testcases
#[inline]
fn nth_from_all(&self, _nth: usize) -> CorpusId {
CorpusId::from(0_usize)
}
#[inline] #[inline]
fn load_input_into(&self, _testcase: &mut Testcase<Self::Input>) -> Result<(), Error> { fn load_input_into(&self, _testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
Err(Error::unsupported("Unsupported by NopCorpus")) Err(Error::unsupported("Unsupported by NopCorpus"))

View File

@ -71,18 +71,35 @@ impl<I> Corpus for OnDiskCorpus<I>
where where
I: Input, I: Input,
{ {
/// Returns the number of elements /// Returns the number of all enabled entries
#[inline] #[inline]
fn count(&self) -> usize { fn count(&self) -> usize {
self.inner.count() self.inner.count()
} }
/// Add an entry to the corpus and return its index /// Returns the number of all disabled entries
fn count_disabled(&self) -> usize {
self.inner.count_disabled()
}
/// Returns the number of all entries
#[inline]
fn count_all(&self) -> usize {
self.inner.count_all()
}
/// Add an enabled testcase to the corpus and return its index
#[inline] #[inline]
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> { fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
self.inner.add(testcase) self.inner.add(testcase)
} }
/// Add a disabled testcase to the corpus and return its index
#[inline]
fn add_disabled(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
self.inner.add_disabled(testcase)
}
/// Replaces the testcase at the given idx /// Replaces the testcase at the given idx
#[inline] #[inline]
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> { fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
@ -95,12 +112,18 @@ where
self.inner.remove(idx) self.inner.remove(idx)
} }
/// Get by id /// Get by id; will check the disabled corpus if not available in the enabled
#[inline] #[inline]
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> { fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
self.inner.get(idx) self.inner.get(idx)
} }
/// Get by id; considers both enabled and disabled testcases
#[inline]
fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
self.inner.get_from_all(idx)
}
/// Current testcase scheduled /// Current testcase scheduled
#[inline] #[inline]
fn current(&self) -> &Option<CorpusId> { fn current(&self) -> &Option<CorpusId> {
@ -133,10 +156,16 @@ where
self.inner.last() self.inner.last()
} }
/// Get the nth corpus id; considers only enabled testcases
#[inline] #[inline]
fn nth(&self, nth: usize) -> CorpusId { fn nth(&self, nth: usize) -> CorpusId {
self.inner.nth(nth) self.inner.nth(nth)
} }
/// Get the nth corpus id; considers both enabled and disabled testcases
#[inline]
fn nth_from_all(&self, nth: usize) -> CorpusId {
self.inner.nth_from_all(nth)
}
#[inline] #[inline]
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> { fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {

View File

@ -63,6 +63,8 @@ where
scheduled_count: usize, scheduled_count: usize,
/// Parent [`CorpusId`], if known /// Parent [`CorpusId`], if known
parent_id: Option<CorpusId>, parent_id: Option<CorpusId>,
/// If the testcase is "disabled"
disabled: bool,
} }
impl<I> HasMetadata for Testcase<I> impl<I> HasMetadata for Testcase<I>
@ -195,6 +197,18 @@ where
self.scheduled_count = scheduled_count; self.scheduled_count = scheduled_count;
} }
/// Get `disabled`
#[inline]
pub fn disabled(&mut self) -> bool {
self.disabled
}
/// Set the testcase as disabled
#[inline]
pub fn set_disabled(&mut self, disabled: bool) {
self.disabled = disabled;
}
/// Create a new Testcase instance given an input /// Create a new Testcase instance given an input
#[inline] #[inline]
pub fn new(mut input: I) -> Self { pub fn new(mut input: I) -> Self {
@ -212,6 +226,7 @@ where
executions: 0, executions: 0,
scheduled_count: 0, scheduled_count: 0,
parent_id: None, parent_id: None,
disabled: false,
} }
} }
@ -232,6 +247,7 @@ where
executions: 0, executions: 0,
scheduled_count: 0, scheduled_count: 0,
parent_id: Some(parent_id), parent_id: Some(parent_id),
disabled: false,
} }
} }
@ -252,6 +268,7 @@ where
executions: 0, executions: 0,
scheduled_count: 0, scheduled_count: 0,
parent_id: None, parent_id: None,
disabled: false,
} }
} }
@ -272,6 +289,7 @@ where
executions, executions,
scheduled_count: 0, scheduled_count: 0,
parent_id: None, parent_id: None,
disabled: false,
} }
} }
@ -312,6 +330,7 @@ where
file_path: None, file_path: None,
#[cfg(feature = "std")] #[cfg(feature = "std")]
metadata_path: None, metadata_path: None,
disabled: false,
} }
} }
} }

View File

@ -683,7 +683,7 @@ where
{ {
state.scalability_monitor_mut().testcase_with_observers += 1; state.scalability_monitor_mut().testcase_with_observers += 1;
} }
fuzzer.process_execution( fuzzer.execute_and_process(
state, state,
self, self,
input.clone(), input.clone(),

View File

@ -681,7 +681,7 @@ where
{ {
state.scalability_monitor_mut().testcase_with_observers += 1; state.scalability_monitor_mut().testcase_with_observers += 1;
} }
fuzzer.process_execution(state, self, input, &observers, &exit_kind, false)? fuzzer.execute_and_process(state, self, input, &observers, &exit_kind, false)?
} else { } else {
#[cfg(feature = "scalability_introspection")] #[cfg(feature = "scalability_introspection")]
{ {

View File

@ -651,7 +651,7 @@ where
{ {
state.scalability_monitor_mut().testcase_with_observers += 1; state.scalability_monitor_mut().testcase_with_observers += 1;
} }
fuzzer.process_execution(state, self, input, &observers, &exit_kind, false)? fuzzer.execute_and_process(state, self, input, &observers, &exit_kind, false)?
} else { } else {
#[cfg(feature = "scalability_introspection")] #[cfg(feature = "scalability_introspection")]
{ {

View File

@ -68,7 +68,34 @@ pub trait HasObjective: UsesState {
/// Evaluates if an input is interesting using the feedback /// Evaluates if an input is interesting using the feedback
pub trait ExecutionProcessor<OT>: UsesState { pub trait ExecutionProcessor<OT>: UsesState {
/// Evaluate if a set of observation channels has an interesting state /// Evaluate if a set of observation channels has an interesting state
fn execute_no_process<EM>(
&mut self,
state: &mut Self::State,
manager: &mut EM,
input: &<Self::State as UsesInput>::Input,
observers: &OT,
exit_kind: &ExitKind,
) -> Result<ExecuteInputResult, Error>
where
EM: EventFirer<State = Self::State>;
/// Process `ExecuteInputResult`. Add to corpus, solution or ignore
#[allow(clippy::too_many_arguments)]
fn process_execution<EM>( fn process_execution<EM>(
&mut self,
state: &mut Self::State,
manager: &mut EM,
input: <Self::State as UsesInput>::Input,
exec_res: &ExecuteInputResult,
observers: &OT,
exit_kind: &ExitKind,
send_events: bool,
) -> Result<Option<CorpusId>, Error>
where
EM: EventFirer<State = Self::State>;
/// Evaluate if a set of observation channels has an interesting state
fn execute_and_process<EM>(
&mut self, &mut self,
state: &mut Self::State, state: &mut Self::State,
manager: &mut EM, manager: &mut EM,
@ -140,6 +167,17 @@ where
manager: &mut EM, manager: &mut EM,
input: <Self::State as UsesInput>::Input, input: <Self::State as UsesInput>::Input,
) -> Result<CorpusId, Error>; ) -> Result<CorpusId, Error>;
/// Adds the input to the corpus as disabled a input.
/// Used during initial corpus loading.
/// Disabled testcases are only used for splicing
/// Returns the `index` of the new testcase in the corpus.
/// Usually, you want to use [`Evaluator::evaluate_input`], unless you know what you are doing.
fn add_disabled_input(
&mut self,
state: &mut Self::State,
input: <Self::State as UsesInput>::Input,
) -> Result<CorpusId, Error>;
} }
/// The main fuzzer trait. /// The main fuzzer trait.
@ -324,8 +362,50 @@ where
OT: ObserversTuple<CS::State> + Serialize + DeserializeOwned, OT: ObserversTuple<CS::State> + Serialize + DeserializeOwned,
CS::State: HasCorpus + HasSolutions + HasExecutions + HasCorpus + HasImported, CS::State: HasCorpus + HasSolutions + HasExecutions + HasCorpus + HasImported,
{ {
/// Evaluate if a set of observation channels has an interesting state fn execute_no_process<EM>(
fn process_execution<EM>( &mut self,
state: &mut Self::State,
manager: &mut EM,
input: &<Self::State as UsesInput>::Input,
observers: &OT,
exit_kind: &ExitKind,
) -> Result<ExecuteInputResult, Error>
where
EM: EventFirer<State = Self::State>,
{
let mut res = ExecuteInputResult::None;
#[cfg(not(feature = "introspection"))]
let is_solution = self
.objective_mut()
.is_interesting(state, manager, input, observers, exit_kind)?;
#[cfg(feature = "introspection")]
let is_solution = self
.objective_mut()
.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
if is_solution {
res = ExecuteInputResult::Solution;
} else {
#[cfg(not(feature = "introspection"))]
let corpus_worthy = self
.feedback_mut()
.is_interesting(state, manager, input, observers, exit_kind)?;
#[cfg(feature = "introspection")]
let corpus_worthy = self
.feedback_mut()
.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
if corpus_worthy {
res = ExecuteInputResult::Corpus;
}
}
Ok(res)
}
fn execute_and_process<EM>(
&mut self, &mut self,
state: &mut Self::State, state: &mut Self::State,
manager: &mut EM, manager: &mut EM,
@ -337,41 +417,38 @@ where
where where
EM: EventFirer<State = Self::State>, EM: EventFirer<State = Self::State>,
{ {
let mut res = ExecuteInputResult::None; let exec_res = self.execute_no_process(state, manager, &input, observers, exit_kind)?;
let corpus_idx = self.process_execution(
state,
manager,
input,
&exec_res,
observers,
exit_kind,
send_events,
)?;
Ok((exec_res, corpus_idx))
}
#[cfg(not(feature = "introspection"))] /// Evaluate if a set of observation channels has an interesting state
let is_solution = self fn process_execution<EM>(
.objective_mut() &mut self,
.is_interesting(state, manager, &input, observers, exit_kind)?; state: &mut Self::State,
manager: &mut EM,
#[cfg(feature = "introspection")] input: <Self::State as UsesInput>::Input,
let is_solution = self exec_res: &ExecuteInputResult,
.objective_mut() observers: &OT,
.is_interesting_introspection(state, manager, &input, observers, exit_kind)?; exit_kind: &ExitKind,
send_events: bool,
if is_solution { ) -> Result<Option<CorpusId>, Error>
res = ExecuteInputResult::Solution; where
} else { EM: EventFirer<State = Self::State>,
#[cfg(not(feature = "introspection"))] {
let is_corpus = self match exec_res {
.feedback_mut()
.is_interesting(state, manager, &input, observers, exit_kind)?;
#[cfg(feature = "introspection")]
let is_corpus = self
.feedback_mut()
.is_interesting_introspection(state, manager, &input, observers, exit_kind)?;
if is_corpus {
res = ExecuteInputResult::Corpus;
}
}
match res {
ExecuteInputResult::None => { ExecuteInputResult::None => {
self.feedback_mut().discard_metadata(state, &input)?; self.feedback_mut().discard_metadata(state, &input)?;
self.objective_mut().discard_metadata(state, &input)?; self.objective_mut().discard_metadata(state, &input)?;
Ok((res, None)) Ok(None)
} }
ExecuteInputResult::Corpus => { ExecuteInputResult::Corpus => {
// Not a solution // Not a solution
@ -408,7 +485,7 @@ where
// This testcase is from the other fuzzers. // This testcase is from the other fuzzers.
*state.imported_mut() += 1; *state.imported_mut() += 1;
} }
Ok((res, Some(idx))) Ok(Some(idx))
} }
ExecuteInputResult::Solution => { ExecuteInputResult::Solution => {
// Not interesting // Not interesting
@ -433,7 +510,7 @@ where
)?; )?;
} }
Ok((res, None)) Ok(None)
} }
} }
} }
@ -466,7 +543,7 @@ where
self.scheduler.on_evaluation(state, &input, observers)?; self.scheduler.on_evaluation(state, &input, observers)?;
self.process_execution(state, manager, input, observers, &exit_kind, send_events) self.execute_and_process(state, manager, input, observers, &exit_kind, send_events)
} }
} }
@ -492,7 +569,17 @@ where
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error> { ) -> Result<(ExecuteInputResult, Option<CorpusId>), Error> {
self.evaluate_input_with_observers(state, executor, manager, input, send_events) self.evaluate_input_with_observers(state, executor, manager, input, send_events)
} }
fn add_disabled_input(
&mut self,
state: &mut Self::State,
input: <Self::State as UsesInput>::Input,
) -> Result<CorpusId, Error> {
let mut testcase = Testcase::with_executions(input.clone(), *state.executions());
testcase.set_disabled(true);
// Add the disabled input to the main corpus
let idx = state.corpus_mut().add_disabled(testcase)?;
Ok(idx)
}
/// Adds an input, even if it's not considered `interesting` by any of the executors /// Adds an input, even if it's not considered `interesting` by any of the executors
fn add_input( fn add_input(
&mut self, &mut self,
@ -540,12 +627,12 @@ where
// several is_interesting implementations collect some data about the run, later used in // several is_interesting implementations collect some data about the run, later used in
// append_metadata; we *must* invoke is_interesting here to collect it // append_metadata; we *must* invoke is_interesting here to collect it
#[cfg(not(feature = "introspection"))] #[cfg(not(feature = "introspection"))]
let _is_corpus = self let _corpus_worthy = self
.feedback_mut() .feedback_mut()
.is_interesting(state, manager, &input, observers, &exit_kind)?; .is_interesting(state, manager, &input, observers, &exit_kind)?;
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
let _is_corpus = self let _corpus_worthy = self
.feedback_mut() .feedback_mut()
.is_interesting_introspection(state, manager, &input, observers, &exit_kind)?; .is_interesting_introspection(state, manager, &input, observers, &exit_kind)?;

View File

@ -15,7 +15,7 @@ use crate::{
mutations::{buffer_copy, buffer_self_copy, ARITH_MAX}, mutations::{buffer_copy, buffer_self_copy, ARITH_MAX},
MutationResult, Mutator, Named, MutationResult, Mutator, Named,
}, },
random_corpus_id, random_corpus_id_with_disabled,
state::{HasCorpus, HasMaxSize, HasRand}, state::{HasCorpus, HasMaxSize, HasRand},
Error, Error,
}; };
@ -286,7 +286,7 @@ where
let size = input.codes().len(); let size = input.codes().len();
// We don't want to use the testcase we're already using for splicing // We don't want to use the testcase we're already using for splicing
let idx = random_corpus_id!(state.corpus(), state.rand_mut()); let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if let Some(cur) = state.corpus().current() { if let Some(cur) = state.corpus().current() {
if idx == *cur { if idx == *cur {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
@ -294,7 +294,7 @@ where
} }
let other_size = { let other_size = {
let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
other_testcase.load_input(state.corpus())?.codes().len() other_testcase.load_input(state.corpus())?.codes().len()
}; };
@ -315,7 +315,7 @@ where
} }
} }
let other_testcase = state.corpus().get(idx)?.borrow_mut(); let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
// no need to `load_input` again - we did that above already. // no need to `load_input` again - we did that above already.
let other = other_testcase.input().as_ref().unwrap(); let other = other_testcase.input().as_ref().unwrap();
@ -358,7 +358,7 @@ where
} }
// We don't want to use the testcase we're already using for splicing // We don't want to use the testcase we're already using for splicing
let idx = random_corpus_id!(state.corpus(), state.rand_mut()); let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if let Some(cur) = state.corpus().current() { if let Some(cur) = state.corpus().current() {
if idx == *cur { if idx == *cur {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
@ -367,7 +367,7 @@ where
let other_size = { let other_size = {
// new scope to make the borrow checker happy // new scope to make the borrow checker happy
let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
other_testcase.load_input(state.corpus())?.codes().len() other_testcase.load_input(state.corpus())?.codes().len()
}; };
@ -379,7 +379,7 @@ where
let len = state.rand_mut().below(min(other_size - from, size) as u64) as usize; let len = state.rand_mut().below(min(other_size - from, size) as u64) as usize;
let to = state.rand_mut().below((size - len) as u64) as usize; let to = state.rand_mut().below((size - len) as u64) as usize;
let other_testcase = state.corpus().get(idx)?.borrow_mut(); let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
// no need to load the input again, it'll already be present at this point. // no need to load the input again, it'll already be present at this point.
let other = other_testcase.input().as_ref().unwrap(); let other = other_testcase.input().as_ref().unwrap();

View File

@ -9,7 +9,7 @@ use crate::{
corpus::Corpus, corpus::Corpus,
inputs::{HasBytesVec, Input}, inputs::{HasBytesVec, Input},
mutators::{MutationResult, Mutator}, mutators::{MutationResult, Mutator},
random_corpus_id, random_corpus_id_with_disabled,
state::{HasCorpus, HasMaxSize, HasRand}, state::{HasCorpus, HasMaxSize, HasRand},
Error, Error,
}; };
@ -1053,7 +1053,7 @@ where
} }
// We don't want to use the testcase we're already using for splicing // We don't want to use the testcase we're already using for splicing
let idx = random_corpus_id!(state.corpus(), state.rand_mut()); let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if let Some(cur) = state.corpus().current() { if let Some(cur) = state.corpus().current() {
if idx == *cur { if idx == *cur {
@ -1062,7 +1062,7 @@ where
} }
let other_size = { let other_size = {
let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
other_testcase.load_input(state.corpus())?.bytes().len() other_testcase.load_input(state.corpus())?.bytes().len()
}; };
@ -1073,7 +1073,7 @@ where
let range = rand_range(state, other_size, min(other_size, max_size - size)); let range = rand_range(state, other_size, min(other_size, max_size - size));
let target = state.rand_mut().below(size as u64) as usize; let target = state.rand_mut().below(size as u64) as usize;
let other_testcase = state.corpus().get(idx)?.borrow_mut(); let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
// No need to load the input again, it'll still be cached. // No need to load the input again, it'll still be cached.
let other = other_testcase.input().as_ref().unwrap(); let other = other_testcase.input().as_ref().unwrap();
@ -1135,7 +1135,7 @@ where
} }
// We don't want to use the testcase we're already using for splicing // We don't want to use the testcase we're already using for splicing
let idx = random_corpus_id!(state.corpus(), state.rand_mut()); let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if let Some(cur) = state.corpus().current() { if let Some(cur) = state.corpus().current() {
if idx == *cur { if idx == *cur {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
@ -1143,7 +1143,7 @@ where
} }
let other_size = { let other_size = {
let mut testcase = state.corpus().get(idx)?.borrow_mut(); let mut testcase = state.corpus().get_from_all(idx)?.borrow_mut();
testcase.load_input(state.corpus())?.bytes().len() testcase.load_input(state.corpus())?.bytes().len()
}; };
@ -1154,7 +1154,7 @@ where
let target = state.rand_mut().below(size as u64) as usize; let target = state.rand_mut().below(size as u64) as usize;
let range = rand_range(state, other_size, min(other_size, size - target)); let range = rand_range(state, other_size, min(other_size, size - target));
let other_testcase = state.corpus().get(idx)?.borrow_mut(); let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
// No need to load the input again, it'll still be cached. // No need to load the input again, it'll still be cached.
let other = other_testcase.input().as_ref().unwrap(); let other = other_testcase.input().as_ref().unwrap();
@ -1207,7 +1207,7 @@ where
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
fn mutate(&mut self, state: &mut S, input: &mut S::Input) -> Result<MutationResult, Error> { fn mutate(&mut self, state: &mut S, input: &mut S::Input) -> Result<MutationResult, Error> {
// We don't want to use the testcase we're already using for splicing // We don't want to use the testcase we're already using for splicing
let idx = random_corpus_id!(state.corpus(), state.rand_mut()); let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if let Some(cur) = state.corpus().current() { if let Some(cur) = state.corpus().current() {
if idx == *cur { if idx == *cur {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
@ -1215,7 +1215,7 @@ where
} }
let (first_diff, last_diff) = { let (first_diff, last_diff) = {
let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
let other = other_testcase.load_input(state.corpus())?; let other = other_testcase.load_input(state.corpus())?;
let (f, l) = locate_diffs(input.bytes(), other.bytes()); let (f, l) = locate_diffs(input.bytes(), other.bytes());
@ -1229,7 +1229,7 @@ where
let split_at = state.rand_mut().between(first_diff, last_diff) as usize; let split_at = state.rand_mut().between(first_diff, last_diff) as usize;
let other_testcase = state.corpus().get(idx)?.borrow_mut(); let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
// Input will already be loaded. // Input will already be loaded.
let other = other_testcase.input().as_ref().unwrap(); let other = other_testcase.input().as_ref().unwrap();

View File

@ -169,7 +169,7 @@ where
) -> Result<(), Error> { ) -> Result<(), Error> {
// todo: is_interesting, etc. // todo: is_interesting, etc.
fuzzer.process_execution(state, event_mgr, last_input, observers, &exit_kind, true)?; fuzzer.execute_and_process(state, event_mgr, last_input, observers, &exit_kind, true)?;
start_timer!(state); start_timer!(state);
self.mutator.post_exec(state, self.current_corpus_idx)?; self.mutator.post_exec(state, self.current_corpus_idx)?;

View File

@ -126,7 +126,7 @@ where
// TODO replace if process_execution adds a return value for solution index // TODO replace if process_execution adds a return value for solution index
let solution_count = state.solutions().count(); let solution_count = state.solutions().count();
let corpus_count = state.corpus().count(); let corpus_count = state.corpus().count();
let (_, corpus_idx) = fuzzer.process_execution( let (_, corpus_idx) = fuzzer.execute_and_process(
state, state,
manager, manager,
input.clone(), input.clone(),

View File

@ -685,9 +685,10 @@ where
if forced { if forced {
let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?; let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?;
} else { } else {
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?; let (res, _) = fuzzer.evaluate_input(self, executor, manager, input.clone())?;
if res == ExecuteInputResult::None { if res == ExecuteInputResult::None {
log::warn!("File {:?} was not interesting, skipped.", &path); fuzzer.add_disabled_input(self, input)?;
log::warn!("input {:?} was not interesting, adding as disabled.", &path);
} }
} }
Ok(()) Ok(())

View File

@ -7,7 +7,10 @@ use std::{
}; };
use libafl::{ use libafl::{
corpus::{inmemory::TestcaseStorage, Corpus, CorpusId, Testcase}, corpus::{
inmemory::{TestcaseStorage, TestcaseStorageMap},
Corpus, CorpusId, Testcase,
},
inputs::{Input, UsesInput}, inputs::{Input, UsesInput},
}; };
use libafl_bolts::Error; use libafl_bolts::Error;
@ -51,7 +54,7 @@ where
} }
/// Touch this index and maybe evict an entry if we have touched an input which was unloaded. /// Touch this index and maybe evict an entry if we have touched an input which was unloaded.
fn touch(&self, idx: CorpusId) -> Result<(), Error> { fn touch(&self, idx: CorpusId, corpus: &TestcaseStorageMap<I>) -> Result<(), Error> {
let mut loaded_mapping = self.loaded_mapping.borrow_mut(); let mut loaded_mapping = self.loaded_mapping.borrow_mut();
let mut loaded_entries = self.loaded_entries.borrow_mut(); let mut loaded_entries = self.loaded_entries.borrow_mut();
match loaded_mapping.entry(idx) { match loaded_mapping.entry(idx) {
@ -71,7 +74,7 @@ where
} }
if loaded_entries.len() > self.max_len { if loaded_entries.len() > self.max_len {
let idx = loaded_entries.pop_first().unwrap().1; // cannot panic let idx = loaded_entries.pop_first().unwrap().1; // cannot panic
let cell = self.mapping.get(idx).ok_or_else(|| { let cell = corpus.get(idx).ok_or_else(|| {
Error::key_not_found(format!("Tried to evict non-existent entry {idx}")) Error::key_not_found(format!("Tried to evict non-existent entry {idx}"))
})?; })?;
let mut tc = cell.try_borrow_mut()?; let mut tc = cell.try_borrow_mut()?;
@ -79,27 +82,32 @@ where
} }
Ok(()) Ok(())
} }
} #[inline]
fn _get<'a>(
impl<I> UsesInput for LibfuzzerCorpus<I> &'a self,
where id: CorpusId,
I: Input + Serialize + for<'de> Deserialize<'de>, corpus: &'a TestcaseStorageMap<I>,
{ ) -> Result<&RefCell<Testcase<I>>, Error> {
type Input = I; self.touch(id, corpus)?;
} corpus.map.get(&id).map(|item| &item.testcase).ok_or_else(|| Error::illegal_state("Nonexistent corpus entry {id} requested (present in loaded entries, but not the mapping?)"))
impl<I> Corpus for LibfuzzerCorpus<I>
where
I: Input + Serialize + for<'de> Deserialize<'de>,
{
fn count(&self) -> usize {
self.mapping.map.len()
} }
fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> { fn _add(
let idx = self.mapping.insert(RefCell::new(testcase)); &mut self,
let mut testcase = self.mapping.get(idx).unwrap().borrow_mut(); testcase: RefCell<Testcase<I>>,
is_disabled: bool,
) -> Result<CorpusId, Error> {
let idx = if is_disabled {
self.mapping.insert_disabled(testcase)
} else {
self.mapping.insert(testcase)
};
let corpus = if is_disabled {
&self.mapping.disabled
} else {
&self.mapping.enabled
};
let mut testcase = corpus.get(idx).unwrap().borrow_mut();
match testcase.file_path() { match testcase.file_path() {
Some(path) if path.canonicalize()?.starts_with(&self.corpus_dir) => { Some(path) if path.canonicalize()?.starts_with(&self.corpus_dir) => {
// if it's already in the correct dir, we retain it // if it's already in the correct dir, we retain it
@ -126,10 +134,40 @@ where
testcase.file_path_mut().replace(path); testcase.file_path_mut().replace(path);
} }
}; };
self.touch(idx, corpus)?;
self.touch(idx)?;
Ok(idx) Ok(idx)
} }
}
impl<I> UsesInput for LibfuzzerCorpus<I>
where
I: Input + Serialize + for<'de> Deserialize<'de>,
{
type Input = I;
}
impl<I> Corpus for LibfuzzerCorpus<I>
where
I: Input + Serialize + for<'de> Deserialize<'de>,
{
#[inline]
fn count(&self) -> usize {
self.mapping.enabled.map.len()
}
#[inline]
fn count_disabled(&self) -> usize {
self.mapping.disabled.map.len()
}
#[inline]
fn count_all(&self) -> usize {
self.count_disabled().saturating_add(self.count_disabled())
}
fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
self._add(RefCell::new(testcase), false)
}
fn add_disabled(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
self._add(RefCell::new(testcase), true)
}
fn replace( fn replace(
&mut self, &mut self,
@ -144,10 +182,18 @@ where
} }
fn get(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> { fn get(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
self.touch(id)?; self._get(id, &self.mapping.enabled)
self.mapping.map.get(&id).map(|item| &item.testcase).ok_or_else(|| Error::illegal_state("Nonexistent corpus entry {id} requested (present in loaded entries, but not the mapping?)"))
} }
fn get_from_all(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
match self._get(id, &self.mapping.enabled) {
Ok(input) => Ok(input),
Err(Error::KeyNotFound(..)) => return self._get(id, &self.mapping.disabled),
Err(e) => {
Err(e)
}
}
}
fn current(&self) -> &Option<CorpusId> { fn current(&self) -> &Option<CorpusId> {
&self.current &self.current
} }
@ -157,19 +203,29 @@ where
} }
fn next(&self, id: CorpusId) -> Option<CorpusId> { fn next(&self, id: CorpusId) -> Option<CorpusId> {
self.mapping.next(id) self.mapping.enabled.next(id)
} }
fn prev(&self, id: CorpusId) -> Option<CorpusId> { fn prev(&self, id: CorpusId) -> Option<CorpusId> {
self.mapping.prev(id) self.mapping.enabled.prev(id)
} }
fn first(&self) -> Option<CorpusId> { fn first(&self) -> Option<CorpusId> {
self.mapping.first() self.mapping.enabled.first()
} }
fn last(&self) -> Option<CorpusId> { fn last(&self) -> Option<CorpusId> {
self.mapping.last() self.mapping.enabled.last()
}
/// Get the nth corpus id; considers both enabled and disabled testcases
#[inline]
fn nth_from_all(&self, nth: usize) -> CorpusId {
let enabled_count = self.count();
if nth >= enabled_count {
return self.mapping.disabled.keys[nth.saturating_sub(enabled_count)];
}
self.mapping.enabled.keys[nth]
} }
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> { fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
@ -239,6 +295,16 @@ where
self.count self.count
} }
// ArtifactCorpus disregards disabled entries
fn count_disabled(&self) -> usize {
0
}
fn count_all(&self) -> usize {
// count_disabled will always return 0
self.count() + self.count_disabled()
}
fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> { fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
let idx = self.count; let idx = self.count;
self.count += 1; self.count += 1;
@ -262,6 +328,10 @@ where
Ok(CorpusId::from(idx)) Ok(CorpusId::from(idx))
} }
fn add_disabled(&mut self, _testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
unimplemented!("ArtifactCorpus disregards disabled inputs")
}
fn replace( fn replace(
&mut self, &mut self,
_idx: CorpusId, _idx: CorpusId,
@ -288,6 +358,16 @@ where
maybe_last.ok_or_else(|| Error::illegal_argument("Can only get the last corpus ID.")) maybe_last.ok_or_else(|| Error::illegal_argument("Can only get the last corpus ID."))
} }
// This just calls Self::get as ArtifactCorpus disregards disabled entries
fn get_from_all(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
self.get(id)
}
// This just calls Self::nth as ArtifactCorpus disregards disabled entries
fn nth_from_all(&self, nth: usize) -> CorpusId {
self.nth(nth)
}
fn current(&self) -> &Option<CorpusId> { fn current(&self) -> &Option<CorpusId> {
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.") unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
} }

View File

@ -12,7 +12,7 @@ use libafl::{
mutators::{ mutators::{
ComposedByMutations, MutationId, MutationResult, Mutator, MutatorsTuple, ScheduledMutator, ComposedByMutations, MutationId, MutationResult, Mutator, MutatorsTuple, ScheduledMutator,
}, },
random_corpus_id, random_corpus_id_with_disabled,
state::{HasCorpus, HasMaxSize, HasRand}, state::{HasCorpus, HasMaxSize, HasRand},
Error, Error,
}; };
@ -392,14 +392,14 @@ where
input: &mut S::Input, input: &mut S::Input,
) -> Result<MutationResult, Error> { ) -> Result<MutationResult, Error> {
// We don't want to use the testcase we're already using for splicing // We don't want to use the testcase we're already using for splicing
let idx = random_corpus_id!(state.corpus(), state.rand_mut()); let idx = random_corpus_id_with_disabled!(state.corpus(), state.rand_mut());
if let Some(cur) = state.corpus().current() { if let Some(cur) = state.corpus().current() {
if idx == *cur { if idx == *cur {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
} }
} }
let mut other_testcase = state.corpus().get(idx)?.borrow_mut(); let mut other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
let other = other_testcase.load_input(state.corpus())?; let other = other_testcase.load_input(state.corpus())?;
let data2 = Vec::from(other.bytes()); let data2 = Vec::from(other.bytes());
drop(other_testcase); drop(other_testcase);