Fuzz the garbage collector

This commit is contained in:
bad 2022-07-13 15:08:22 +02:00
parent 4306dcc6c0
commit cd1d83714b
7 changed files with 125 additions and 38 deletions

2
.gitignore vendored
View file

@ -1 +1 @@
/target target

12
gc/fuzz/Cargo.lock generated
View file

@ -31,6 +31,9 @@ dependencies = [
[[package]] [[package]]
name = "gc" name = "gc"
version = "0.1.0" version = "0.1.0"
dependencies = [
"gctrace-derive",
]
[[package]] [[package]]
name = "gc-fuzz" name = "gc-fuzz"
@ -40,6 +43,15 @@ dependencies = [
"libfuzzer-sys", "libfuzzer-sys",
] ]
[[package]]
name = "gctrace-derive"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "libfuzzer-sys" name = "libfuzzer-sys"
version = "0.4.3" version = "0.4.3"

View file

@ -1,33 +1,83 @@
#![no_main] #![no_main]
use gc::{self, gc_ref::GcRef, test_utils::GotDropped, trace::GCTrace};
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use gc::{self, test_utils::GotDropped}; use std::cell::RefCell;
use libfuzzer_sys::arbitrary::Arbitrary; use libfuzzer_sys::arbitrary;
#[derive(Arbitrary, Debug)] #[derive(arbitrary::Arbitrary, Debug)]
enum AllocatorMethod { struct ChildIDx(usize);
Alloc,
Remove { impl ChildIDx {
// Free the index^th allocation we've made. fn choose<'a, T>(&self, choices: &'a [T]) -> Option<&'a T> {
index: usize let idx = self.choose_index(choices.len())?;
}, Some(choices.get(idx).unwrap())
CollectGarbage }
fn choose_index(&self, len: usize) -> Option<usize> {
(len != 0).then(|| self.0 % len)
}
} }
fuzz_target!(|ops: Vec<AllocatorMethod>| { #[derive(arbitrary::Arbitrary, Debug)]
let allocator = gc::allocator::GCAllocator::new(); enum GraphExplore {
for op in ops { ExploreChild(ChildIDx, Box<GraphExplore>),
match op { Stop,
AllocatorMethod::Alloc => { }
}, #[derive(arbitrary::Arbitrary, Debug)]
enum Op {
AddLink(GraphExplore, Option<GraphExplore>),
RemoveChild(GraphExplore, ChildIDx),
CollectGarbage,
}
AllocatorMethod::Remove {index} => { #[derive(GCTrace, Default)]
}, struct Node {
children: Vec<GCNode>,
}
AllocatorMethod::CollectGarbage => { type GCNode = GcRef<RefCell<Node>>;
} 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<Op>| {
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(&()) };
}); });

View file

@ -2,9 +2,14 @@ use std::{marker::PhantomData, ops::Deref, ptr::NonNull};
use crate::trace::GCTrace; use crate::trace::GCTrace;
#[derive(Clone)]
pub struct GcRef<T: GCTrace>(pub(crate) NonNull<T>, PhantomData<T>); pub struct GcRef<T: GCTrace>(pub(crate) NonNull<T>, PhantomData<T>);
impl<T: GCTrace> Clone for GcRef<T> {
fn clone(&self) -> Self {
Self(self.0, self.1)
}
}
impl<T: GCTrace> Deref for GcRef<T> { impl<T: GCTrace> Deref for GcRef<T> {
type Target = T; type Target = T;

View file

@ -21,8 +21,7 @@ impl GCTracer {
pub fn mark_reachable_rec<T: GCTrace>(&mut self, obj: &T) { pub fn mark_reachable_rec<T: GCTrace>(&mut self, obj: &T) {
let ptr = obj as *const T as *const (); let ptr = obj as *const T as *const ();
if !self.explored.contains(&ptr) { if self.explored.insert(ptr) {
self.explored.insert(ptr);
obj.trace(self); obj.trace(self);
} }
} }

View file

@ -40,3 +40,11 @@ unsafe impl<K: GCTrace, V: GCTrace> GCTrace for HashMap<K, V> {
} }
} }
} }
unsafe impl<T: GCTrace> GCTrace for Vec<T> {
fn trace(&self, tracer: &mut GCTracer) {
for val in self.iter() {
tracer.mark_reachable_rec(val)
}
}
}

View file

@ -16,29 +16,42 @@ pub struct Environment {
} }
impl Environment { impl Environment {
pub fn set_var(&mut self, name: String, v: Primitive) -> Option<Primitive> { // Update an already existing variable in current scope
self.variables.insert(name, v) pub fn update_var(&mut self, name: &str, v: Primitive) -> Option<Primitive> {
if let Some(cur) = self.variables.get_mut(name) {
Some(std::mem::replace(cur, v))
} else {
self.parent
.as_ref()
.and_then(|parent| parent.borrow_mut().update_var(name, v))
}
} }
pub fn set_var(&mut self, name: String, v: Primitive) -> Option<Primitive> {
self.update_var(&name, v.clone())
.or_else(|| self.variables.insert(name, v))
}
pub fn get_var(&self, name: &str) -> Option<Primitive> { pub fn get_var(&self, name: &str) -> Option<Primitive> {
self.variables self.variables.get(name).cloned().or_else(|| {
.get(name) self.parent
.cloned() .as_ref()
.or_else(|| self.parent.as_ref().and_then(|v| v.borrow().get_var(name))) .and_then(|v| (**v).borrow().get_var(name))
})
} }
} }
pub struct World { pub struct World {
env: GcRef<Environment>, env: GcRef<RefCell<Environment>>,
_gc: GCAllocator, _gc: GCAllocator,
} }
impl World { impl World {
pub fn set_var(&mut self, _name: String, _v: Primitive) -> Option<Primitive> { pub fn set_var(&mut self, name: String, v: Primitive) -> Option<Primitive> {
todo!() self.env.borrow_mut().set_var(name, v)
// self.env.set_var(name, v)
} }
pub fn get_var(&self, name: &str) -> Option<Primitive> { pub fn get_var(&self, name: &str) -> Option<Primitive> {
self.env.get_var(name) self.env.borrow().get_var(name)
} }
} }
@ -56,7 +69,7 @@ impl World {
impl Default for World { impl Default for World {
fn default() -> Self { fn default() -> Self {
let mut gc = GCAllocator::default(); let mut gc = GCAllocator::default();
let env = gc.alloc(Environment::default()); let env = gc.alloc(RefCell::default());
Self { env, _gc: gc } Self { env, _gc: gc }
} }
} }