Skip to content

Commit 9e0c56e

Browse files
authored
feat: add support for calling generators from anywhere (#261)
1 parent e01216c commit 9e0c56e

File tree

20 files changed

+598
-193
lines changed

20 files changed

+598
-193
lines changed

examples/basic/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,10 @@ inherits = "dev"
3636
opt-level = 3
3737
incremental = false
3838
codegen-units = 1
39+
40+
[lints.rust.unexpected_cfgs]
41+
level = "warn"
42+
check-cfg = [
43+
'cfg(kani)',
44+
'cfg(bolero_should_panic)',
45+
]

examples/basic/src/lib.rs

+34
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,40 @@ mod tests {
5151
});
5252
}
5353

54+
#[test]
55+
#[cfg_attr(kani, kani::proof)]
56+
fn exhaustive_test() {
57+
let should_panic = should_panic();
58+
59+
check!()
60+
.exhaustive()
61+
.with_type()
62+
.cloned()
63+
.for_each(|(a, b)| assert!(add(a, b, should_panic) >= a));
64+
}
65+
66+
#[test]
67+
#[cfg_attr(kani, kani::proof)]
68+
fn run_test() {
69+
let should_panic = should_panic();
70+
71+
check!().run(|| {
72+
let a = bolero::any();
73+
let b = bolero::any();
74+
assert!(add(a, b, should_panic) >= a)
75+
});
76+
}
77+
78+
#[test]
79+
#[cfg_attr(kani, kani::proof)]
80+
fn unit_test() {
81+
let should_panic = should_panic();
82+
83+
let a = bolero::any();
84+
let b = bolero::any();
85+
assert!(add(a, b, should_panic) >= a);
86+
}
87+
5488
#[test]
5589
fn panicking_generator_test() {
5690
#[derive(Debug)]

examples/boolean-tree/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,10 @@ inherits = "dev"
1717
opt-level = 3
1818
incremental = false
1919
codegen-units = 1
20+
21+
[lints.rust.unexpected_cfgs]
22+
level = "warn"
23+
check-cfg = [
24+
'cfg(kani)',
25+
'cfg(bolero_should_panic)',
26+
]

examples/rle-stack/Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@ inherits = "dev"
1414
opt-level = 3
1515
incremental = false
1616
codegen-units = 1
17+
18+
[lints.rust.unexpected_cfgs]
19+
level = "warn"
20+
check-cfg = [
21+
'cfg(kani)',
22+
'cfg(bolero_should_panic)',
23+
]

lib/bolero-afl/src/lib.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#[doc(hidden)]
66
#[cfg(any(test, all(feature = "lib", fuzzing_afl)))]
77
pub mod fuzzer {
8-
use bolero_engine::{driver, input, panic, Engine, Never, TargetLocation, Test};
8+
use bolero_engine::{driver, input, panic, Engine, Never, ScopedEngine, TargetLocation, Test};
99
use std::io::Read;
1010

1111
extern "C" {
@@ -54,6 +54,44 @@ pub mod fuzzer {
5454
}
5555
}
5656

57+
impl ScopedEngine for AflEngine {
58+
type Output = Never;
59+
60+
fn run<F, R>(self, mut test: F, options: driver::Options) -> Self::Output
61+
where
62+
F: FnMut() -> R,
63+
R: bolero_engine::IntoResult,
64+
{
65+
panic::set_hook();
66+
67+
// extend the lifetime of the bytes so it can be stored in local storage
68+
let driver = bolero_engine::driver::bytes::Driver::new(vec![], &options);
69+
let driver = bolero_engine::driver::object::Object(driver);
70+
let mut driver = Box::new(driver);
71+
72+
let mut input = AflInput::new(options);
73+
74+
unsafe {
75+
__afl_manual_init();
76+
}
77+
78+
while unsafe { __afl_persistent_loop(1000) } != 0 {
79+
input.reset();
80+
let bytes = core::mem::take(&mut input.input);
81+
let tmp = driver.reset(bytes, &input.options);
82+
let (drv, result) = bolero_engine::any::run(driver, &mut test);
83+
driver = drv;
84+
input.input = driver.reset(tmp, &input.options);
85+
86+
if result.is_err() {
87+
std::process::abort();
88+
}
89+
}
90+
91+
std::process::exit(0);
92+
}
93+
}
94+
5795
#[derive(Debug)]
5896
pub struct AflInput {
5997
options: driver::Options,

lib/bolero-engine/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ readme = "../../README.md"
1212
rust-version = "1.66.0"
1313

1414
[features]
15+
any = ["bolero-generator/any"]
1516
cache = ["bolero-generator/alloc"]
1617
rng = ["rand", "rand_xoshiro", "bolero-generator/alloc"]
1718

lib/bolero-engine/src/any.rs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
pub use bolero_generator::any::*;
2+
3+
/// Runs a function that overrides the default driver for `bolero_generator::any` and
4+
/// returns the result
5+
#[cfg(not(kani))]
6+
pub fn run<D, F, R>(driver: Box<D>, test: F) -> (Box<D>, Result<bool, crate::panic::PanicError>)
7+
where
8+
D: 'static + bolero_generator::driver::object::DynDriver + core::any::Any + Sized,
9+
F: FnMut() -> R,
10+
R: super::IntoResult,
11+
{
12+
let mut test = core::panic::AssertUnwindSafe(test);
13+
scope::with(driver, || {
14+
crate::panic::catch(|| test.0().into_result().map(|_| true))
15+
})
16+
}
17+
18+
/// Runs a function that overrides the default driver for `bolero_generator::any` and
19+
/// returns the result
20+
#[cfg(kani)]
21+
pub fn run<F, R>(
22+
driver: bolero_generator::kani::Driver,
23+
mut test: F,
24+
) -> (
25+
bolero_generator::kani::Driver,
26+
Result<bool, crate::panic::PanicError>,
27+
)
28+
where
29+
F: FnMut() -> R,
30+
R: super::IntoResult,
31+
{
32+
scope::with(driver, || test().into_result().map(|_| true))
33+
}

lib/bolero-engine/src/failure.rs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ impl<Input: Debug> Display for Failure<Input> {
2121
if let Some(seed) = &self.seed {
2222
writeln!(f, "BOLERO_RANDOM_SEED={}\n", seed)?;
2323
}
24+
2425
writeln!(f, "Input: \n{:#?}\n", self.input)?;
2526
writeln!(f, "Error: \n{}", self.error)?;
2627

lib/bolero-engine/src/lib.rs

+25-20
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,35 @@
1-
pub use anyhow::Error;
2-
pub use bolero_generator::{
3-
driver::{self, Driver},
4-
TypeGenerator, ValueGenerator,
5-
};
6-
7-
#[cfg(kani)]
8-
pub use bolero_generator::kani;
9-
101
pub type Seed = u128;
112

3+
#[cfg(feature = "any")]
4+
pub mod any;
5+
pub mod failure;
6+
pub mod input;
127
#[cfg(not(kani))]
138
pub mod panic;
149
#[cfg(kani)]
1510
#[path = "./noop/panic.rs"]
1611
pub mod panic;
17-
12+
mod result;
1813
#[cfg(feature = "rng")]
1914
pub mod rng;
2015
pub mod shrink;
16+
#[doc(hidden)]
17+
pub mod target_location;
2118
mod test;
22-
pub use test::*;
2319

24-
pub mod failure;
2520
pub use crate::failure::Failure;
26-
27-
pub mod input;
21+
pub use anyhow::Error;
22+
#[cfg(kani)]
23+
pub use bolero_generator::kani;
24+
pub use bolero_generator::{
25+
driver::{self, Driver},
26+
TypeGenerator, ValueGenerator,
27+
};
2828
pub use input::Input;
29-
30-
#[doc(hidden)]
31-
pub mod target_location;
29+
pub use result::IntoResult;
3230
#[doc(hidden)]
3331
pub use target_location::TargetLocation;
34-
35-
mod result;
36-
pub use result::IntoResult;
32+
pub use test::*;
3733

3834
/// Trait for defining an engine that executes a test
3935
pub trait Engine<T: Test>: Sized {
@@ -42,6 +38,15 @@ pub trait Engine<T: Test>: Sized {
4238
fn run(self, test: T, options: driver::Options) -> Self::Output;
4339
}
4440

41+
pub trait ScopedEngine {
42+
type Output;
43+
44+
fn run<F, R>(self, test: F, options: driver::Options) -> Self::Output
45+
where
46+
F: FnMut() -> R,
47+
R: IntoResult;
48+
}
49+
4550
// TODO change this to `!` when stabilized
4651
#[doc(hidden)]
4752
pub type Never = ();

lib/bolero-engine/src/panic.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ impl PanicError {
8383
}
8484

8585
#[inline]
86-
pub fn catch<F: RefUnwindSafe + FnOnce() -> Result<Output, PanicError>, Output>(
86+
pub fn catch<F: RefUnwindSafe + FnOnce() -> Result<bool, PanicError>>(
8787
fun: F,
88-
) -> Result<Output, PanicError> {
88+
) -> Result<bool, PanicError> {
8989
let res = catch_unwind(AssertUnwindSafe(|| __panic_marker_start__(fun)));
9090
match res {
9191
Ok(Ok(v)) => Ok(v),
@@ -101,6 +101,13 @@ pub fn catch<F: RefUnwindSafe + FnOnce() -> Result<Output, PanicError>, Output>(
101101
}
102102
};
103103
}
104+
105+
// if an `any::Error` was returned, then the input wasn't valid
106+
#[cfg(feature = "any")]
107+
if err.downcast_ref::<bolero_generator::any::Error>().is_some() {
108+
return Ok(false);
109+
}
110+
104111
try_downcast!(PanicInfo, "{}");
105112
try_downcast!(anyhow::Error, "{}");
106113
try_downcast!(String, "{}");

lib/bolero-generator/src/any/kani.rs

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::cell::RefCell;
2-
31
type Type = crate::kani::Driver;
42

53
pub use core::convert::Infallible as Error;

lib/bolero-honggfuzz/src/lib.rs

+37-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#[cfg(any(test, all(feature = "lib", fuzzing_honggfuzz)))]
77
pub mod fuzzer {
88
use bolero_engine::{
9-
driver, input, panic as bolero_panic, Engine, Never, TargetLocation, Test,
9+
driver, input, panic as bolero_panic, Engine, Never, ScopedEngine, TargetLocation, Test,
1010
};
1111
use std::{mem::MaybeUninit, slice};
1212

@@ -39,6 +39,35 @@ pub mod fuzzer {
3939
}
4040
}
4141

42+
impl ScopedEngine for HonggfuzzEngine {
43+
type Output = Never;
44+
45+
fn run<F, R>(self, mut test: F, options: driver::Options) -> Self::Output
46+
where
47+
F: FnMut() -> R,
48+
R: bolero_engine::IntoResult,
49+
{
50+
bolero_panic::set_hook();
51+
52+
// extend the lifetime of the bytes so it can be stored in local storage
53+
let driver = bolero_engine::driver::bytes::Driver::new(&[][..], &options);
54+
let driver = bolero_engine::driver::object::Object(driver);
55+
let mut driver = Box::new(driver);
56+
57+
let mut input = HonggfuzzInput::new(options);
58+
59+
loop {
60+
driver.reset(input.get_slice(), &input.options);
61+
let (drv, result) = bolero_engine::any::run(driver, &mut test);
62+
driver = drv;
63+
64+
if result.is_err() {
65+
std::process::abort();
66+
}
67+
}
68+
}
69+
}
70+
4271
pub struct HonggfuzzInput {
4372
buf_ptr: MaybeUninit<*const u8>,
4473
len_ptr: MaybeUninit<usize>,
@@ -54,11 +83,15 @@ pub mod fuzzer {
5483
}
5584
}
5685

57-
fn test_input(&mut self) -> input::Bytes {
58-
let input = unsafe {
86+
fn get_slice(&mut self) -> &'static [u8] {
87+
unsafe {
5988
HF_ITER(self.buf_ptr.as_mut_ptr(), self.len_ptr.as_mut_ptr());
6089
slice::from_raw_parts(self.buf_ptr.assume_init(), self.len_ptr.assume_init())
61-
};
90+
}
91+
}
92+
93+
fn test_input(&mut self) -> input::Bytes {
94+
let input = self.get_slice();
6295
input::Bytes::new(input, &self.options)
6396
}
6497
}

lib/bolero-kani/src/lib.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ pub mod lib {
88
#[allow(unused_imports)]
99
use super::*;
1010

11-
use bolero_engine::{driver, input, kani::Driver as KaniDriver, Engine, TargetLocation, Test};
11+
use bolero_engine::{
12+
driver, input, kani::Driver as KaniDriver, Engine, ScopedEngine, TargetLocation, Test,
13+
};
1214

1315
#[derive(Debug, Default)]
1416
pub struct KaniEngine {}
@@ -45,6 +47,34 @@ pub mod lib {
4547
}
4648
}
4749

50+
impl ScopedEngine for KaniEngine {
51+
type Output = ();
52+
53+
fn run<F, R>(self, test: F, options: driver::Options) -> Self::Output
54+
where
55+
F: FnMut() -> R,
56+
R: bolero_engine::IntoResult,
57+
{
58+
let driver = KaniDriver::new(&options);
59+
let (_driver, result) = bolero_engine::any::run(driver, test);
60+
match result {
61+
Ok(was_valid) => {
62+
// show if the generator was satisfiable
63+
// TODO fail the harness if it's not: https://github.com/model-checking/kani/issues/2792
64+
#[cfg(kani)]
65+
kani::cover!(
66+
was_valid,
67+
"the generator should produce at least one valid value"
68+
);
69+
let _ = was_valid;
70+
}
71+
Err(_) => {
72+
panic!("test failed");
73+
}
74+
}
75+
}
76+
}
77+
4878
struct KaniInput {
4979
options: driver::Options,
5080
}

0 commit comments

Comments
 (0)