Skip to content

Commit 8bdb373

Browse files
authored
gc_fuzz: Struct fields (#13101)
* Add struct fields: POD and externref * Update .expect() for try_from for all Update Field mutation randomness Update boxing in emitting binary * Remove special case for 'externref'
1 parent e3e5aa6 commit 8bdb373

5 files changed

Lines changed: 478 additions & 124 deletions

File tree

crates/fuzzing/src/generators/gc_ops/limits.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub const NUM_GLOBALS_RANGE: RangeInclusive<u32> = 0..=10;
1313
pub const TABLE_SIZE_RANGE: RangeInclusive<u32> = 0..=100;
1414
/// Range for the maximum number of rec groups.
1515
pub const MAX_REC_GROUPS_RANGE: RangeInclusive<u32> = 0..=10;
16+
/// Range for the maximum number of fields per struct type.
17+
pub const MAX_FIELDS_RANGE: RangeInclusive<u32> = 0..=8;
1618
/// Maximum number of operations.
1719
pub const MAX_OPS: usize = 100;
1820

@@ -24,7 +26,9 @@ pub struct GcOpsLimits {
2426
pub(crate) table_size: u32,
2527
pub(crate) max_rec_groups: u32,
2628
pub(crate) max_types: u32,
29+
pub(crate) max_fields: u32,
2730
}
31+
2832
impl GcOpsLimits {
2933
/// Fixup the limits to ensure they are within the valid range.
3034
pub(crate) fn fixup(&mut self) {
@@ -36,6 +40,7 @@ impl GcOpsLimits {
3640
table_size,
3741
max_rec_groups,
3842
max_types,
43+
max_fields,
3944
} = self;
4045

4146
let clamp = |limit: &mut u32, range: RangeInclusive<u32>| {
@@ -46,5 +51,6 @@ impl GcOpsLimits {
4651
clamp(num_globals, NUM_GLOBALS_RANGE);
4752
clamp(max_rec_groups, MAX_REC_GROUPS_RANGE);
4853
clamp(max_types, MAX_TYPES_RANGE);
54+
clamp(max_fields, MAX_FIELDS_RANGE);
4955
}
5056
}

crates/fuzzing/src/generators/gc_ops/mutator.rs

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Mutators for the `gc` operations.
22
use crate::generators::gc_ops::limits::GcOpsLimits;
33
use crate::generators::gc_ops::ops::{GcOp, GcOps};
4-
use crate::generators::gc_ops::types::{TypeId, Types};
4+
use crate::generators::gc_ops::types::{CompositeType, FieldType, StructField, TypeId, Types};
55
use mutatis::{
66
Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult, mutators as m,
77
};
@@ -22,15 +22,11 @@ impl TypesMutator {
2222
types: &mut Types,
2323
limits: &GcOpsLimits,
2424
) -> mutatis::Result<()> {
25-
if c.shrink()
26-
|| types.type_defs.len()
27-
>= usize::try_from(limits.max_types).expect("max_types is too large")
28-
{
25+
if c.shrink() || types.type_defs.len() >= usize::try_from(limits.max_types).unwrap() {
2926
return Ok(());
3027
}
3128

32-
let max_rec_groups =
33-
usize::try_from(limits.max_rec_groups).expect("max_rec_groups is too large");
29+
let max_rec_groups = usize::try_from(limits.max_rec_groups).unwrap();
3430
if types.rec_groups.is_empty() && max_rec_groups == 0 {
3531
return Ok(());
3632
}
@@ -54,8 +50,9 @@ impl TypesMutator {
5450
} else {
5551
None
5652
};
57-
types.insert_empty_struct(tid, gid, is_final, supertype);
58-
log::debug!("Added empty struct {tid:?} to rec group {gid:?}");
53+
// Add struct with no fields; fields can be added later by `mutate_struct_fields`.
54+
types.insert_struct(tid, gid, is_final, supertype, Vec::new());
55+
log::debug!("Added struct {tid:?} to rec group {gid:?}");
5956
Ok(())
6057
})?;
6158
Ok(())
@@ -200,10 +197,8 @@ impl TypesMutator {
200197
) -> mutatis::Result<()> {
201198
if c.shrink()
202199
|| types.rec_groups.is_empty()
203-
|| types.rec_groups.len()
204-
>= usize::try_from(limits.max_rec_groups).expect("max_rec_groups is too large")
205-
|| types.type_defs.len()
206-
>= usize::try_from(limits.max_types).expect("max_types is too large")
200+
|| types.rec_groups.len() >= usize::try_from(limits.max_rec_groups).unwrap()
201+
|| types.type_defs.len() >= usize::try_from(limits.max_types).unwrap()
207202
{
208203
return Ok(());
209204
}
@@ -218,16 +213,17 @@ impl TypesMutator {
218213
return Ok(());
219214
}
220215

221-
// Collect (TypeId, is_final, supertype) for members of the source group.
222-
let members: SmallVec<[(TypeId, bool, Option<TypeId>); 32]> = src_members
223-
.iter()
224-
.filter_map(|tid| {
225-
types
226-
.type_defs
227-
.get(tid)
228-
.map(|def| (*tid, def.is_final, def.supertype))
229-
})
230-
.collect();
216+
// Collect (TypeId, is_final, supertype, fields) for members of the source group.
217+
let members: SmallVec<[(TypeId, bool, Option<TypeId>, Vec<StructField>); 32]> =
218+
src_members
219+
.iter()
220+
.filter_map(|tid| {
221+
types.type_defs.get(tid).map(|def| {
222+
let CompositeType::Struct(ref st) = def.composite_type;
223+
(*tid, def.is_final, def.supertype, st.fields.clone())
224+
})
225+
})
226+
.collect();
231227

232228
if members.is_empty() {
233229
return Ok(());
@@ -239,15 +235,15 @@ impl TypesMutator {
239235

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

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

253249
log::debug!(
@@ -332,8 +328,7 @@ impl TypesMutator {
332328
if c.shrink()
333329
|| types.rec_groups.is_empty()
334330
|| types.type_defs.len() < 2
335-
|| types.rec_groups.len()
336-
>= usize::try_from(limits.max_rec_groups).expect("max_rec_groups is too large")
331+
|| types.rec_groups.len() >= usize::try_from(limits.max_rec_groups).unwrap()
337332
{
338333
return Ok(());
339334
}
@@ -390,6 +385,19 @@ impl TypesMutator {
390385
Ok(())
391386
}
392387

388+
/// Mutate struct fields (add/remove/modify via `m::vec`).
389+
fn mutate_struct_fields(
390+
&mut self,
391+
c: &mut Candidates<'_>,
392+
types: &mut Types,
393+
) -> mutatis::Result<()> {
394+
for (_, def) in types.type_defs.iter_mut() {
395+
let CompositeType::Struct(ref mut st) = def.composite_type;
396+
m::vec(StructFieldMutator).mutate(c, &mut st.fields)?;
397+
}
398+
Ok(())
399+
}
400+
393401
/// Run all type / rec-group mutations. [`GcOpsLimits`] come from [`GcOps`].
394402
fn mutate_with_limits(
395403
&mut self,
@@ -405,10 +413,45 @@ impl TypesMutator {
405413
self.remove_group(c, types)?;
406414
self.merge_groups(c, types)?;
407415
self.split_group(c, types, limits)?;
416+
self.mutate_struct_fields(c, types)?;
417+
408418
Ok(())
409419
}
410420
}
411421

422+
/// Mutator for [`StructField`]: used by `m::vec` to add, remove, and
423+
/// modify fields within a struct type.
424+
#[derive(Debug, Default)]
425+
pub struct StructFieldMutator;
426+
427+
impl Mutate<StructField> for StructFieldMutator {
428+
fn mutate(&mut self, c: &mut Candidates<'_>, field: &mut StructField) -> MutResult<()> {
429+
c.mutation(|ctx| {
430+
let old = format!("{field:?}");
431+
field.field_type = FieldType::random(ctx.rng());
432+
field.mutable = (ctx.rng().gen_u32() % 2) == 0;
433+
log::debug!("Mutated field {old} -> {field:?}");
434+
Ok(())
435+
})?;
436+
Ok(())
437+
}
438+
}
439+
440+
impl Generate<StructField> for StructFieldMutator {
441+
fn generate(&mut self, ctx: &mut Context) -> MutResult<StructField> {
442+
let field = StructField {
443+
field_type: FieldType::random(ctx.rng()),
444+
mutable: (ctx.rng().gen_u32() % 2) == 0,
445+
};
446+
log::debug!("Generated field {field:?}");
447+
Ok(field)
448+
}
449+
}
450+
451+
impl DefaultMutate for StructField {
452+
type DefaultMutate = StructFieldMutator;
453+
}
454+
412455
/// Mutator for [`GcOps`].
413456
///
414457
/// Also implements [`Mutate`] / [`Generate`] for [`GcOp`] so `m::vec` can mutate
@@ -465,7 +508,7 @@ impl Generate<GcOps> for GcOpsMutator {
465508
let mut ops = GcOps::default();
466509
let mut session = mutatis::Session::new();
467510

468-
for _ in 0..64 {
511+
for _ in 0..2048 {
469512
session.mutate(&mut ops)?;
470513
}
471514

0 commit comments

Comments
 (0)