input reworked as state machine
This commit is contained in:
parent
dec37fcfdd
commit
66a1d4f206
@ -310,12 +310,13 @@ mod tests {
|
||||
use crate::inputs::bytes::BytesInput;
|
||||
use crate::utils::DefaultRand;
|
||||
|
||||
use alloc::rc::Rc;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_queuecorpus() {
|
||||
let rand = DefaultRand::preseeded_rr();
|
||||
let rand: Rc<_> = DefaultRand::preseeded().into();
|
||||
let mut q = QueueCorpus::new(OnDiskCorpus::new(&rand, PathBuf::from("fancy/path")));
|
||||
let i = BytesInput::new(vec![0; 4]);
|
||||
let t = Testcase::with_filename_rr(i, PathBuf::from("fancyfile"));
|
||||
|
@ -6,7 +6,9 @@ use crate::AflError;
|
||||
use alloc::rc::Rc;
|
||||
use core::cell::RefCell;
|
||||
use hashbrown::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// TODO: Give example
|
||||
/// Metadata for a testcase
|
||||
@ -15,6 +17,88 @@ pub trait TestcaseMetadata {
|
||||
fn name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
enum FileBackedTestcase<I, P> {
|
||||
/// A testcase on disk, not yet loaded
|
||||
Stored { filename: P },
|
||||
|
||||
/// A testcase that has been loaded, and not yet dirtied.
|
||||
/// The input should be equal to the on-disk state.
|
||||
Loaded { input: I, filename: P },
|
||||
|
||||
/// A testcase that has been mutated, but not yet written to disk
|
||||
Dirty { input: I, filename: P },
|
||||
}
|
||||
|
||||
impl<I, P> FileBackedTestcase<I, P>
|
||||
where
|
||||
I: Input,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
/// Load a testcase from disk if it is not already loaded.
|
||||
///
|
||||
/// # Errors
|
||||
/// Errors if the testcase is [Dirty](FileBackedTestcase::Dirty)
|
||||
pub fn load(self) -> Result<Self, AflError> {
|
||||
match self {
|
||||
Self::Stored { filename } => {
|
||||
let input = I::from_file(&filename)?;
|
||||
Ok(Self::Loaded { filename, input })
|
||||
}
|
||||
Self::Loaded {
|
||||
input: _,
|
||||
filename: _,
|
||||
} => Ok(self),
|
||||
_ => Err(AflError::IllegalState(
|
||||
"Attempted load on dirty testcase".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Make sure that the in-memory state is syncd to disk, and load it from disk if
|
||||
/// Nece
|
||||
pub fn refresh(self) -> Result<Self, AflError> {
|
||||
match self {
|
||||
Self::Dirty {
|
||||
input: _,
|
||||
filename: _,
|
||||
} => self.save(),
|
||||
other => other.load(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes changes to disk
|
||||
pub fn save(self) -> Result<Self, AflError> {
|
||||
match self {
|
||||
Self::Loaded {
|
||||
input: _,
|
||||
filename: _,
|
||||
} => Ok(self),
|
||||
Self::Dirty { input, filename } => {
|
||||
let mut file = File::create(&filename)?;
|
||||
file.write_all(input.serialize()?)?;
|
||||
|
||||
Ok(Self::Loaded { input, filename })
|
||||
}
|
||||
Self::Stored { filename } => Err(AflError::IllegalState(format!(
|
||||
"Tried to store to {:?} without input (in stored state)",
|
||||
filename.as_ref()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
// Removes contents of this testcase from memory
|
||||
pub fn unload(self) -> Result<Self, AflError> {
|
||||
match self {
|
||||
Self::Loaded { input: _, filename } => Ok(Self::Stored { filename }),
|
||||
Self::Stored { filename: _ } => Ok(self),
|
||||
Self::Dirty {
|
||||
filename: _,
|
||||
input: _,
|
||||
} => self.save(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry in the Testcase Corpus
|
||||
#[derive(Default)]
|
||||
pub struct Testcase<I>
|
||||
@ -22,7 +106,7 @@ where
|
||||
I: Input,
|
||||
{
|
||||
/// The input of this testcase
|
||||
input: Option<I>, // TODO remove box
|
||||
input: Option<I>,
|
||||
/// Filename, if this testcase is backed by a file in the filesystem
|
||||
filename: Option<PathBuf>,
|
||||
/// Map of metadatas associated with this testcase
|
||||
|
@ -110,6 +110,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_engine() {
|
||||
// TODO: Replace _rr with .Into traits
|
||||
let rand: Rc<_> = DefaultRand::preseeded().into();
|
||||
|
||||
let mut corpus = InMemoryCorpus::<BytesInput, _>::new(&rand);
|
||||
@ -121,6 +122,9 @@ mod tests {
|
||||
mutator.add_mutation(mutation_bitflip);
|
||||
let stage = DefaultMutationalStage::new(&rand, &executor, mutator);
|
||||
engine.add_stage(Box::new(stage));
|
||||
|
||||
//
|
||||
|
||||
for i in 0..1000 {
|
||||
engine
|
||||
.fuzz_one(&mut corpus)
|
||||
|
@ -197,8 +197,8 @@ mod tests {
|
||||
fn serialize(&self) -> Result<&[u8], AflError> {
|
||||
Ok("NOP".as_bytes())
|
||||
}
|
||||
fn deserialize(&mut self, _buf: &[u8]) -> Result<(), AflError> {
|
||||
Ok(())
|
||||
fn deserialize(buf: &[u8]) -> Result<Self, AflError> {
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,10 @@ extern crate alloc;
|
||||
|
||||
use core::convert::From;
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::inputs::{HasBytesVec, HasTargetBytes, Input};
|
||||
use crate::AflError;
|
||||
|
||||
@ -15,10 +19,9 @@ impl Input for BytesInput {
|
||||
fn serialize(&self) -> Result<&[u8], AflError> {
|
||||
Ok(&self.bytes)
|
||||
}
|
||||
fn deserialize(&mut self, buf: &[u8]) -> Result<(), AflError> {
|
||||
self.bytes.truncate(0);
|
||||
self.bytes.extend_from_slice(buf);
|
||||
Ok(())
|
||||
|
||||
fn deserialize(buf: &[u8]) -> Result<Self, AflError> {
|
||||
Ok(Self { bytes: buf.into() })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,29 +4,33 @@ pub mod bytes;
|
||||
pub use bytes::BytesInput;
|
||||
|
||||
use core::clone::Clone;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
use crate::AflError;
|
||||
|
||||
/// An input for the target
|
||||
pub trait Input: Clone {
|
||||
/// Write this input to the file
|
||||
fn to_file(&self, path: &PathBuf) -> Result<(), AflError> {
|
||||
fn to_file<P>(&self, path: P) -> Result<(), AflError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(self.serialize()?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load the contents of this input from a file
|
||||
fn from_file(&mut self, path: &PathBuf) -> Result<(), AflError> {
|
||||
let mut file = File::create(path)?;
|
||||
let mut buf = vec![];
|
||||
file.read_to_end(&mut buf)?;
|
||||
self.deserialize(&buf)?;
|
||||
Ok(())
|
||||
fn from_file<P>(path: P) -> Result<Self, AflError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut file = File::open(path).map_err(AflError::File)?;
|
||||
let mut bytes: Vec<u8> = vec![];
|
||||
file.read_to_end(&mut bytes).map_err(AflError::File)?;
|
||||
Self::deserialize(&bytes)
|
||||
}
|
||||
|
||||
/// Serialize this input, for later deserialization.
|
||||
@ -35,7 +39,7 @@ pub trait Input: Clone {
|
||||
fn serialize(&self) -> Result<&[u8], AflError>;
|
||||
|
||||
/// Deserialize this input, using the bytes serialized before.
|
||||
fn deserialize(&mut self, buf: &[u8]) -> Result<(), AflError>;
|
||||
fn deserialize(buf: &[u8]) -> Result<Self, AflError>;
|
||||
}
|
||||
|
||||
/// Can be serialized to a bytes representation
|
||||
|
@ -31,6 +31,8 @@ pub enum AflError {
|
||||
IteratorEnd(String),
|
||||
#[error("Not implemented: {0}")]
|
||||
NotImplemented(String),
|
||||
#[error("Illegal state: {0}")]
|
||||
IllegalState(String),
|
||||
#[error("Unknown error: {0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
|
@ -324,16 +324,16 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::corpus::{Corpus, InMemoryCorpus};
|
||||
use crate::inputs::{BytesInput, HasBytesVec};
|
||||
use crate::mutators::scheduled::mutation_splice;
|
||||
use crate::utils::{DefaultHasRand, Rand, XKCDRand};
|
||||
use alloc::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn test_mut_splice() {
|
||||
// With the current impl, seed of 1 will result in a split at pos 2.
|
||||
let rand = &XKCDRand::new_rr();
|
||||
let rand: Rc<_> = XKCDRand::new().into();
|
||||
let mut has_rand = DefaultHasRand::new(&rand);
|
||||
let mut corpus = InMemoryCorpus::new(&rand);
|
||||
corpus.add_input(BytesInput::new(vec!['a' as u8, 'b' as u8, 'c' as u8]));
|
||||
|
23
src/utils.rs
23
src/utils.rs
@ -128,11 +128,6 @@ impl Xoshiro256StarRand {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// Creates a new Xoshiro rand with the given seed, wrapped in a Rc<RefCell<T>>.
|
||||
pub fn new_rr(seed: u64) -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self::new(seed)))
|
||||
}
|
||||
|
||||
/// Creates a rand instance, pre-seeded with the current time in nanoseconds.
|
||||
pub fn preseeded() -> Self {
|
||||
let seed = SystemTime::now()
|
||||
@ -141,11 +136,6 @@ impl Xoshiro256StarRand {
|
||||
.as_nanos() as u64;
|
||||
Self::new(seed)
|
||||
}
|
||||
|
||||
/// Creates a new rand instance, pre-seeded
|
||||
pub fn preseeded_rr() -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self::preseeded()))
|
||||
}
|
||||
}
|
||||
|
||||
/// fake rand, for testing purposes
|
||||
@ -166,15 +156,18 @@ impl Rand for XKCDRand {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Into<Rc<RefCell<Self>>> for XKCDRand {
|
||||
fn into(self) -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl XKCDRand {
|
||||
pub fn new() -> Self {
|
||||
Self { val: 4 }
|
||||
}
|
||||
|
||||
pub fn new_rr() -> Rc<RefCell<Self>> {
|
||||
Rc::new(RefCell::new(Self::new()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A very basic HasRand
|
||||
@ -238,7 +231,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_has_rand() {
|
||||
let rand = DefaultRand::preseeded_rr();
|
||||
let rand = DefaultRand::preseeded().into();
|
||||
let has_rand = DefaultHasRand::new(&rand);
|
||||
|
||||
assert!(has_rand.rand_below(100) < 100);
|
||||
|
Loading…
x
Reference in New Issue
Block a user