Skip to content

Commit

Permalink
Merge pull request #10 from jma-qb/multifix_lain
Browse files Browse the repository at this point in the history
fix #1, #2, #7 and #9. Propose solutions for #4 #5 and #6
  • Loading branch information
domenukk authored Nov 9, 2024
2 parents 208e927 + 8ee3ffb commit 68f6657
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 59 deletions.
4 changes: 3 additions & 1 deletion lain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ num-traits = "0.2"
num-derive = "0.3"
num = "0.4"
lazy_static = "1.2"
serde = { version = "1.0" , optional = true, features = ["derive"] }
serde = { version = "1.0", optional = true, features = ["derive"] }
field-offset = "0.3"

[features]
default_features = []
serde_support = ["serde"]
pick_invalid_enum = []
ignore_min_max = []

[profile.release]
debug = true
188 changes: 136 additions & 52 deletions lain/src/mutatable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ enum VecResizeType {
fn grow_vec<T: NewFuzzed + SerializedSize, R: Rng>(
vec: &mut Vec<T>,
mutator: &mut Mutator<R>,
max_elems: Option<usize>,
mut max_size: Option<usize>,
) {
let resize_count = VecResizeCount::new_fuzzed(mutator, None);
let resize_max = if let Some(max) = max_elems {
max
} else {
9 // old magic value from lain
};
let mut num_elements = if vec.is_empty() {
mutator.gen_range(1, 9)
mutator.gen_range(1, resize_max)
} else {
match resize_count {
VecResizeCount::Quarter => vec.len() / 4,
VecResizeCount::Half => vec.len() / 2,
VecResizeCount::ThreeQuarters => vec.len() - (vec.len() / 4),
VecResizeCount::FixedBytes => mutator.gen_range(1, 9),
VecResizeCount::FixedBytes => mutator.gen_range(1, resize_max),
VecResizeCount::AllBytes => mutator.gen_range(1, vec.len() + 1),
}
};
Expand All @@ -58,6 +64,10 @@ fn grow_vec<T: NewFuzzed + SerializedSize, R: Rng>(
num_elements = min(num_elements, max_size / T::max_default_object_size());
}

if let Some(max_elems) = max_elems {
num_elements = min(max_elems - vec.len(), num_elements);
}

if num_elements == 0 {
return;
}
Expand Down Expand Up @@ -125,28 +135,30 @@ fn grow_vec<T: NewFuzzed + SerializedSize, R: Rng>(
/// Shrinks a `Vec`.
/// This will randomly select to resize by a factor of 1/4, 1/2, 3/4, or a fixed number of bytes
/// in the range of [1, 8]. Elements may be removed randomly from the beginning or end of the the vec
fn shrink_vec<T, R: Rng>(vec: &mut Vec<T>, mutator: &mut Mutator<R>) {
fn shrink_vec<T, R: Rng>(vec: &mut Vec<T>, mutator: &mut Mutator<R>, min_size: Option<usize>) {
if vec.is_empty() {
return;
}

let min_size = if let Some(min) = min_size { min } else { 0 };

let resize_count = VecResizeCount::new_fuzzed(mutator, None);
let mut num_elements = match resize_count {
VecResizeCount::Quarter => vec.len() / 4,
VecResizeCount::Half => vec.len() / 2,
VecResizeCount::ThreeQuarters => vec.len() - (vec.len() / 4),
VecResizeCount::FixedBytes => mutator.gen_range(1, 9),
VecResizeCount::AllBytes => vec.len(),
VecResizeCount::FixedBytes => min(min(mutator.gen_range(1, 9), vec.len()), min_size),
VecResizeCount::AllBytes => min(vec.len(), min_size),
};

if num_elements == 0 {
num_elements = mutator.gen_range(0, vec.len() + 1);
}

num_elements = std::cmp::min(num_elements, vec.len());
num_elements = std::cmp::min(num_elements, vec.len() - min_size);

// Special case probably isn't required here, but better to be explicit
if num_elements == vec.len() {
if num_elements == vec.len() && min_size == 0 {
vec.drain(..);
return;
}
Expand Down Expand Up @@ -177,7 +189,7 @@ where

// 1% chance to resize this vec
if mutator.gen_chance(CHANCE_TO_RESIZE_VEC) {
shrink_vec(self, mutator);
shrink_vec(self, mutator, Some(1));
} else {
// Recreate the constraints so that the min/max types match
let constraints = constraints.and_then(|c| {
Expand Down Expand Up @@ -209,44 +221,82 @@ where
constraints: Option<&Constraints<Self::RangeType>>,
) {
const CHANCE_TO_RESIZE_VEC: f64 = 0.01;
const CHANCE_TO_RESIZE_EMPTY_VEC: f64 = 0.33;

if T::max_default_object_size() == 0 {
return;
}

// we can grow the vector if we have no size constraint or the max size quota hasn't
// been fulfilled
let can_grow = constraints
.map(|c| {
c.max_size
.map(|s| s > 0 && s > T::max_default_object_size())
.unwrap_or(true)
})
.unwrap_or(false);
let mut can_grow = true;
if let Some(max_elems) = constraints.and_then(|c| c.max) {
if self.len() >= max_elems {
can_grow = false;
}
}

if mutator.gen_chance(CHANCE_TO_RESIZE_VEC) {
let resize_type = VecResizeType::new_fuzzed(mutator, None);
if resize_type == VecResizeType::Grow && can_grow {
grow_vec(self, mutator, constraints.and_then(|c| c.max_size));
if let Some(max_size) = constraints.and_then(|c| c.max_size) {
if self.len() >= max_size / T::max_default_object_size() {
can_grow = false;
}
}

if self.is_empty() {
if mutator.gen_chance(CHANCE_TO_RESIZE_EMPTY_VEC) {
grow_vec(
self,
mutator,
constraints.and_then(|c| c.max),
constraints.and_then(|c| c.max_size),
);
} else {
shrink_vec(self, mutator);
// Recreate the constraints so that the min/max types match
let constraints = constraints.and_then(|c| {
if c.max_size.is_none() {
None
} else {
let mut new_constraints = Constraints::new();
new_constraints.base_object_size_accounted_for =
c.base_object_size_accounted_for;
new_constraints.max_size = c.max_size;

Some(new_constraints)
}
});

self.as_mut_slice().mutate(mutator, constraints.as_ref());
}
} else {
// Recreate the constraints so that the min/max types match
let constraints = constraints.and_then(|c| {
if c.max_size.is_none() {
None
if mutator.gen_chance(CHANCE_TO_RESIZE_VEC) {
let resize_type = VecResizeType::new_fuzzed(mutator, None);
if resize_type == VecResizeType::Grow && can_grow {
grow_vec(
self,
mutator,
constraints.and_then(|c| c.max),
constraints.and_then(|c| c.max_size),
);
} else {
let mut new_constraints = Constraints::new();
new_constraints.base_object_size_accounted_for =
c.base_object_size_accounted_for;
new_constraints.max_size = c.max_size;

Some(new_constraints)
shrink_vec(self, mutator, constraints.and_then(|c| c.min));
}
});
} else {
// Recreate the constraints so that the min/max types match
let constraints = constraints.and_then(|c| {
if c.max_size.is_none() {
None
} else {
let mut new_constraints = Constraints::new();
new_constraints.base_object_size_accounted_for =
c.base_object_size_accounted_for;
new_constraints.max_size = c.max_size;

self.as_mut_slice().mutate(mutator, constraints.as_ref());
Some(new_constraints)
}
});

self.as_mut_slice().mutate(mutator, constraints.as_ref());
}
}
}
}
Expand All @@ -264,6 +314,7 @@ where
constraints: Option<&Constraints<Self::RangeType>>,
) {
const CHANCE_TO_RESIZE_VEC: f64 = 0.01;
const CHANCE_TO_RESIZE_EMPTY_VEC: f64 = 0.33;

if T::max_default_object_size() == 0 {
return;
Expand All @@ -275,29 +326,61 @@ where
.map(|c| c.max_size.map(|s| s > 0).unwrap_or(true))
.unwrap_or(false);

if mutator.gen_chance(CHANCE_TO_RESIZE_VEC) {
let resize_type = VecResizeType::new_fuzzed(mutator, None);
if resize_type == VecResizeType::Grow && can_grow {
grow_vec(self, mutator, constraints.and_then(|c| c.max_size));
if self.is_empty() {
if mutator.gen_chance(CHANCE_TO_RESIZE_EMPTY_VEC) {
grow_vec(
self,
mutator,
constraints.and_then(|c| c.max),
constraints.and_then(|c| c.max_size),
);
} else {
shrink_vec(self, mutator);
// Recreate the constraints so that the min/max types match
let constraints = constraints.and_then(|c| {
if c.max_size.is_none() {
None
} else {
let mut new_constraints = Constraints::new();
new_constraints.base_object_size_accounted_for =
c.base_object_size_accounted_for;
new_constraints.max_size = c.max_size;

Some(new_constraints)
}
});

self.as_mut_slice().mutate(mutator, constraints.as_ref());
}
} else {
// Recreate the constraints so that the min/max types match
let constraints = constraints.and_then(|c| {
if c.max_size.is_none() {
None
if mutator.gen_chance(CHANCE_TO_RESIZE_VEC) {
let resize_type = VecResizeType::new_fuzzed(mutator, None);
if resize_type == VecResizeType::Grow && can_grow {
grow_vec(
self,
mutator,
constraints.and_then(|c| c.max),
constraints.and_then(|c| c.max_size),
);
} else {
let mut new_constraints = Constraints::new();
new_constraints.base_object_size_accounted_for =
c.base_object_size_accounted_for;
new_constraints.max_size = c.max_size;

Some(new_constraints)
shrink_vec(self, mutator, constraints.and_then(|c| c.min));
}
});
} else {
// Recreate the constraints so that the min/max types match
let constraints = constraints.and_then(|c| {
if c.max_size.is_none() {
None
} else {
let mut new_constraints = Constraints::new();
new_constraints.base_object_size_accounted_for =
c.base_object_size_accounted_for;
new_constraints.max_size = c.max_size;

self.as_mut_slice().mutate(mutator, constraints.as_ref());
Some(new_constraints)
}
});

self.as_mut_slice().mutate(mutator, constraints.as_ref());
}
}
}
}
Expand Down Expand Up @@ -618,18 +701,19 @@ where
mutator: &mut Mutator<R>,
constraints: Option<&Constraints<Self::RangeType>>,
) {
const CHANCE_TO_FLIP_OPTION_STATE: f64 = 0.01;
const CHANCE_TO_FLIP_SOME_STATE: f64 = 0.05;
const CHANCE_TO_FLIP_NONE_STATE: f64 = 0.10;
match self {
Some(inner) => {
// small chance to make this None
if mutator.gen_chance(CHANCE_TO_FLIP_OPTION_STATE) {
if mutator.gen_chance(CHANCE_TO_FLIP_SOME_STATE) {
*self = None;
} else {
inner.mutate(mutator, constraints);
}
}
None => {
if mutator.gen_chance(CHANCE_TO_FLIP_OPTION_STATE) {
if mutator.gen_chance(CHANCE_TO_FLIP_NONE_STATE) {
let new_item = T::new_fuzzed(mutator, constraints);

*self = Some(new_item);
Expand Down
12 changes: 9 additions & 3 deletions lain/src/mutator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ use serde::{Deserialize, Serialize};

// set these to 0 to disable
pub const CHANCE_TO_REPEAT_ARRAY_VALUE: f64 = 0.05;
pub const CHANCE_TO_PICK_INVALID_ENUM: f64 = 0.10;
pub const CHANCE_TO_IGNORE_MIN_MAX: f64 = 0.05;
#[cfg(feature = "pick_invalid_enum")]
pub const CHANCE_TO_PICK_INVALID_ENUM: f64 = 0.01;
#[cfg(not(feature = "pick_invalid_enum"))]
pub const CHANCE_TO_PICK_INVALID_ENUM: f64 = 0.0;
#[cfg(feature = "ignore_min_max")]
pub const CHANCE_TO_IGNORE_MIN_MAX: f64 = 0.01;
#[cfg(not(feature = "ignore_min_max"))]
pub const CHANCE_TO_IGNORE_MIN_MAX: f64 = 0.0;

#[repr(u8)]
#[derive(Debug, Copy, Clone, NewFuzzed)]
Expand Down Expand Up @@ -107,7 +113,7 @@ impl<R: Rng> Mutator<R> {
self.corpus_state.fields_fuzzed += 1;
}

if self.gen_chance(0.10) {
if self.gen_chance(0.01) {
*num = T::select_dangerous_number(&mut self.rng);
return;
}
Expand Down
9 changes: 6 additions & 3 deletions lain_derive/src/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,16 @@ fn mutatable_enum_visitor(variants: &[Variant], cont_ident: &syn::Ident) -> Vec<
fn mutatable_struct_visitor(fields: &[Field]) -> Vec<TokenStream> {
fields
.iter()
.map(|field| {
.filter_map(|field| {
if field.attrs.ignore() {
return None;
}
let (_field_ident, _field_ident_string, initializer) =
field_mutator(field, "self.", false);

quote! {
Some(quote! {
#initializer
}
})
})
.collect()
}
Expand Down

0 comments on commit 68f6657

Please sign in to comment.