#![no_main] use gc::{self, gc_ref::GcRef, test_utils::GotDropped, trace::GCTrace}; use libfuzzer_sys::fuzz_target; use std::cell::RefCell; use libfuzzer_sys::arbitrary; #[derive(arbitrary::Arbitrary, Debug)] struct ChildIDx(usize); impl ChildIDx { fn choose<'a, T>(&self, choices: &'a [T]) -> Option<&'a T> { let idx = self.choose_index(choices.len())?; Some(choices.get(idx).unwrap()) } fn choose_index(&self, len: usize) -> Option { (len != 0).then(|| self.0 % len) } } #[derive(arbitrary::Arbitrary, Debug)] enum GraphExplore { ExploreChild(ChildIDx, Box), Stop, } #[derive(arbitrary::Arbitrary, Debug)] enum Op { AddLink(GraphExplore, Option), RemoveChild(GraphExplore, ChildIDx), CollectGarbage, } #[derive(GCTrace, Default)] struct Node { children: Vec, } type GCNode = GcRef>; impl Node { fn explore(node: GCNode, instructions: GraphExplore) -> GCNode { match instructions { GraphExplore::ExploreChild(child_idx, child_instructions) => { let child = child_idx .choose(&*node.borrow().children) .map(|v| v.clone()); child .map(|c| Node::explore(c, *child_instructions)) .unwrap_or(node) } GraphExplore::Stop => node, } } } fuzz_target!(|ops: Vec| { let mut allocator = gc::allocator::GCAllocator::default(); let root: GCNode = allocator.alloc(RefCell::default()); for op in ops { match op { Op::AddLink(parent_path, child_path) => { let child = child_path .map(|c| Node::explore(root.clone(), c)) .unwrap_or_else(|| allocator.alloc(RefCell::default())); let parent = Node::explore(root.clone(), parent_path); parent.borrow_mut().children.push(child); } Op::RemoveChild(parent_path, child_idx) => { let parent = Node::explore(root.clone(), parent_path); let children = &mut parent.borrow_mut().children; let idx = child_idx.choose_index(children.len()); if let Some(idx) = idx { children.remove(idx); } } Op::CollectGarbage => unsafe { allocator.gc(&root) }, } } unsafe { allocator.gc(&()) }; });