fuzzcheck-rs icon indicating copy to clipboard operation
fuzzcheck-rs copied to clipboard

Is there a way to construct mutually recursive structs?

Open teymour-aldridge opened this issue 3 years ago • 3 comments

e.g. trying to derive DefaultMutator fails (not unexpectedly) on the following structs.

struct MutuallyRecursiveA {
  b: Vec<MutuallyRecursiveB>,
  data: Vec<u64>
}

struct MutuallyRecursiveB {
  a: Option<A>,
  data: bool
}

I tried using make_mutator, but I couldn't get the recursion to work.

Is this possible?

teymour-aldridge avatar Jan 15 '22 18:01 teymour-aldridge

I think I've solved in my case by reworking the types I'm using to not be mutually recursive.

teymour-aldridge avatar Jan 16 '22 16:01 teymour-aldridge

It's possible, but it is pushing of the limits of what is currently possible, and I don't know what the quality of the resulting mutators will be (maybe they're fine, I just haven't tested it).

You can try to write something like:

make_mutator! {
    name: AMutator,
    recursive: true,
    default: false,
    type:
        struct MutuallyRecursiveA {
            b: Vec<MutuallyRecursiveB>,
            #[field_mutator(<Vec<u64> as DefaultMutator>::Mutator = { <Vec<u64>>::default_mutator() })]
            data: Vec<u64>,
        }
}

make_mutator! {
    name: BMutator,
    recursive: true,
    default: true,
    type:
        struct MutuallyRecursiveB {
            #[field_mutator(
                OptionMutator<MutuallyRecursiveA, AMutator<VecMutator<MutuallyRecursiveB, RecurToMutator<BMutator>>>>
            ) = {
                OptionMutator::new(AMutator::new(
                    VecMutator::new(self_.into(), 0..=usize::MAX),
                    <Vec<u64>>::default_mutator(),
                ))
            }]
            a: Option<MutuallyRecursiveA>,
            #[field_mutator(<bool as DefaultMutator>::Mutator = { <bool>::default_mutator() })]
            data: bool
        }
}

It is not a perfect solution: MutuallyRecursiveA has no default mutator, only MutuallyRecursiveB does. You could however, repeat the snippet above twice except that A and B are switched, giving different names to the mutators. The idea is that for each mutually recursive type, you have a “default mutator” and a “mutator to be used only as a submutator to the other type’s default mutator”.

That's not fundamentally necessary though, and I could improve the procedural macro to make it possible to define these mutually recursive mutators using only one make_mutator! per type. I would need to think about those generic bounds a bit more (again! lol).

Note though that due to a questionable design decision (because of superfluous generic bound, again!) , even the code I posted above doesn't actually work. So you have to construct BMutator with:

 let mutator = RecursiveMutator::new(|self_| {
    BMutator::new(
        OptionMutator::new(AMutator::new(
            VecMutator::new(self_.into(), 0..=usize::MAX),
            <Vec<u64>>::default_mutator(),
        )),
        bool::default_mutator(),
    )
});

I'll try and fix that and post the progress in this issue. It may be a LONG time before mutually-recursive types are easy to work with though.

loiclec avatar Jan 17 '22 16:01 loiclec

Thank you so much!

teymour-aldridge avatar Jan 17 '22 17:01 teymour-aldridge