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:
parent
d3b3d5d462
commit
47c41c2925
@ -37,22 +37,75 @@ where
|
||||
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>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
/// Returns the number of elements
|
||||
/// Returns the number of all enabled entries
|
||||
#[inline]
|
||||
fn count(&self) -> usize {
|
||||
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]
|
||||
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
|
||||
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
|
||||
#[inline]
|
||||
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
|
||||
@ -68,27 +121,18 @@ where
|
||||
Ok(testcase)
|
||||
}
|
||||
|
||||
/// Get by id
|
||||
/// Get by id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
|
||||
let testcase = { self.inner.get(idx)? };
|
||||
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) = 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);
|
||||
}
|
||||
self.cache_testcase(testcase, idx, false)?;
|
||||
Ok(testcase)
|
||||
}
|
||||
/// Get by id; considers both enabled and disabled testcases
|
||||
#[inline]
|
||||
fn get_from_all(&self, idx: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
|
||||
let testcase = { self.inner.get_from_all(idx)? };
|
||||
self.cache_testcase(testcase, idx, true)?;
|
||||
Ok(testcase)
|
||||
}
|
||||
|
||||
@ -124,10 +168,16 @@ where
|
||||
self.inner.last()
|
||||
}
|
||||
|
||||
/// Get the nth corpus id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn nth(&self, nth: usize) -> CorpusId {
|
||||
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]
|
||||
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
|
||||
|
@ -28,28 +28,21 @@ where
|
||||
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`)
|
||||
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)]
|
||||
#[serde(bound = "I: serde::de::DeserializeOwned")]
|
||||
pub struct TestcaseStorage<I>
|
||||
pub struct TestcaseStorageMap<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
/// The map in which testcases are stored
|
||||
pub map: TestcaseStorageMap<I>,
|
||||
#[cfg(not(feature = "corpus_btreemap"))]
|
||||
/// 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`)
|
||||
pub keys: Vec<CorpusId>,
|
||||
/// The progressive idx
|
||||
progressive_idx: usize,
|
||||
/// First inserted idx
|
||||
#[cfg(not(feature = "corpus_btreemap"))]
|
||||
first_idx: Option<CorpusId>,
|
||||
@ -58,14 +51,7 @@ where
|
||||
last_idx: Option<CorpusId>,
|
||||
}
|
||||
|
||||
impl<I> UsesInput for TestcaseStorage<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
type Input = I;
|
||||
}
|
||||
|
||||
impl<I> TestcaseStorage<I>
|
||||
impl<I> TestcaseStorageMap<I>
|
||||
where
|
||||
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`
|
||||
#[cfg(not(feature = "corpus_btreemap"))]
|
||||
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)
|
||||
}
|
||||
|
||||
/// Create new `TestcaseStorage`
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
map: TestcaseStorageMap::default(),
|
||||
keys: vec![],
|
||||
progressive_idx: 0,
|
||||
#[cfg(not(feature = "corpus_btreemap"))]
|
||||
map: hashbrown::HashMap::default(),
|
||||
#[cfg(feature = "corpus_btreemap")]
|
||||
map: alloc::collections::BTreeMap::default(),
|
||||
keys: Vec::default(),
|
||||
#[cfg(not(feature = "corpus_btreemap"))]
|
||||
first_idx: None,
|
||||
#[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.
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
||||
@ -307,22 +348,44 @@ impl<I> Corpus for InMemoryCorpus<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
/// Returns the number of elements
|
||||
/// Returns the number of all enabled entries
|
||||
#[inline]
|
||||
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]
|
||||
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
|
||||
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
|
||||
#[inline]
|
||||
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
|
||||
self.storage
|
||||
.enabled
|
||||
.replace(idx, testcase)
|
||||
.ok_or_else(|| Error::key_not_found(format!("Index {idx} not found")))
|
||||
}
|
||||
@ -331,18 +394,29 @@ where
|
||||
#[inline]
|
||||
fn remove(&mut self, idx: CorpusId) -> Result<Testcase<I>, Error> {
|
||||
self.storage
|
||||
.enabled
|
||||
.remove(idx)
|
||||
.map(|x| x.take())
|
||||
.ok_or_else(|| Error::key_not_found(format!("Index {idx} not found")))
|
||||
}
|
||||
|
||||
/// Get by id
|
||||
/// Get by id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
|
||||
self.storage
|
||||
.enabled
|
||||
.get(idx)
|
||||
.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
|
||||
#[inline]
|
||||
@ -358,27 +432,38 @@ where
|
||||
|
||||
#[inline]
|
||||
fn next(&self, idx: CorpusId) -> Option<CorpusId> {
|
||||
self.storage.next(idx)
|
||||
self.storage.enabled.next(idx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn prev(&self, idx: CorpusId) -> Option<CorpusId> {
|
||||
self.storage.prev(idx)
|
||||
self.storage.enabled.prev(idx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn first(&self) -> Option<CorpusId> {
|
||||
self.storage.first()
|
||||
self.storage.enabled.first()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn last(&self) -> Option<CorpusId> {
|
||||
self.storage.last()
|
||||
self.storage.enabled.last()
|
||||
}
|
||||
|
||||
/// Get the nth corpus id; considers only enabled testcases
|
||||
#[inline]
|
||||
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]
|
||||
|
@ -64,13 +64,24 @@ impl<I> Corpus for InMemoryOnDiskCorpus<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
/// Returns the number of elements
|
||||
/// Returns the number of all enabled entries
|
||||
#[inline]
|
||||
fn count(&self) -> usize {
|
||||
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]
|
||||
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
|
||||
let idx = self.inner.add(testcase)?;
|
||||
@ -80,6 +91,16 @@ where
|
||||
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
|
||||
#[inline]
|
||||
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
|
||||
@ -99,12 +120,18 @@ where
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Get by id
|
||||
/// Get by id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
|
||||
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
|
||||
#[inline]
|
||||
fn current(&self) -> &Option<CorpusId> {
|
||||
@ -137,10 +164,16 @@ where
|
||||
self.inner.last()
|
||||
}
|
||||
|
||||
/// Get the nth corpus id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn nth(&self, nth: usize) -> CorpusId {
|
||||
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> {
|
||||
if testcase.input_mut().is_none() {
|
||||
|
@ -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_rules! random_corpus_id {
|
||||
($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
|
||||
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;
|
||||
|
||||
/// 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
|
||||
fn is_empty(&self) -> bool {
|
||||
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>;
|
||||
|
||||
/// 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.
|
||||
fn replace(
|
||||
&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.
|
||||
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>;
|
||||
|
||||
/// 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
|
||||
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 {
|
||||
self.ids()
|
||||
.nth(nth)
|
||||
.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,
|
||||
/// if necessary, and if was not already loaded (`== Some(input)`).
|
||||
/// After this call, `testcase.input()` must always return `Some(input)`.
|
||||
|
@ -28,18 +28,35 @@ impl<I> Corpus for NopCorpus<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
/// Returns the number of elements
|
||||
/// Returns the number of all enabled entries
|
||||
#[inline]
|
||||
fn count(&self) -> usize {
|
||||
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]
|
||||
fn add(&mut self, _testcase: Testcase<I>) -> Result<CorpusId, Error> {
|
||||
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
|
||||
#[inline]
|
||||
fn replace(&mut self, _idx: CorpusId, _testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
|
||||
@ -52,12 +69,18 @@ where
|
||||
Err(Error::unsupported("Unsupported by NopCorpus"))
|
||||
}
|
||||
|
||||
/// Get by id
|
||||
/// Get by id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn get(&self, _idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
|
||||
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
|
||||
#[inline]
|
||||
fn current(&self) -> &Option<CorpusId> {
|
||||
@ -90,11 +113,18 @@ where
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the nth corpus id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn nth(&self, _nth: usize) -> CorpusId {
|
||||
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]
|
||||
fn load_input_into(&self, _testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
|
||||
Err(Error::unsupported("Unsupported by NopCorpus"))
|
||||
|
@ -71,18 +71,35 @@ impl<I> Corpus for OnDiskCorpus<I>
|
||||
where
|
||||
I: Input,
|
||||
{
|
||||
/// Returns the number of elements
|
||||
/// Returns the number of all enabled entries
|
||||
#[inline]
|
||||
fn count(&self) -> usize {
|
||||
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]
|
||||
fn add(&mut self, testcase: Testcase<I>) -> Result<CorpusId, Error> {
|
||||
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
|
||||
#[inline]
|
||||
fn replace(&mut self, idx: CorpusId, testcase: Testcase<I>) -> Result<Testcase<I>, Error> {
|
||||
@ -95,12 +112,18 @@ where
|
||||
self.inner.remove(idx)
|
||||
}
|
||||
|
||||
/// Get by id
|
||||
/// Get by id; will check the disabled corpus if not available in the enabled
|
||||
#[inline]
|
||||
fn get(&self, idx: CorpusId) -> Result<&RefCell<Testcase<I>>, Error> {
|
||||
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
|
||||
#[inline]
|
||||
fn current(&self) -> &Option<CorpusId> {
|
||||
@ -133,10 +156,16 @@ where
|
||||
self.inner.last()
|
||||
}
|
||||
|
||||
/// Get the nth corpus id; considers only enabled testcases
|
||||
#[inline]
|
||||
fn nth(&self, nth: usize) -> CorpusId {
|
||||
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]
|
||||
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
|
||||
|
@ -63,6 +63,8 @@ where
|
||||
scheduled_count: usize,
|
||||
/// Parent [`CorpusId`], if known
|
||||
parent_id: Option<CorpusId>,
|
||||
/// If the testcase is "disabled"
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
impl<I> HasMetadata for Testcase<I>
|
||||
@ -195,6 +197,18 @@ where
|
||||
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
|
||||
#[inline]
|
||||
pub fn new(mut input: I) -> Self {
|
||||
@ -212,6 +226,7 @@ where
|
||||
executions: 0,
|
||||
scheduled_count: 0,
|
||||
parent_id: None,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,6 +247,7 @@ where
|
||||
executions: 0,
|
||||
scheduled_count: 0,
|
||||
parent_id: Some(parent_id),
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,6 +268,7 @@ where
|
||||
executions: 0,
|
||||
scheduled_count: 0,
|
||||
parent_id: None,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,6 +289,7 @@ where
|
||||
executions,
|
||||
scheduled_count: 0,
|
||||
parent_id: None,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,6 +330,7 @@ where
|
||||
file_path: None,
|
||||
#[cfg(feature = "std")]
|
||||
metadata_path: None,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -683,7 +683,7 @@ where
|
||||
{
|
||||
state.scalability_monitor_mut().testcase_with_observers += 1;
|
||||
}
|
||||
fuzzer.process_execution(
|
||||
fuzzer.execute_and_process(
|
||||
state,
|
||||
self,
|
||||
input.clone(),
|
||||
|
@ -681,7 +681,7 @@ where
|
||||
{
|
||||
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 {
|
||||
#[cfg(feature = "scalability_introspection")]
|
||||
{
|
||||
|
@ -651,7 +651,7 @@ where
|
||||
{
|
||||
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 {
|
||||
#[cfg(feature = "scalability_introspection")]
|
||||
{
|
||||
|
@ -68,7 +68,34 @@ pub trait HasObjective: UsesState {
|
||||
/// Evaluates if an input is interesting using the feedback
|
||||
pub trait ExecutionProcessor<OT>: UsesState {
|
||||
/// 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>(
|
||||
&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,
|
||||
state: &mut Self::State,
|
||||
manager: &mut EM,
|
||||
@ -140,6 +167,17 @@ where
|
||||
manager: &mut EM,
|
||||
input: <Self::State as UsesInput>::Input,
|
||||
) -> 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.
|
||||
@ -324,8 +362,50 @@ where
|
||||
OT: ObserversTuple<CS::State> + Serialize + DeserializeOwned,
|
||||
CS::State: HasCorpus + HasSolutions + HasExecutions + HasCorpus + HasImported,
|
||||
{
|
||||
/// Evaluate if a set of observation channels has an interesting state
|
||||
fn process_execution<EM>(
|
||||
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>,
|
||||
{
|
||||
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,
|
||||
state: &mut Self::State,
|
||||
manager: &mut EM,
|
||||
@ -337,41 +417,38 @@ where
|
||||
where
|
||||
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"))]
|
||||
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 is_corpus = self
|
||||
.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 {
|
||||
/// Evaluate if a set of observation channels has an interesting state
|
||||
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>,
|
||||
{
|
||||
match exec_res {
|
||||
ExecuteInputResult::None => {
|
||||
self.feedback_mut().discard_metadata(state, &input)?;
|
||||
self.objective_mut().discard_metadata(state, &input)?;
|
||||
Ok((res, None))
|
||||
Ok(None)
|
||||
}
|
||||
ExecuteInputResult::Corpus => {
|
||||
// Not a solution
|
||||
@ -408,7 +485,7 @@ where
|
||||
// This testcase is from the other fuzzers.
|
||||
*state.imported_mut() += 1;
|
||||
}
|
||||
Ok((res, Some(idx)))
|
||||
Ok(Some(idx))
|
||||
}
|
||||
ExecuteInputResult::Solution => {
|
||||
// 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.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> {
|
||||
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
|
||||
fn add_input(
|
||||
&mut self,
|
||||
@ -540,12 +627,12 @@ where
|
||||
// several is_interesting implementations collect some data about the run, later used in
|
||||
// append_metadata; we *must* invoke is_interesting here to collect it
|
||||
#[cfg(not(feature = "introspection"))]
|
||||
let _is_corpus = self
|
||||
let _corpus_worthy = self
|
||||
.feedback_mut()
|
||||
.is_interesting(state, manager, &input, observers, &exit_kind)?;
|
||||
|
||||
#[cfg(feature = "introspection")]
|
||||
let _is_corpus = self
|
||||
let _corpus_worthy = self
|
||||
.feedback_mut()
|
||||
.is_interesting_introspection(state, manager, &input, observers, &exit_kind)?;
|
||||
|
||||
|
@ -15,7 +15,7 @@ use crate::{
|
||||
mutations::{buffer_copy, buffer_self_copy, ARITH_MAX},
|
||||
MutationResult, Mutator, Named,
|
||||
},
|
||||
random_corpus_id,
|
||||
random_corpus_id_with_disabled,
|
||||
state::{HasCorpus, HasMaxSize, HasRand},
|
||||
Error,
|
||||
};
|
||||
@ -286,7 +286,7 @@ where
|
||||
let size = input.codes().len();
|
||||
|
||||
// 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 idx == *cur {
|
||||
return Ok(MutationResult::Skipped);
|
||||
@ -294,7 +294,7 @@ where
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
@ -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.
|
||||
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
|
||||
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 idx == *cur {
|
||||
return Ok(MutationResult::Skipped);
|
||||
@ -367,7 +367,7 @@ where
|
||||
|
||||
let other_size = {
|
||||
// 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()
|
||||
};
|
||||
|
||||
@ -379,7 +379,7 @@ where
|
||||
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 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.
|
||||
let other = other_testcase.input().as_ref().unwrap();
|
||||
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
corpus::Corpus,
|
||||
inputs::{HasBytesVec, Input},
|
||||
mutators::{MutationResult, Mutator},
|
||||
random_corpus_id,
|
||||
random_corpus_id_with_disabled,
|
||||
state::{HasCorpus, HasMaxSize, HasRand},
|
||||
Error,
|
||||
};
|
||||
@ -1053,7 +1053,7 @@ where
|
||||
}
|
||||
|
||||
// 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 idx == *cur {
|
||||
@ -1062,7 +1062,7 @@ where
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
@ -1073,7 +1073,7 @@ where
|
||||
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 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.
|
||||
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
|
||||
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 idx == *cur {
|
||||
return Ok(MutationResult::Skipped);
|
||||
@ -1143,7 +1143,7 @@ where
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
@ -1154,7 +1154,7 @@ where
|
||||
let target = state.rand_mut().below(size as u64) as usize;
|
||||
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.
|
||||
let other = other_testcase.input().as_ref().unwrap();
|
||||
|
||||
@ -1207,7 +1207,7 @@ where
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
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
|
||||
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 idx == *cur {
|
||||
return Ok(MutationResult::Skipped);
|
||||
@ -1215,7 +1215,7 @@ where
|
||||
}
|
||||
|
||||
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 (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 other_testcase = state.corpus().get(idx)?.borrow_mut();
|
||||
let other_testcase = state.corpus().get_from_all(idx)?.borrow_mut();
|
||||
// Input will already be loaded.
|
||||
let other = other_testcase.input().as_ref().unwrap();
|
||||
|
||||
|
@ -169,7 +169,7 @@ where
|
||||
) -> Result<(), Error> {
|
||||
// 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);
|
||||
self.mutator.post_exec(state, self.current_corpus_idx)?;
|
||||
|
@ -126,7 +126,7 @@ where
|
||||
// TODO replace if process_execution adds a return value for solution index
|
||||
let solution_count = state.solutions().count();
|
||||
let corpus_count = state.corpus().count();
|
||||
let (_, corpus_idx) = fuzzer.process_execution(
|
||||
let (_, corpus_idx) = fuzzer.execute_and_process(
|
||||
state,
|
||||
manager,
|
||||
input.clone(),
|
||||
|
@ -685,9 +685,10 @@ where
|
||||
if forced {
|
||||
let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?;
|
||||
} else {
|
||||
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?;
|
||||
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input.clone())?;
|
||||
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(())
|
||||
|
@ -7,7 +7,10 @@ use std::{
|
||||
};
|
||||
|
||||
use libafl::{
|
||||
corpus::{inmemory::TestcaseStorage, Corpus, CorpusId, Testcase},
|
||||
corpus::{
|
||||
inmemory::{TestcaseStorage, TestcaseStorageMap},
|
||||
Corpus, CorpusId, Testcase,
|
||||
},
|
||||
inputs::{Input, UsesInput},
|
||||
};
|
||||
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.
|
||||
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_entries = self.loaded_entries.borrow_mut();
|
||||
match loaded_mapping.entry(idx) {
|
||||
@ -71,7 +74,7 @@ where
|
||||
}
|
||||
if loaded_entries.len() > self.max_len {
|
||||
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}"))
|
||||
})?;
|
||||
let mut tc = cell.try_borrow_mut()?;
|
||||
@ -79,27 +82,32 @@ where
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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>,
|
||||
{
|
||||
fn count(&self) -> usize {
|
||||
self.mapping.map.len()
|
||||
#[inline]
|
||||
fn _get<'a>(
|
||||
&'a self,
|
||||
id: CorpusId,
|
||||
corpus: &'a TestcaseStorageMap<I>,
|
||||
) -> Result<&RefCell<Testcase<I>>, Error> {
|
||||
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?)"))
|
||||
}
|
||||
|
||||
fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
|
||||
let idx = self.mapping.insert(RefCell::new(testcase));
|
||||
let mut testcase = self.mapping.get(idx).unwrap().borrow_mut();
|
||||
|
||||
fn _add(
|
||||
&mut self,
|
||||
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() {
|
||||
Some(path) if path.canonicalize()?.starts_with(&self.corpus_dir) => {
|
||||
// if it's already in the correct dir, we retain it
|
||||
@ -126,10 +134,40 @@ where
|
||||
testcase.file_path_mut().replace(path);
|
||||
}
|
||||
};
|
||||
|
||||
self.touch(idx)?;
|
||||
self.touch(idx, corpus)?;
|
||||
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(
|
||||
&mut self,
|
||||
@ -144,10 +182,18 @@ where
|
||||
}
|
||||
|
||||
fn get(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
|
||||
self.touch(id)?;
|
||||
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?)"))
|
||||
self._get(id, &self.mapping.enabled)
|
||||
}
|
||||
|
||||
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> {
|
||||
&self.current
|
||||
}
|
||||
@ -157,19 +203,29 @@ where
|
||||
}
|
||||
|
||||
fn next(&self, id: CorpusId) -> Option<CorpusId> {
|
||||
self.mapping.next(id)
|
||||
self.mapping.enabled.next(id)
|
||||
}
|
||||
|
||||
fn prev(&self, id: CorpusId) -> Option<CorpusId> {
|
||||
self.mapping.prev(id)
|
||||
self.mapping.enabled.prev(id)
|
||||
}
|
||||
|
||||
fn first(&self) -> Option<CorpusId> {
|
||||
self.mapping.first()
|
||||
self.mapping.enabled.first()
|
||||
}
|
||||
|
||||
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> {
|
||||
@ -239,6 +295,16 @@ where
|
||||
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> {
|
||||
let idx = self.count;
|
||||
self.count += 1;
|
||||
@ -262,6 +328,10 @@ where
|
||||
Ok(CorpusId::from(idx))
|
||||
}
|
||||
|
||||
fn add_disabled(&mut self, _testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
|
||||
unimplemented!("ArtifactCorpus disregards disabled inputs")
|
||||
}
|
||||
|
||||
fn replace(
|
||||
&mut self,
|
||||
_idx: CorpusId,
|
||||
@ -288,6 +358,16 @@ where
|
||||
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> {
|
||||
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use libafl::{
|
||||
mutators::{
|
||||
ComposedByMutations, MutationId, MutationResult, Mutator, MutatorsTuple, ScheduledMutator,
|
||||
},
|
||||
random_corpus_id,
|
||||
random_corpus_id_with_disabled,
|
||||
state::{HasCorpus, HasMaxSize, HasRand},
|
||||
Error,
|
||||
};
|
||||
@ -392,14 +392,14 @@ where
|
||||
input: &mut S::Input,
|
||||
) -> Result<MutationResult, Error> {
|
||||
// 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 idx == *cur {
|
||||
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 data2 = Vec::from(other.bytes());
|
||||
drop(other_testcase);
|
||||
|
Loading…
x
Reference in New Issue
Block a user