Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/fuzzing/src/generators/gc_ops/limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub const NUM_GLOBALS_RANGE: RangeInclusive<u32> = 0..=10;
pub const TABLE_SIZE_RANGE: RangeInclusive<u32> = 0..=100;
/// Range for the maximum number of rec groups.
pub const MAX_REC_GROUPS_RANGE: RangeInclusive<u32> = 0..=10;
/// Range for the maximum number of fields per struct type.
pub const MAX_FIELDS_RANGE: RangeInclusive<u32> = 0..=8;
/// Maximum number of operations.
pub const MAX_OPS: usize = 100;

Expand All @@ -24,7 +26,9 @@ pub struct GcOpsLimits {
pub(crate) table_size: u32,
pub(crate) max_rec_groups: u32,
pub(crate) max_types: u32,
pub(crate) max_fields: u32,
}

impl GcOpsLimits {
/// Fixup the limits to ensure they are within the valid range.
pub(crate) fn fixup(&mut self) {
Expand All @@ -36,6 +40,7 @@ impl GcOpsLimits {
table_size,
max_rec_groups,
max_types,
max_fields,
} = self;

let clamp = |limit: &mut u32, range: RangeInclusive<u32>| {
Expand All @@ -46,5 +51,6 @@ impl GcOpsLimits {
clamp(num_globals, NUM_GLOBALS_RANGE);
clamp(max_rec_groups, MAX_REC_GROUPS_RANGE);
clamp(max_types, MAX_TYPES_RANGE);
clamp(max_fields, MAX_FIELDS_RANGE);
}
}
90 changes: 73 additions & 17 deletions crates/fuzzing/src/generators/gc_ops/mutator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Mutators for the `gc` operations.
use crate::generators::gc_ops::limits::GcOpsLimits;
use crate::generators::gc_ops::ops::{GcOp, GcOps};
use crate::generators::gc_ops::types::{TypeId, Types};
use crate::generators::gc_ops::types::{CompositeType, FieldType, StructField, TypeId, Types};
use mutatis::{
Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult, mutators as m,
};
Expand Down Expand Up @@ -54,8 +54,9 @@ impl TypesMutator {
} else {
None
};
types.insert_empty_struct(tid, gid, is_final, supertype);
log::debug!("Added empty struct {tid:?} to rec group {gid:?}");
// Add struct with no fields; fields can be added later by `mutate_struct_fields`.
types.insert_struct(tid, gid, is_final, supertype, Vec::new());
log::debug!("Added struct {tid:?} to rec group {gid:?}");
Ok(())
})?;
Ok(())
Expand Down Expand Up @@ -218,16 +219,17 @@ impl TypesMutator {
return Ok(());
}

// Collect (TypeId, is_final, supertype) for members of the source group.
let members: SmallVec<[(TypeId, bool, Option<TypeId>); 32]> = src_members
.iter()
.filter_map(|tid| {
types
.type_defs
.get(tid)
.map(|def| (*tid, def.is_final, def.supertype))
})
.collect();
// Collect (TypeId, is_final, supertype, fields) for members of the source group.
let members: SmallVec<[(TypeId, bool, Option<TypeId>, Vec<StructField>); 32]> =
src_members
.iter()
.filter_map(|tid| {
types.type_defs.get(tid).map(|def| {
let CompositeType::Struct(ref st) = def.composite_type;
(*tid, def.is_final, def.supertype, st.fields.clone())
})
})
.collect();

if members.is_empty() {
return Ok(());
Expand All @@ -239,15 +241,15 @@ impl TypesMutator {

// Allocate fresh type ids for each member and build old-to-new map.
let mut old_to_new: BTreeMap<TypeId, TypeId> = BTreeMap::new();
for (old_tid, _, _) in &members {
for (old_tid, _, _, _) in &members {
old_to_new.insert(*old_tid, types.fresh_type_id(ctx.rng()));
}

// Insert duplicated defs, rewriting intra-group supertype edges to cloned ids.
for (old_tid, is_final, supertype) in &members {
for (old_tid, is_final, supertype, fields) in &members {
let new_tid = old_to_new[old_tid];
let mapped_super = supertype.map(|st| *old_to_new.get(&st).unwrap_or(&st));
types.insert_empty_struct(new_tid, new_gid, *is_final, mapped_super);
types.insert_struct(new_tid, new_gid, *is_final, mapped_super, fields.clone());
}

log::debug!(
Expand Down Expand Up @@ -390,6 +392,19 @@ impl TypesMutator {
Ok(())
}

/// Mutate struct fields (add/remove/modify via `m::vec`).
fn mutate_struct_fields(
&mut self,
c: &mut Candidates<'_>,
types: &mut Types,
) -> mutatis::Result<()> {
for (_, def) in types.type_defs.iter_mut() {
let CompositeType::Struct(ref mut st) = def.composite_type;
m::vec(StructFieldMutator).mutate(c, &mut st.fields)?;
}
Ok(())
}

/// Run all type / rec-group mutations. [`GcOpsLimits`] come from [`GcOps`].
fn mutate_with_limits(
&mut self,
Expand All @@ -405,10 +420,51 @@ impl TypesMutator {
self.remove_group(c, types)?;
self.merge_groups(c, types)?;
self.split_group(c, types, limits)?;
self.mutate_struct_fields(c, types)?;

Ok(())
}
}

/// Mutator for [`StructField`]: used by `m::vec` to add, remove, and
/// modify fields within a struct type.
#[derive(Debug, Default)]
pub struct StructFieldMutator;

impl Mutate<StructField> for StructFieldMutator {
fn mutate(&mut self, c: &mut Candidates<'_>, field: &mut StructField) -> MutResult<()> {
c.mutation(|ctx| {
let old = format!("{field:?}");
let idx = usize::try_from(ctx.rng().gen_u32())
.expect("failed to convert rng output to usize")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just .unwrap(): usize::try_from(my_u32).unwrap() is common enough that it doesn't need a diagnostic message and it also shouldn't ever fail on our supported architectures anyway. Same with the other instances of this here.

% FieldType::ALL.len();
field.field_type = FieldType::ALL[idx];
field.mutable = (ctx.rng().gen_u32() % 2) == 0;
log::debug!("Mutated field {old} -> {field:?}");
Ok(())
})?;
Ok(())
}
}

impl Generate<StructField> for StructFieldMutator {
fn generate(&mut self, ctx: &mut Context) -> MutResult<StructField> {
let idx = usize::try_from(ctx.rng().gen_u32())
.expect("failed to convert rng output to usize")
% FieldType::ALL.len();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this pattern is probably a little more straight forward as:

let idx = ctx.rng().gen_index(FieldType::ALL.len());
let idx = u32::try_from(idx).unwrap();

And maybe even better deduplicated as a fn FieldType::random(rng: &mut mutatis::Rng) -> u32 helper function.

let field = StructField {
field_type: FieldType::ALL[idx],
mutable: (ctx.rng().gen_u32() % 2) == 0,
};
log::debug!("Generated field {field:?}");
Ok(field)
}
}

impl DefaultMutate for StructField {
type DefaultMutate = StructFieldMutator;
}

/// Mutator for [`GcOps`].
///
/// Also implements [`Mutate`] / [`Generate`] for [`GcOp`] so `m::vec` can mutate
Expand Down Expand Up @@ -465,7 +521,7 @@ impl Generate<GcOps> for GcOpsMutator {
let mut ops = GcOps::default();
let mut session = mutatis::Session::new();

for _ in 0..64 {
for _ in 0..2048 {
session.mutate(&mut ops)?;
}

Expand Down
Loading
Loading