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;
|
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> {
|
||||||
|
@ -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]
|
||||||
|
@ -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() {
|
||||||
|
@ -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)`.
|
||||||
|
@ -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"))
|
||||||
|
@ -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> {
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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")]
|
||||||
{
|
{
|
||||||
|
@ -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")]
|
||||||
{
|
{
|
||||||
|
@ -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)?;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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)?;
|
||||||
|
@ -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(),
|
||||||
|
@ -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(())
|
||||||
|
@ -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.")
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user