diff --git a/.gitignore b/.gitignore index ea8c4bf..eb5a316 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target +target diff --git a/gc/fuzz/Cargo.lock b/gc/fuzz/Cargo.lock index 78771d2..0d8e497 100644 --- a/gc/fuzz/Cargo.lock +++ b/gc/fuzz/Cargo.lock @@ -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" diff --git a/gc/fuzz/fuzz_targets/fuzz_target_1.rs b/gc/fuzz/fuzz_targets/fuzz_target_1.rs index a981436..f45eedd 100644 --- a/gc/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/gc/fuzz/fuzz_targets/fuzz_target_1.rs @@ -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 { + (len != 0).then(|| self.0 % len) + } } -fuzz_target!(|ops: Vec| { - let allocator = gc::allocator::GCAllocator::new(); - for op in ops { - match op { - AllocatorMethod::Alloc => { +#[derive(arbitrary::Arbitrary, Debug)] +enum GraphExplore { + ExploreChild(ChildIDx, Box), + Stop, +} - }, +#[derive(arbitrary::Arbitrary, Debug)] +enum Op { + AddLink(GraphExplore, Option), + RemoveChild(GraphExplore, ChildIDx), + CollectGarbage, +} - AllocatorMethod::Remove {index} => { - }, +#[derive(GCTrace, Default)] +struct Node { + children: Vec, +} - AllocatorMethod::CollectGarbage => { +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(&()) }; }); diff --git a/gc/src/gc_ref.rs b/gc/src/gc_ref.rs index 7f5ae86..8c7ac57 100644 --- a/gc/src/gc_ref.rs +++ b/gc/src/gc_ref.rs @@ -2,9 +2,14 @@ use std::{marker::PhantomData, ops::Deref, ptr::NonNull}; use crate::trace::GCTrace; -#[derive(Clone)] pub struct GcRef(pub(crate) NonNull, PhantomData); +impl Clone for GcRef { + fn clone(&self) -> Self { + Self(self.0, self.1) + } +} + impl Deref for GcRef { type Target = T; diff --git a/gc/src/trace.rs b/gc/src/trace.rs index 3d7a76d..487cf6d 100644 --- a/gc/src/trace.rs +++ b/gc/src/trace.rs @@ -21,8 +21,7 @@ impl GCTracer { pub fn mark_reachable_rec(&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); } } diff --git a/gc/src/trace_impl.rs b/gc/src/trace_impl.rs index d264fa3..eab2547 100644 --- a/gc/src/trace_impl.rs +++ b/gc/src/trace_impl.rs @@ -40,3 +40,11 @@ unsafe impl GCTrace for HashMap { } } } + +unsafe impl GCTrace for Vec { + fn trace(&self, tracer: &mut GCTracer) { + for val in self.iter() { + tracer.mark_reachable_rec(val) + } + } +} diff --git a/src/interpreter/world.rs b/src/interpreter/world.rs index ef89c5c..142346b 100644 --- a/src/interpreter/world.rs +++ b/src/interpreter/world.rs @@ -16,29 +16,42 @@ pub struct Environment { } impl Environment { - pub fn set_var(&mut self, name: String, v: Primitive) -> Option { - self.variables.insert(name, v) + // Update an already existing variable in current scope + pub fn update_var(&mut self, name: &str, v: Primitive) -> Option { + 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 { + self.update_var(&name, v.clone()) + .or_else(|| self.variables.insert(name, v)) + } + pub fn get_var(&self, name: &str) -> Option { - 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, + env: GcRef>, _gc: GCAllocator, } impl World { - pub fn set_var(&mut self, _name: String, _v: Primitive) -> Option { - todo!() - // self.env.set_var(name, v) + pub fn set_var(&mut self, name: String, v: Primitive) -> Option { + self.env.borrow_mut().set_var(name, v) } pub fn get_var(&self, name: &str) -> Option { - 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 } } }