Skip to content

Commit bd5d499

Browse files
committed
Make it harder to build a vector mutator without inherent complexity
1 parent 75b8d15 commit bd5d499

File tree

9 files changed

+111
-44
lines changed

9 files changed

+111
-44
lines changed

fuzzcheck/src/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ where
337337
}
338338
fn fuzz_test() {
339339
fuzzcheck::fuzz_test(foo)
340-
.mutator(VecMutator::new(u8::default_mutator(), 2 ..= 10, true))
340+
.mutator(VecMutator::new(u8::default_mutator(), 2 ..= 10))
341341
// ..
342342
# ;
343343
}

fuzzcheck/src/mutators/fixed_len_vector.rs

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ where
2222
max_complexity: Cell<f64>,
2323
search_space_complexity: Cell<f64>,
2424
has_inherent_complexity: bool,
25-
inherent_complexity: f64,
25+
inherent_complexity: Cell<f64>,
2626
_phantom: PhantomData<T>,
2727
}
2828
impl<T, M> FixedLenVecMutator<T, M>
@@ -32,7 +32,7 @@ where
3232
{
3333
#[no_coverage]
3434
pub fn new_with_repeated_mutator(mutator: M, len: usize) -> Self {
35-
Self::new(vec![mutator; len], true)
35+
Self::new(vec![mutator; len])
3636
}
3737
}
3838

@@ -41,8 +41,34 @@ where
4141
T: Clone + 'static,
4242
M: Mutator<T>,
4343
{
44+
/// Note: only use this function if you really know what you are doing!
45+
///
46+
/// Create a `FixedLenVecMutator` using the given submutators.
47+
/// The complexity of the generated vectors will be only the sum of the
48+
/// complexities of their elements.
49+
///
50+
/// This is not the default behaviour.
51+
/// Normally, a vector such as `[1u8, 2u8]` would have a complexity of `17`:
52+
/// `2 * 8` for the first two integers, and `+ 1` for the inherent
53+
/// complexity of the vector itself. If the vector contains elements with a
54+
/// minimum complexity of 0.0, then its length would also influence its
55+
/// complexity. For example, `[(), ()]` would have a complexity of `3.0`:
56+
/// `1` for the vector and `+ 2` for the length.
57+
///
58+
/// By using this function to create the `FixedLenVecMutator`, we get rid
59+
/// of the "inherent" part of the vector complexity. For example, the vector
60+
/// `[1u8, 2u8]` will have a complexity of 16.0 and the vector `[[], []]`
61+
/// will have a complexity of 0.0.
62+
///
63+
/// Note that *all mutators in a fuzz test must agree on the complexity of
64+
/// a value*. For example, if you are mutating a 2-tuple of vectors:
65+
/// `(Vec<u8>, Vec<u8>)` using two `FixedLenVecMutator`, then both must
66+
/// agree on whether to include the inherent complexity of the vectors or
67+
/// not. That is, given the value `([1, 2], [1, 2])`, it is an error to
68+
/// evaluate the complexity of the first vector as `16.0` and the complexity
69+
/// of the second vector as `17.0`.
4470
#[no_coverage]
45-
pub fn new(mutators: Vec<M>, inherent_complexity: bool) -> Self {
71+
pub fn new_without_inherent_complexity(mutators: Vec<M>) -> Self {
4672
assert!(!mutators.is_empty());
4773

4874
Self {
@@ -52,8 +78,25 @@ where
5278
min_complexity: Cell::new(std::f64::INFINITY),
5379
max_complexity: Cell::default(),
5480
search_space_complexity: Cell::default(),
55-
has_inherent_complexity: inherent_complexity,
56-
inherent_complexity: 0.,
81+
has_inherent_complexity: false,
82+
inherent_complexity: Cell::default(),
83+
_phantom: PhantomData,
84+
}
85+
}
86+
87+
#[no_coverage]
88+
pub fn new(mutators: Vec<M>) -> Self {
89+
assert!(!mutators.is_empty());
90+
91+
Self {
92+
rng: Rng::default(),
93+
mutators,
94+
initialized: Cell::new(false),
95+
min_complexity: Cell::new(std::f64::INFINITY),
96+
max_complexity: Cell::default(),
97+
search_space_complexity: Cell::default(),
98+
has_inherent_complexity: true,
99+
inherent_complexity: Cell::default(),
57100
_phantom: PhantomData,
58101
}
59102
}
@@ -213,6 +256,7 @@ impl<T: Clone + 'static, M: Mutator<T>> Mutator<Vec<T>> for FixedLenVecMutator<T
213256
|cplx, m| cplx + m.global_search_space_complexity(),
214257
);
215258
self.initialized.set(true);
259+
self.inherent_complexity.set(inherent_complexity);
216260
self.min_complexity.set(min_complexity);
217261
self.max_complexity.set(max_complexity);
218262
self.search_space_complexity.set(search_space_complexity);
@@ -304,7 +348,7 @@ impl<T: Clone + 'static, M: Mutator<T>> Mutator<Vec<T>> for FixedLenVecMutator<T
304348
#[doc(hidden)]
305349
#[no_coverage]
306350
fn complexity(&self, _value: &Vec<T>, cache: &Self::Cache) -> f64 {
307-
cache.sum_cplx + self.inherent_complexity
351+
cache.sum_cplx + self.inherent_complexity.get()
308352
}
309353

310354
#[doc(hidden)]
@@ -321,7 +365,7 @@ impl<T: Clone + 'static, M: Mutator<T>> Mutator<Vec<T>> for FixedLenVecMutator<T
321365
fn random_arbitrary(&self, max_cplx: f64) -> (Vec<T>, f64) {
322366
let target_cplx = crate::mutators::gen_f64(&self.rng, 1.0..max_cplx);
323367
let (v, sum_cplx) = self.new_input_with_complexity(target_cplx);
324-
(v, sum_cplx + self.inherent_complexity)
368+
(v, sum_cplx + self.inherent_complexity.get())
325369
}
326370

327371
#[doc(hidden)]
@@ -347,11 +391,11 @@ impl<T: Clone + 'static, M: Mutator<T>> Mutator<Vec<T>> for FixedLenVecMutator<T
347391
let step = &mut step.crossover_steps[choice];
348392
let old_el_cplx = self.mutators[choice].complexity(&value[choice], &cache.inner[choice]);
349393
let current_cplx = self.complexity(value, cache);
350-
let max_el_cplx = current_cplx - old_el_cplx - self.inherent_complexity;
394+
let max_el_cplx = current_cplx - old_el_cplx - self.inherent_complexity.get();
351395
if let Some((el, new_el_cplx)) = step.get_next_subvalue(subvalue_provider, max_el_cplx) && self.mutators[choice].is_valid(el) {
352396
let mut el = el.clone();
353397
std::mem::swap(&mut value[choice], &mut el);
354-
let cplx = cache.sum_cplx - old_el_cplx + new_el_cplx + self.inherent_complexity;
398+
let cplx = cache.sum_cplx - old_el_cplx + new_el_cplx + self.inherent_complexity.get();
355399
let token = UnmutateVecToken::ReplaceElement(choice, el);
356400
return Some((token, cplx));
357401
}
@@ -364,7 +408,7 @@ impl<T: Clone + 'static, M: Mutator<T>> Mutator<Vec<T>> for FixedLenVecMutator<T
364408
let idcs = &idcs[..count];
365409
Some(self.mutate_elements(value, cache, idcs, current_cplx, max_cplx))
366410
} else {
367-
let spare_cplx = max_cplx - current_cplx - self.inherent_complexity;
411+
let spare_cplx = max_cplx - current_cplx - self.inherent_complexity.get();
368412
let idx = step.element_step % value.len();
369413
step.element_step += 1;
370414
self.mutate_element(value, cache, step, subvalue_provider, idx, current_cplx, spare_cplx)

fuzzcheck/src/mutators/grammar/mutators.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ use crate::mutators::tuples::Tuple1Mutator;
1818
use crate::mutators::vector::VecMutator;
1919
use crate::Mutator;
2020

21+
// NOTE: the complexity of the vectors in the AST is the complexity of their
22+
// elements and nothing else. That is, we don't take their inherent complexity
23+
// into account. This is because we only really care about the complexity of the
24+
// string representation of the AST. Adding complexity for each sequence would
25+
// only add noise. It would not translate to actual added complexity from the
26+
// user's perspective.
27+
// For example:
28+
//
29+
// The grammar:
30+
// [a-z]?[a-z]?[a-z]?
31+
// producing the value:
32+
// ae
33+
// vs.
34+
// The grammar:
35+
// [a-z]{,3}
36+
// producing the same value:
37+
// ae
38+
//
39+
// The AST of the first value contains four vectors wherease the AST of the
40+
// second value contains only 1. But the two values are equally complex from
41+
// the user's point of view.
2142
make_single_variant_mutator! {
2243
pub enum AST {
2344
Token(char),
@@ -244,7 +265,9 @@ impl ASTMutator {
244265
fn recur(m: RecurToMutator<ASTMutator>) -> Self {
245266
Self {
246267
inner: Box::new(Either::Right(Either::Left(ASTSingleVariant::Sequence(
247-
Tuple1Mutator::new(Either::Left(FixedLenVecMutator::new(vec![m], false))),
268+
Tuple1Mutator::new(Either::Left(FixedLenVecMutator::new_without_inherent_complexity(vec![
269+
m,
270+
]))),
248271
)))),
249272
}
250273
}
@@ -283,13 +306,14 @@ impl ASTMutator {
283306
let m = Self::from_grammar_rec(g.clone(), others);
284307
ms.push(m);
285308
}
286-
Self::sequence(Either::Left(FixedLenVecMutator::new(ms, false)))
309+
Self::sequence(Either::Left(FixedLenVecMutator::new_without_inherent_complexity(ms)))
310+
}
311+
Grammar::Repetition(g, range) => {
312+
Self::sequence(Either::Right(VecMutator::new_without_inherent_complexity(
313+
Self::from_grammar_rec(g.clone(), others),
314+
range.start..=range.end - 1,
315+
)))
287316
}
288-
Grammar::Repetition(g, range) => Self::sequence(Either::Right(VecMutator::new(
289-
Self::from_grammar_rec(g.clone(), others),
290-
range.start..=range.end - 1,
291-
false,
292-
))),
293317
Grammar::Recurse(g) => {
294318
if let Some(m) = others.get(&g.as_ptr()) {
295319
Self::recur(RecurToMutator::from(m))

fuzzcheck/src/mutators/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ pub(crate) fn gen_f64(rng: &fastrand::Rng, range: Range<f64>) -> f64 {
318318
#[must_use]
319319
#[no_coverage]
320320
fn size_to_cplxity(size: usize) -> f64 {
321-
(usize::BITS - (size.saturating_sub(1)).leading_zeros()) as f64
321+
(usize::BITS - size.leading_zeros()) as f64
322322
}
323323

324324
#[cfg(test)]
@@ -330,12 +330,12 @@ mod test {
330330
#[no_coverage]
331331
fn test_size_to_cplxity() {
332332
assert_eq!(0.0, size_to_cplxity(0));
333-
assert_eq!(0.0, size_to_cplxity(1));
334-
assert_eq!(1.0, size_to_cplxity(2));
333+
assert_eq!(1.0, size_to_cplxity(1));
334+
assert_eq!(2.0, size_to_cplxity(2));
335335
assert_eq!(2.0, size_to_cplxity(3));
336-
assert_eq!(2.0, size_to_cplxity(4));
336+
assert_eq!(3.0, size_to_cplxity(4));
337337
assert_eq!(3.0, size_to_cplxity(5));
338-
assert_eq!(3.0, size_to_cplxity(8));
338+
assert_eq!(4.0, size_to_cplxity(8));
339339
assert_eq!(5.0, size_to_cplxity(31));
340340
}
341341
}

fuzzcheck/src/mutators/vector/mod.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ where
2828
type Mutator = VecMutator<T, T::Mutator>;
2929
#[no_coverage]
3030
fn default_mutator() -> Self::Mutator {
31-
VecMutator::new(T::default_mutator(), 0..=usize::MAX, true)
31+
VecMutator::new(T::default_mutator(), 0..=usize::MAX)
3232
}
3333
}
3434

@@ -81,13 +81,25 @@ where
8181
M: Mutator<T>,
8282
{
8383
#[no_coverage]
84-
pub fn new(m: M, len_range: RangeInclusive<usize>, inherent_complexity: bool) -> Self {
84+
pub fn new_without_inherent_complexity(m: M, len_range: RangeInclusive<usize>) -> Self {
8585
Self {
8686
m,
8787
len_range,
8888
rng: fastrand::Rng::new(),
8989
mutations: VectorMutation::default(),
90-
inherent_complexity,
90+
inherent_complexity: false,
91+
_phantom: PhantomData,
92+
}
93+
}
94+
95+
#[no_coverage]
96+
pub fn new(m: M, len_range: RangeInclusive<usize>) -> Self {
97+
Self {
98+
m,
99+
len_range,
100+
rng: fastrand::Rng::new(),
101+
mutations: VectorMutation::default(),
102+
inherent_complexity: true,
91103
_phantom: PhantomData,
92104
}
93105
}

fuzzcheck/src/mutators/vector/only_choose_length.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! use fuzzcheck::{Mutator, DefaultMutator};
88
//! use fuzzcheck::mutators::vector::VecMutator;
99
//!
10-
//! let m /* : impl Mutator<Vec<()>> */ = VecMutator::new(<()>::default_mutator(), 2..=5, true);
10+
//! let m /* : impl Mutator<Vec<()>> */ = VecMutator::new(<()>::default_mutator(), 2..=5);
1111
//! ```
1212
//! Then the values that `m` can produce are only:
1313
//! ```txt

fuzzcheck/tests/derived_mutually_recursive_structs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ make_mutator! {
4545
OptionMutator<MutuallyRecursiveA, AMutator<VecMutator<MutuallyRecursiveB, RecurToMutator<BMutator>>>>
4646
= {
4747
OptionMutator::new(AMutator::new(
48-
VecMutator::new(self_.into(), 0..=usize::MAX, true),
48+
VecMutator::new(self_.into(), 0..=usize::MAX),
4949
<Vec<u64>>::default_mutator(),
5050
))
5151
})]

fuzzcheck/tests/derived_recursive_struct.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ make_mutator! {
5656
self_.into()
5757
)
5858
),
59-
0..=usize::MAX,
60-
true
59+
0..=usize::MAX
6160
)
6261
}
6362
)]
@@ -79,7 +78,7 @@ make_mutator! {
7978
struct SampleStruct2 {
8079
#[field_mutator(
8180
VecMutator<Box<SampleStruct2>, BoxMutator<RecurToMutator<SampleStruct2Mutator>>> = {
82-
VecMutator::new(BoxMutator::new(self_.into()), 0..=10, true)
81+
VecMutator::new(BoxMutator::new(self_.into()), 0..=10)
8382
}
8483
)]
8584
w: Vec<Box<SampleStruct2>>,

fuzzcheck/tests/vector.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,7 @@ use fuzzcheck::mutators::integer::U8Mutator;
22
use fuzzcheck::mutators::vector::VecMutator;
33
#[test]
44
fn test_vector_mutator() {
5-
// let m = VecMutator::new(U8Mutator::default(), 0..=10);
6-
// fuzzcheck_mutators::testing_utilities::test_mutator(m, 100.0, 100.0, false, 500, 500);
7-
// let m = VecMutator::new(U8Mutator::default(), 0..=10);
8-
// fuzzcheck_mutators::testing_utilities::test_mutator(m, 20000.0, 20000.0, false, 500, 500);
9-
// let m = VecMutator::new(U8Mutator::default(), 10..=20);
10-
// fuzzcheck_mutators::testing_utilities::test_mutator(m, 10000.0, 10000.0, false, 500, 500);
11-
// // todo: test with an unlimited range
12-
13-
let m = VecMutator::new(
14-
VecMutator::new(U8Mutator::default(), 0..=usize::MAX, false),
15-
0..=usize::MAX,
16-
false,
17-
);
5+
let m = VecMutator::new(VecMutator::new(U8Mutator::default(), 0..=usize::MAX), 0..=usize::MAX);
186
fuzzcheck::mutators::testing_utilities::test_mutator(m, 500.0, 500.0, false, true, 100, 150);
197
}
208

0 commit comments

Comments
 (0)