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]]
name = "gc"
version = "0.1.0"
dependencies = [
"gctrace-derive",
]
[[package]]
name = "gc-fuzz"
@ -40,6 +43,15 @@ dependencies = [
"libfuzzer-sys",
]
[[package]]
name = "gctrace-derive"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "libfuzzer-sys"
version = "0.4.3"

View File

@ -1,33 +1,83 @@
#![no_main]
use gc::{self, gc_ref::GcRef, test_utils::GotDropped, trace::GCTrace};
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)]
enum AllocatorMethod {
Alloc,
Remove {
// Free the index^th allocation we've made.
index: usize
},
CollectGarbage
#[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<usize> {
(len != 0).then(|| self.0 % len)
}
}
fuzz_target!(|ops: Vec<AllocatorMethod>| {
let allocator = gc::allocator::GCAllocator::new();
for op in ops {
match op {
AllocatorMethod::Alloc => {
#[derive(arbitrary::Arbitrary, Debug)]
enum GraphExplore {
ExploreChild(ChildIDx, Box<GraphExplore>),
Stop,
}
},
#[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;
#[derive(Clone)]
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> {
type Target = T;

View File

@ -21,8 +21,7 @@ impl GCTracer {
pub fn mark_reachable_rec<T: GCTrace>(&mut self, obj: &T) {
let ptr = obj as *const T as *const ();
if !self.explored.contains(&ptr) {
self.explored.insert(ptr);
if self.explored.insert(ptr) {
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 {
pub fn set_var(&mut self, name: String, v: Primitive) -> Option<Primitive> {
self.variables.insert(name, v)
// Update an already existing variable in current scope
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> {
self.variables
.get(name)
.cloned()
.or_else(|| self.parent.as_ref().and_then(|v| v.borrow().get_var(name)))
self.variables.get(name).cloned().or_else(|| {
self.parent
.as_ref()
.and_then(|v| (**v).borrow().get_var(name))
})
}
}
pub struct World {
env: GcRef<Environment>,
env: GcRef<RefCell<Environment>>,
_gc: GCAllocator,
}
impl World {
pub fn set_var(&mut self, _name: String, _v: Primitive) -> Option<Primitive> {
todo!()
// self.env.set_var(name, v)
pub fn set_var(&mut self, name: String, v: Primitive) -> Option<Primitive> {
self.env.borrow_mut().set_var(name, v)
}
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 {
fn default() -> Self {
let mut gc = GCAllocator::default();
let env = gc.alloc(Environment::default());
let env = gc.alloc(RefCell::default());
Self { env, _gc: gc }
}
}