use core::{
cell::{Ref, RefMut},
fmt::Debug,
marker::PhantomData,
time::Duration,
};
#[cfg(feature = "std")]
use std::{
fs,
path::{Path, PathBuf},
vec::Vec,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[cfg(test)]
use crate::bolts::rands::StdRand;
use crate::{
bolts::{
rands::Rand,
serdeany::{NamedSerdeAnyMap, SerdeAny, SerdeAnyMap},
},
corpus::{Corpus, CorpusId, HasTestcase, Testcase},
events::{Event, EventFirer, LogSeverity},
feedbacks::Feedback,
fuzzer::{Evaluator, ExecuteInputResult},
generators::Generator,
inputs::{Input, UsesInput},
monitors::ClientPerfMonitor,
Error,
};
pub const DEFAULT_MAX_SIZE: usize = 1_048_576;
pub trait State: UsesInput + Serialize + DeserializeOwned {}
pub trait UsesState: UsesInput<Input = <Self::State as UsesInput>::Input> {
type State: UsesInput;
}
impl<KS> UsesInput for KS
where
KS: UsesState,
{
type Input = <KS::State as UsesInput>::Input;
}
pub trait HasCorpus: UsesInput {
type Corpus: Corpus<Input = <Self as UsesInput>::Input>;
fn corpus(&self) -> &Self::Corpus;
fn corpus_mut(&mut self) -> &mut Self::Corpus;
}
pub trait HasMaxSize {
fn max_size(&self) -> usize;
fn set_max_size(&mut self, max_size: usize);
}
pub trait HasSolutions: UsesInput {
type Solutions: Corpus<Input = <Self as UsesInput>::Input>;
fn solutions(&self) -> &Self::Solutions;
fn solutions_mut(&mut self) -> &mut Self::Solutions;
}
pub trait HasRand {
type Rand: Rand;
fn rand(&self) -> &Self::Rand;
fn rand_mut(&mut self) -> &mut Self::Rand;
}
pub trait HasClientPerfMonitor {
fn introspection_monitor(&self) -> &ClientPerfMonitor;
fn introspection_monitor_mut(&mut self) -> &mut ClientPerfMonitor;
}
pub trait HasMetadata {
fn metadata_map(&self) -> &SerdeAnyMap;
fn metadata_map_mut(&mut self) -> &mut SerdeAnyMap;
#[inline]
fn add_metadata<M>(&mut self, meta: M)
where
M: SerdeAny,
{
self.metadata_map_mut().insert(meta);
}
#[inline]
fn has_metadata<M>(&self) -> bool
where
M: SerdeAny,
{
self.metadata_map().get::<M>().is_some()
}
#[inline]
fn metadata<M>(&self) -> Result<&M, Error>
where
M: SerdeAny,
{
self.metadata_map().get::<M>().ok_or_else(|| {
Error::key_not_found(format!("{} not found", core::any::type_name::<M>()))
})
}
#[inline]
fn metadata_mut<M>(&mut self) -> Result<&mut M, Error>
where
M: SerdeAny,
{
self.metadata_map_mut().get_mut::<M>().ok_or_else(|| {
Error::key_not_found(format!("{} not found", core::any::type_name::<M>()))
})
}
}
pub trait HasNamedMetadata {
fn named_metadata_map(&self) -> &NamedSerdeAnyMap;
fn named_metadata_map_mut(&mut self) -> &mut NamedSerdeAnyMap;
#[inline]
fn add_named_metadata<M>(&mut self, meta: M, name: &str)
where
M: SerdeAny,
{
self.named_metadata_map_mut().insert(meta, name);
}
#[inline]
fn has_named_metadata<M>(&self, name: &str) -> bool
where
M: SerdeAny,
{
self.named_metadata_map().contains::<M>(name)
}
#[inline]
fn named_metadata<M>(&self, name: &str) -> Result<&M, Error>
where
M: SerdeAny,
{
self.named_metadata_map().get::<M>(name).ok_or_else(|| {
Error::key_not_found(format!("{} not found", core::any::type_name::<M>()))
})
}
#[inline]
fn named_metadata_mut<M>(&mut self, name: &str) -> Result<&mut M, Error>
where
M: SerdeAny,
{
self.named_metadata_map_mut()
.get_mut::<M>(name)
.ok_or_else(|| {
Error::key_not_found(format!("{} not found", core::any::type_name::<M>()))
})
}
}
pub trait HasExecutions {
fn executions(&self) -> &usize;
fn executions_mut(&mut self) -> &mut usize;
}
pub trait HasStartTime {
fn start_time(&self) -> &Duration;
fn start_time_mut(&mut self) -> &mut Duration;
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "
C: serde::Serialize + for<'a> serde::Deserialize<'a>,
SC: serde::Serialize + for<'a> serde::Deserialize<'a>,
R: serde::Serialize + for<'a> serde::Deserialize<'a>
")]
pub struct StdState<I, C, R, SC> {
rand: R,
executions: usize,
start_time: Duration,
corpus: C,
solutions: SC,
metadata: SerdeAnyMap,
named_metadata: NamedSerdeAnyMap,
max_size: usize,
#[cfg(feature = "introspection")]
introspection_monitor: ClientPerfMonitor,
#[cfg(feature = "std")]
remaining_initial_files: Option<Vec<PathBuf>>,
phantom: PhantomData<I>,
}
impl<I, C, R, SC> UsesInput for StdState<I, C, R, SC>
where
I: Input,
{
type Input = I;
}
impl<I, C, R, SC> State for StdState<I, C, R, SC>
where
C: Corpus<Input = Self::Input>,
R: Rand,
SC: Corpus<Input = Self::Input>,
Self: UsesInput,
{
}
impl<I, C, R, SC> HasRand for StdState<I, C, R, SC>
where
R: Rand,
{
type Rand = R;
#[inline]
fn rand(&self) -> &Self::Rand {
&self.rand
}
#[inline]
fn rand_mut(&mut self) -> &mut Self::Rand {
&mut self.rand
}
}
impl<I, C, R, SC> HasCorpus for StdState<I, C, R, SC>
where
I: Input,
C: Corpus<Input = <Self as UsesInput>::Input>,
R: Rand,
{
type Corpus = C;
#[inline]
fn corpus(&self) -> &Self::Corpus {
&self.corpus
}
#[inline]
fn corpus_mut(&mut self) -> &mut Self::Corpus {
&mut self.corpus
}
}
impl<I, C, R, SC> HasTestcase for StdState<I, C, R, SC>
where
I: Input,
C: Corpus<Input = <Self as UsesInput>::Input>,
R: Rand,
{
fn testcase(&self, id: CorpusId) -> Result<Ref<Testcase<<Self as UsesInput>::Input>>, Error> {
Ok(self.corpus().get(id)?.borrow())
}
fn testcase_mut(
&self,
id: CorpusId,
) -> Result<RefMut<Testcase<<Self as UsesInput>::Input>>, Error> {
Ok(self.corpus().get(id)?.borrow_mut())
}
}
impl<I, C, R, SC> HasSolutions for StdState<I, C, R, SC>
where
I: Input,
SC: Corpus<Input = <Self as UsesInput>::Input>,
{
type Solutions = SC;
#[inline]
fn solutions(&self) -> &SC {
&self.solutions
}
#[inline]
fn solutions_mut(&mut self) -> &mut SC {
&mut self.solutions
}
}
impl<I, C, R, SC> HasMetadata for StdState<I, C, R, SC> {
#[inline]
fn metadata_map(&self) -> &SerdeAnyMap {
&self.metadata
}
#[inline]
fn metadata_map_mut(&mut self) -> &mut SerdeAnyMap {
&mut self.metadata
}
}
impl<I, C, R, SC> HasNamedMetadata for StdState<I, C, R, SC> {
#[inline]
fn named_metadata_map(&self) -> &NamedSerdeAnyMap {
&self.named_metadata
}
#[inline]
fn named_metadata_map_mut(&mut self) -> &mut NamedSerdeAnyMap {
&mut self.named_metadata
}
}
impl<I, C, R, SC> HasExecutions for StdState<I, C, R, SC> {
#[inline]
fn executions(&self) -> &usize {
&self.executions
}
#[inline]
fn executions_mut(&mut self) -> &mut usize {
&mut self.executions
}
}
impl<I, C, R, SC> HasMaxSize for StdState<I, C, R, SC> {
fn max_size(&self) -> usize {
self.max_size
}
fn set_max_size(&mut self, max_size: usize) {
self.max_size = max_size;
}
}
impl<I, C, R, SC> HasStartTime for StdState<I, C, R, SC> {
#[inline]
fn start_time(&self) -> &Duration {
&self.start_time
}
#[inline]
fn start_time_mut(&mut self) -> &mut Duration {
&mut self.start_time
}
}
#[cfg(feature = "std")]
impl<C, I, R, SC> StdState<I, C, R, SC>
where
I: Input,
C: Corpus<Input = <Self as UsesInput>::Input>,
R: Rand,
SC: Corpus<Input = <Self as UsesInput>::Input>,
{
pub fn must_load_initial_inputs(&self) -> bool {
self.corpus().count() == 0
|| (self.remaining_initial_files.is_some()
&& !self.remaining_initial_files.as_ref().unwrap().is_empty())
}
fn visit_initial_directory(files: &mut Vec<PathBuf>, in_dir: &Path) -> Result<(), Error> {
for entry in fs::read_dir(in_dir)? {
let entry = entry?;
let path = entry.path();
if path.file_name().unwrap().to_string_lossy().starts_with('.') {
continue;
}
let attributes = fs::metadata(&path);
if attributes.is_err() {
continue;
}
let attr = attributes?;
if attr.is_file() && attr.len() > 0 {
files.push(path);
} else if attr.is_dir() {
Self::visit_initial_directory(files, &path)?;
}
}
Ok(())
}
fn load_initial_inputs_custom<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
in_dirs: &[PathBuf],
forced: bool,
loader: &mut dyn FnMut(&mut Z, &mut Self, &Path) -> Result<I, Error>,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
if let Some(remaining) = self.remaining_initial_files.as_ref() {
if remaining.is_empty() {
return Ok(());
}
} else {
let mut files = vec![];
for in_dir in in_dirs {
Self::visit_initial_directory(&mut files, in_dir)?;
}
self.remaining_initial_files = Some(files);
}
self.continue_loading_initial_inputs_custom(fuzzer, executor, manager, forced, loader)
}
fn load_initial_inputs_custom_by_filenames<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
file_list: &[PathBuf],
forced: bool,
loader: &mut dyn FnMut(&mut Z, &mut Self, &Path) -> Result<I, Error>,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
if let Some(remaining) = self.remaining_initial_files.as_ref() {
if remaining.is_empty() {
return Ok(());
}
} else {
self.remaining_initial_files = Some(file_list.to_vec());
}
self.continue_loading_initial_inputs_custom(fuzzer, executor, manager, forced, loader)
}
fn continue_loading_initial_inputs_custom<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
forced: bool,
loader: &mut dyn FnMut(&mut Z, &mut Self, &Path) -> Result<I, Error>,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
if self.remaining_initial_files.is_none() {
return Err(Error::illegal_state("No initial files were loaded, cannot continue loading. Call a `load_initial_input` fn first!"));
}
while let Some(path) = self.remaining_initial_files.as_mut().unwrap().pop() {
log::info!("Loading file {:?} ...", &path);
let input = loader(fuzzer, self, &path)?;
if forced {
let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?;
} else {
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?;
if res == ExecuteInputResult::None {
log::warn!("File {:?} was not interesting, skipped.", &path);
}
}
}
manager.fire(
self,
Event::Log {
severity_level: LogSeverity::Debug,
message: format!("Loaded {} initial testcases.", self.corpus().count()), phantom: PhantomData::<I>,
},
)?;
Ok(())
}
pub fn load_initial_inputs_by_filenames<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
file_list: &[PathBuf],
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.load_initial_inputs_custom_by_filenames(
fuzzer,
executor,
manager,
file_list,
false,
&mut |_, _, path| I::from_file(path),
)
}
pub fn load_initial_inputs_forced<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
in_dirs: &[PathBuf],
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.load_initial_inputs_custom(
fuzzer,
executor,
manager,
in_dirs,
true,
&mut |_, _, path| I::from_file(path),
)
}
pub fn load_initial_inputs_by_filenames_forced<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
file_list: &[PathBuf],
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.load_initial_inputs_custom_by_filenames(
fuzzer,
executor,
manager,
file_list,
true,
&mut |_, _, path| I::from_file(path),
)
}
pub fn load_initial_inputs<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
in_dirs: &[PathBuf],
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.load_initial_inputs_custom(
fuzzer,
executor,
manager,
in_dirs,
false,
&mut |_, _, path| I::from_file(path),
)
}
}
impl<C, I, R, SC> StdState<I, C, R, SC>
where
I: Input,
C: Corpus<Input = <Self as UsesInput>::Input>,
R: Rand,
SC: Corpus<Input = <Self as UsesInput>::Input>,
{
fn generate_initial_internal<G, E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
generator: &mut G,
manager: &mut EM,
num: usize,
forced: bool,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
G: Generator<<Self as UsesInput>::Input, Self>,
Z: Evaluator<E, EM, State = Self>,
{
let mut added = 0;
for _ in 0..num {
let input = generator.generate(self)?;
if forced {
let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?;
added += 1;
} else {
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?;
if res != ExecuteInputResult::None {
added += 1;
}
}
}
manager.fire(
self,
Event::Log {
severity_level: LogSeverity::Debug,
message: format!("Loaded {added} over {num} initial testcases"),
phantom: PhantomData,
},
)?;
Ok(())
}
pub fn generate_initial_inputs_forced<G, E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
generator: &mut G,
manager: &mut EM,
num: usize,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
G: Generator<<Self as UsesInput>::Input, Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.generate_initial_internal(fuzzer, executor, generator, manager, num, true)
}
pub fn generate_initial_inputs<G, E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
generator: &mut G,
manager: &mut EM,
num: usize,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
G: Generator<<Self as UsesInput>::Input, Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.generate_initial_internal(fuzzer, executor, generator, manager, num, false)
}
pub fn new<F, O>(
rand: R,
corpus: C,
solutions: SC,
feedback: &mut F,
objective: &mut O,
) -> Result<Self, Error>
where
F: Feedback<Self>,
O: Feedback<Self>,
{
let mut state = Self {
rand,
executions: 0,
start_time: Duration::from_millis(0),
metadata: SerdeAnyMap::default(),
named_metadata: NamedSerdeAnyMap::default(),
corpus,
solutions,
max_size: DEFAULT_MAX_SIZE,
#[cfg(feature = "introspection")]
introspection_monitor: ClientPerfMonitor::new(),
#[cfg(feature = "std")]
remaining_initial_files: None,
phantom: PhantomData,
};
feedback.init_state(&mut state)?;
objective.init_state(&mut state)?;
Ok(state)
}
}
#[cfg(feature = "introspection")]
impl<I, C, R, SC> HasClientPerfMonitor for StdState<I, C, R, SC> {
fn introspection_monitor(&self) -> &ClientPerfMonitor {
&self.introspection_monitor
}
fn introspection_monitor_mut(&mut self) -> &mut ClientPerfMonitor {
&mut self.introspection_monitor
}
}
#[cfg(not(feature = "introspection"))]
impl<I, C, R, SC> HasClientPerfMonitor for StdState<I, C, R, SC> {
fn introspection_monitor(&self) -> &ClientPerfMonitor {
unimplemented!()
}
fn introspection_monitor_mut(&mut self) -> &mut ClientPerfMonitor {
unimplemented!()
}
}
#[cfg(test)]
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct NopState<I> {
metadata: SerdeAnyMap,
rand: StdRand,
phantom: PhantomData<I>,
}
#[cfg(test)]
impl<I> NopState<I> {
#[must_use]
pub fn new() -> Self {
NopState {
metadata: SerdeAnyMap::new(),
rand: StdRand::default(),
phantom: PhantomData,
}
}
}
#[cfg(test)]
impl<I> UsesInput for NopState<I>
where
I: Input,
{
type Input = I;
}
#[cfg(test)]
impl<I> HasExecutions for NopState<I> {
fn executions(&self) -> &usize {
unimplemented!()
}
fn executions_mut(&mut self) -> &mut usize {
unimplemented!()
}
}
#[cfg(test)]
impl<I> HasMetadata for NopState<I> {
fn metadata_map(&self) -> &SerdeAnyMap {
&self.metadata
}
fn metadata_map_mut(&mut self) -> &mut SerdeAnyMap {
&mut self.metadata
}
}
#[cfg(test)]
impl<I> HasRand for NopState<I> {
type Rand = StdRand;
fn rand(&self) -> &Self::Rand {
&self.rand
}
fn rand_mut(&mut self) -> &mut Self::Rand {
&mut self.rand
}
}
#[cfg(test)]
impl<I> HasClientPerfMonitor for NopState<I> {
fn introspection_monitor(&self) -> &ClientPerfMonitor {
unimplemented!()
}
fn introspection_monitor_mut(&mut self) -> &mut ClientPerfMonitor {
unimplemented!()
}
}
#[cfg(test)]
impl<I> State for NopState<I> where I: Input {}
#[cfg(feature = "python")]
#[allow(missing_docs)]
pub mod pybind {
use alloc::{boxed::Box, vec::Vec};
use std::path::PathBuf;
use pyo3::{prelude::*, types::PyDict};
use crate::{
bolts::{ownedref::OwnedMutPtr, rands::pybind::PythonRand},
corpus::pybind::PythonCorpus,
events::pybind::PythonEventManager,
executors::pybind::PythonExecutor,
feedbacks::pybind::PythonFeedback,
fuzzer::pybind::PythonStdFuzzerWrapper,
generators::pybind::PythonGenerator,
inputs::BytesInput,
pybind::PythonMetadata,
state::{
HasCorpus, HasExecutions, HasMaxSize, HasMetadata, HasRand, HasSolutions, StdState,
},
};
pub type PythonStdState = StdState<BytesInput, PythonCorpus, PythonRand, PythonCorpus>;
#[pyclass(unsendable, name = "StdState")]
#[derive(Debug)]
pub struct PythonStdStateWrapper {
pub inner: OwnedMutPtr<PythonStdState>,
}
impl PythonStdStateWrapper {
pub fn wrap(r: &mut PythonStdState) -> Self {
Self {
inner: OwnedMutPtr::Ptr(r),
}
}
#[must_use]
pub fn unwrap(&self) -> &PythonStdState {
self.inner.as_ref()
}
pub fn unwrap_mut(&mut self) -> &mut PythonStdState {
self.inner.as_mut()
}
}
#[pymethods]
impl PythonStdStateWrapper {
#[new]
fn new(
py_rand: PythonRand,
corpus: PythonCorpus,
solutions: PythonCorpus,
feedback: &mut PythonFeedback,
objective: &mut PythonFeedback,
) -> Self {
Self {
inner: OwnedMutPtr::Owned(Box::new(
StdState::new(py_rand, corpus, solutions, feedback, objective)
.expect("Failed to create a new StdState"),
)),
}
}
fn metadata(&mut self) -> PyObject {
let meta = self.inner.as_mut().metadata_map_mut();
if !meta.contains::<PythonMetadata>() {
Python::with_gil(|py| {
let dict: Py<PyDict> = PyDict::new(py).into();
meta.insert(PythonMetadata::new(dict.to_object(py)));
});
}
meta.get::<PythonMetadata>().unwrap().map.clone()
}
fn rand(&self) -> PythonRand {
self.inner.as_ref().rand().clone()
}
fn corpus(&self) -> PythonCorpus {
self.inner.as_ref().corpus().clone()
}
fn solutions(&self) -> PythonCorpus {
self.inner.as_ref().solutions().clone()
}
fn executions(&self) -> usize {
*self.inner.as_ref().executions()
}
fn max_size(&self) -> usize {
self.inner.as_ref().max_size()
}
fn generate_initial_inputs(
&mut self,
py_fuzzer: &mut PythonStdFuzzerWrapper,
py_executor: &mut PythonExecutor,
py_generator: &mut PythonGenerator,
py_mgr: &mut PythonEventManager,
num: usize,
) {
self.inner
.as_mut()
.generate_initial_inputs(
py_fuzzer.unwrap_mut(),
py_executor,
py_generator,
py_mgr,
num,
)
.expect("Failed to generate the initial corpus");
}
#[allow(clippy::needless_pass_by_value)]
fn load_initial_inputs(
&mut self,
py_fuzzer: &mut PythonStdFuzzerWrapper,
py_executor: &mut PythonExecutor,
py_mgr: &mut PythonEventManager,
in_dirs: Vec<PathBuf>,
) {
self.inner
.as_mut()
.load_initial_inputs(py_fuzzer.unwrap_mut(), py_executor, py_mgr, &in_dirs)
.expect("Failed to load the initial corpus");
}
}
pub fn register(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PythonStdStateWrapper>()?;
Ok(())
}
}