Fuzz the garbage collector
This commit is contained in:
parent
4306dcc6c0
commit
cd1d83714b
7 changed files with 125 additions and 38 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1 @@
|
||||||
/target
|
target
|
||||||
|
|
12
gc/fuzz/Cargo.lock
generated
12
gc/fuzz/Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fuzz_target!(|ops: Vec<AllocatorMethod>| {
|
fn choose_index(&self, len: usize) -> Option<usize> {
|
||||||
let allocator = gc::allocator::GCAllocator::new();
|
(len != 0).then(|| self.0 % len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GCTrace, Default)]
|
||||||
|
struct Node {
|
||||||
|
children: Vec<GCNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
for op in ops {
|
||||||
match op {
|
match op {
|
||||||
AllocatorMethod::Alloc => {
|
Op::AddLink(parent_path, child_path) => {
|
||||||
|
let child = child_path
|
||||||
},
|
.map(|c| Node::explore(root.clone(), c))
|
||||||
|
.unwrap_or_else(|| allocator.alloc(RefCell::default()));
|
||||||
AllocatorMethod::Remove {index} => {
|
|
||||||
},
|
|
||||||
|
|
||||||
AllocatorMethod::CollectGarbage => {
|
|
||||||
|
|
||||||
|
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(&()) };
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue