#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{set_states_corpus_index, CorpusIndex, Scheduler};
use crate::error::FeroxFuzzError;
use crate::state::SharedState;
use crate::std_ext::ops::Len;
use crate::std_ext::tuple::Named;
use tracing::{error, instrument, trace};
#[allow(clippy::doc_link_with_quotes)]
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RandomScheduler {
current: usize,
indices: Vec<CorpusIndex>,
longest_corpus: usize,
#[cfg_attr(feature = "serde", serde(skip))]
state: SharedState,
}
impl Scheduler for RandomScheduler {
#[instrument(skip(self), fields(%self.current, %self.longest_corpus, ?self.indices), level = "trace")]
fn next(&mut self) -> Result<(), FeroxFuzzError> {
if self.current >= self.longest_corpus {
trace!("scheduler has run to completion");
return Err(FeroxFuzzError::IterationStopped);
}
for index in &mut self.indices {
let length = index.len();
let random_idx = self.state.rng_mut().below(length);
set_states_corpus_index(&self.state, index.name(), random_idx)?;
}
self.current += 1;
Ok(())
}
fn reset(&mut self) {
self.current = 0;
for index in &mut self.indices {
let corpus = self.state.corpus_by_name(index.name()).unwrap();
let len = corpus.len();
if len > self.longest_corpus {
self.longest_corpus = len;
}
index.update_length(len);
set_states_corpus_index(&self.state, index.name(), 0).unwrap();
}
trace!("scheduler has been reset");
}
fn update_length(&mut self) {
for index in &mut self.indices {
let corpus = self.state.corpus_by_name(index.name()).unwrap();
let len = corpus.len();
if len > self.longest_corpus {
self.longest_corpus = len;
}
index.update_length(len);
}
}
}
impl RandomScheduler {
#[inline]
#[instrument(skip_all, level = "trace")]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
pub fn new(state: SharedState) -> Result<Self, FeroxFuzzError> {
let corpora = state.corpora();
let mut longest_corpus = 0;
let mut indices = Vec::with_capacity(corpora.len());
let mut current = state
.stats()
.read()
.map_or(0, |stats| stats.requests() as usize);
for (name, corpus) in &*corpora {
let length = corpus.len();
if length == 0 {
error!(%name, "corpus is empty");
return Err(FeroxFuzzError::EmptyCorpus { name: name.clone() });
}
if length > longest_corpus {
longest_corpus = length;
}
indices.push(CorpusIndex::new(name, length, length));
}
if indices.is_empty() {
error!("no corpora were found");
return Err(FeroxFuzzError::EmptyCorpusMap);
}
let mut scheduler = Self {
longest_corpus,
state,
indices,
current: 0,
};
while current > 0 {
Scheduler::next(&mut scheduler)?;
current -= 1;
}
Ok(scheduler)
}
#[instrument(skip(self), level = "trace")]
pub fn limit_to_corpora(&mut self, corpora: &[&str]) {
self.indices.retain(|index| corpora.contains(&index.name()));
}
}
#[allow(clippy::copy_iterator)]
impl Iterator for RandomScheduler {
type Item = ();
fn next(&mut self) -> Option<Self::Item> {
Scheduler::next(self).ok()
}
}
impl Named for RandomScheduler {
#[allow(clippy::unnecessary_literal_bound)]
fn name(&self) -> &str {
"RandomScheduler"
}
}