Skip to content

Commit

Permalink
feat: add support for calling generators from anywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
camshaft committed Nov 26, 2024
1 parent 665f1c7 commit 1bc046d
Show file tree
Hide file tree
Showing 24 changed files with 1,095 additions and 323 deletions.
34 changes: 34 additions & 0 deletions examples/basic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,40 @@ mod tests {
});
}

#[test]
#[cfg_attr(kani, kani::proof)]
fn exhaustive_test() {
let should_panic = should_panic();

check!()
.exhaustive()
.with_type()
.cloned()
.for_each(|(a, b)| assert!(add(a, b, should_panic) >= a));
}

#[test]
#[cfg_attr(kani, kani::proof)]
fn run_test() {
let should_panic = should_panic();

check!().run(|| {
let a = bolero::any();
let b = bolero::any();
assert!(add(a, b, should_panic) >= a)
});
}

#[test]
#[cfg_attr(kani, kani::proof)]
fn unit_test() {
let should_panic = should_panic();

let a = bolero::any();
let b = bolero::any();
assert!(add(a, b, should_panic) >= a);
}

#[test]
fn panicking_generator_test() {
#[derive(Debug)]
Expand Down
38 changes: 37 additions & 1 deletion lib/bolero-afl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#[doc(hidden)]
#[cfg(any(test, all(feature = "lib", fuzzing_afl)))]
pub mod fuzzer {
use bolero_engine::{driver, input, panic, Engine, Never, TargetLocation, Test};
use bolero_engine::{driver, input, panic, Engine, Never, ScopedEngine, TargetLocation, Test};
use std::io::Read;

extern "C" {
Expand Down Expand Up @@ -54,6 +54,42 @@ pub mod fuzzer {
}
}

impl ScopedEngine for AflEngine {
type Output = Never;

fn run<F, R>(self, mut test: F, options: driver::Options) -> Self::Output
where
F: FnMut() -> R,
R: bolero_engine::IntoResult,
{
panic::set_hook();

// extend the lifetime of the bytes so it can be stored in local storage
let driver = bolero_engine::driver::bytes::Driver::new(vec![], &options);
let driver = bolero_engine::driver::object::Object(driver);
let mut driver = Box::new(driver);

let mut input = AflInput::new(options);

unsafe {
__afl_manual_init();
}

while unsafe { __afl_persistent_loop(1000) } != 0 {
input.reset();
let bytes = core::mem::take(&mut input.input);
let tmp = driver.reset(bytes, &input.options);
let (drv, result) = bolero_engine::any::run(driver, &mut test);
driver = drv;
input.input = driver.reset(tmp, &input.options);

if result.is_err() {
std::process::abort();
}
}
}
}

#[derive(Debug)]
pub struct AflInput {
options: driver::Options,
Expand Down
1 change: 1 addition & 0 deletions lib/bolero-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ readme = "../../README.md"
rust-version = "1.66.0"

[features]
any = ["bolero-generator/any"]
cache = ["bolero-generator/alloc"]
rng = ["rand", "rand_xoshiro", "bolero-generator/alloc"]

Expand Down
33 changes: 33 additions & 0 deletions lib/bolero-engine/src/any.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
pub use bolero_generator::any::*;

/// Runs a function that overrides the default driver for `bolero_generator::any` and
/// returns the result
#[cfg(not(kani))]
pub fn run<D, F, R>(driver: Box<D>, test: F) -> (Box<D>, Result<bool, crate::panic::PanicError>)
where
D: 'static + bolero_generator::driver::object::DynDriver + core::any::Any + Sized,
F: FnMut() -> R,
R: super::IntoResult,
{
let mut test = core::panic::AssertUnwindSafe(test);
scope::with(driver, || {
crate::panic::catch(|| test.0().into_result().map(|_| true))
})
}

/// Runs a function that overrides the default driver for `bolero_generator::any` and
/// returns the result
#[cfg(kani)]
pub fn run<F, R>(
driver: bolero_generator::kani::Driver,
mut test: F,
) -> (
bolero_generator::kani::Driver,
Result<bool, crate::panic::PanicError>,
)
where
F: FnMut() -> R,
R: super::IntoResult,
{
scope::with(driver, || test().into_result().map(|_| true))
}
1 change: 1 addition & 0 deletions lib/bolero-engine/src/failure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ impl<Input: Debug> Display for Failure<Input> {
if let Some(seed) = &self.seed {
writeln!(f, "BOLERO_RANDOM_SEED={}\n", seed)?;
}

writeln!(f, "Input: \n{:#?}\n", self.input)?;
writeln!(f, "Error: \n{}", self.error)?;

Expand Down
31 changes: 20 additions & 11 deletions lib/bolero-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,31 @@ pub use bolero_generator::{

pub type Seed = u128;

#[cfg(feature = "any")]
pub mod any;
pub mod failure;
pub mod input;
#[cfg(not(kani))]
pub mod panic;
#[cfg(kani)]
#[path = "./noop/panic.rs"]
pub mod panic;

#[cfg(feature = "rng")]
pub mod rng;
pub mod shrink;
mod test;
pub use test::*;

pub mod failure;
pub use crate::failure::Failure;

pub mod input;
pub use input::Input;

#[doc(hidden)]
pub mod target_location;
mod test;
#[doc(hidden)]
pub use target_location::TargetLocation;

mod result;
#[cfg(kani)]
pub use bolero_generator::kani;

pub use crate::failure::Failure;
pub use input::Input;
pub use result::IntoResult;
pub use test::*;

/// Trait for defining an engine that executes a test
pub trait Engine<T: Test>: Sized {
Expand All @@ -39,6 +39,15 @@ pub trait Engine<T: Test>: Sized {
fn run(self, test: T, options: driver::Options) -> Self::Output;
}

pub trait ScopedEngine {
type Output;

fn run<F, R>(self, test: F, options: driver::Options) -> Self::Output
where
F: FnMut() -> R,
R: IntoResult;
}

// TODO change this to `!` when stabilized
#[doc(hidden)]
pub type Never = ();
11 changes: 9 additions & 2 deletions lib/bolero-engine/src/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ impl PanicError {
}

#[inline]
pub fn catch<F: RefUnwindSafe + FnOnce() -> Result<Output, PanicError>, Output>(
pub fn catch<F: RefUnwindSafe + FnOnce() -> Result<bool, PanicError>>(
fun: F,
) -> Result<Output, PanicError> {
) -> Result<bool, PanicError> {
let res = catch_unwind(AssertUnwindSafe(|| __panic_marker_start__(fun)));
match res {
Ok(Ok(v)) => Ok(v),
Expand All @@ -101,6 +101,13 @@ pub fn catch<F: RefUnwindSafe + FnOnce() -> Result<Output, PanicError>, Output>(
}
};
}

// if an `any::Error` was returned, then the input wasn't valid
#[cfg(feature = "any")]
if err.downcast_ref::<bolero_generator::any::Error>().is_some() {
return Ok(false);
}

try_downcast!(PanicInfo, "{}");
try_downcast!(anyhow::Error, "{}");
try_downcast!(String, "{}");
Expand Down
5 changes: 4 additions & 1 deletion lib/bolero-generator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ readme = "README.md"
rust-version = "1.66.0"

[features]
default = ["either", "std"]
default = ["any", "either", "std"]
any = ["getrandom", "rand_xoshiro", "std"]
std = ["alloc", "either/use_std"]
alloc = ["rand_core/alloc"]

[dependencies]
arbitrary = { version = "1.0", optional = true }
bolero-generator-derive = { version = "0.11", path = "../bolero-generator-derive" }
either = { version = "1.5", default-features = false, optional = true }
getrandom = { version = "0.2", optional = true }
rand_core = { version = "0.6", default-features = false }
rand_xoshiro = { version = "0.6", optional = true }

[dev-dependencies]
rand = "0.8"
Expand Down
113 changes: 113 additions & 0 deletions lib/bolero-generator/src/any.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use crate::{gen, TypeGenerator, ValueGenerator};

#[cfg(not(kani))]
mod default;

#[cfg(any(kani, test))]
#[cfg_attr(not(kani), allow(dead_code, unused_imports))]
mod kani;

pub mod scope {
#[cfg(not(kani))]
pub use super::default::*;
#[cfg(kani)]
pub use super::kani::*;
}

pub use scope::{assume, fill_bytes, Error};

pub trait Any: ValueGenerator {
fn any(&self) -> Self::Output;
}

impl<G: 'static + ValueGenerator> Any for G {
#[track_caller]
fn any(&self) -> Self::Output {
scope::any(self)
}
}

#[inline]
pub fn any<T: TypeGenerator>() -> T {
gen().any()
}

pub trait AnySliceExt<T> {
fn pick(&self) -> &T;
}

impl<T> AnySliceExt<T> for [T] {
#[inline]
fn pick(&self) -> &T {
let index = (0..self.len()).any();
&self[index]
}
}

pub trait AnySliceMutExt<T> {
fn shuffle(&mut self);
fn fill_any(&mut self)
where
T: TypeGenerator;
}

impl<T> AnySliceMutExt<T> for [T] {
#[inline]
fn shuffle(&mut self) {
for src in (1..self.len()).rev() {
let dst = (0..=src).any();
// invariant: elements with index > src have been locked in place.
self.swap(src, dst);
}
}

#[inline]
fn fill_any(&mut self)
where
T: TypeGenerator,
{
for value in self {
*value = any();
}
}
}

#[cfg(feature = "alloc")]
impl<T> AnySliceMutExt<T> for alloc::collections::VecDeque<T> {
#[inline]
fn shuffle(&mut self) {
for src in (1..self.len()).rev() {
let dst = (0..=src).any();
// invariant: elements with index > src have been locked in place.
self.swap(src, dst);
}
}

#[inline]
fn fill_any(&mut self)
where
T: TypeGenerator,
{
for value in self {
*value = any();
}
}
}

#[inline]
pub fn fill<T>(values: &mut [T])
where
T: TypeGenerator,
{
values.fill_any()
}

#[inline]
pub fn shuffle<T>(items: &mut [T]) {
items.shuffle()
}

#[inline]
pub fn pick<T>(items: &[T]) -> &T {
items.pick()
}
Loading

0 comments on commit 1bc046d

Please sign in to comment.