use lua_gc::{Gc, HeapRef, Marker, Trace};
#[derive(Debug)]
pub struct GcRef<T: Trace + 'static>(pub Gc<T>);
impl<T: Trace + 'static> GcRef<T> {
pub fn new(value: T) -> Self {
let gc = lua_gc::with_current_heap(|heap| match heap {
Some(heap) => heap.allocate(value),
None => panic!(
"GcRef::new::<{}> with no active HeapGuard — a detached allocation \
would never be freed (issue #249 class); push a HeapGuard or \
bootstrap window on the entry path",
std::any::type_name::<T>()
),
});
GcRef(gc)
}
pub fn trace_obj(&self, m: &mut Marker) {
if m.try_visit(self.identity()) {
(**self).trace(m);
}
}
}
impl<T: Trace + 'static> GcRef<T> {
pub fn ptr_eq(a: &Self, b: &Self) -> bool {
Gc::ptr_eq(a.0, b.0)
}
pub fn identity(&self) -> usize {
self.0.identity()
}
pub fn strong_count(&self) -> usize {
1
}
pub fn weak_count(&self) -> usize {
0
}
pub fn downgrade(&self) -> GcWeak<T> {
let identity = self.identity();
let tracked = lua_gc::with_current_heap(|heap| {
heap.map(|heap| {
let token = heap.register_allocation_token(identity);
(HeapRef::from_heap(heap), token)
})
});
if tracked.is_none() && lua_gc::strict_guard_mode() && self.0.is_heap_owned() {
panic!(
"OMNILUA_GC_STRICT_GUARD: GcRef::downgrade::<{}> on a heap-owned box with no \
active HeapGuard — the weak handle would upgrade forever, including after \
sweep frees the target (use-after-free); push a HeapGuard on the entry path",
std::any::type_name::<T>()
);
}
let (heap, allocation_token) = match tracked {
Some((heap, token)) => (Some(heap), token),
None => (None, 0),
};
GcWeak {
target: self.0,
identity,
allocation_token,
heap,
}
}
pub fn account_buffer(&self, delta: isize) {
if delta == 0 {
return;
}
lua_gc::with_current_heap(|h| match h {
Some(h) => self.0.account_buffer(h, delta),
None => {
if lua_gc::strict_guard_mode() && self.0.is_heap_owned() {
panic!(
"OMNILUA_GC_STRICT_GUARD: account_buffer({delta}) on a heap-owned \
{} with no active HeapGuard — the charge would be silently dropped \
and the pacer would drift from real memory; push a HeapGuard on \
the entry path",
std::any::type_name::<T>()
);
}
}
})
}
}
#[derive(Debug)]
pub struct GcWeak<T: Trace + 'static> {
target: Gc<T>,
identity: usize,
allocation_token: usize,
heap: Option<HeapRef>,
}
impl<T: Trace + 'static> GcWeak<T> {
pub fn upgrade(&self) -> Option<GcRef<T>> {
if let Some(heap) = &self.heap {
if !heap.contains_allocation(self.identity, self.allocation_token) {
return None;
}
}
Some(GcRef(self.target))
}
pub fn strong_count(&self) -> usize {
usize::from(self.upgrade().is_some())
}
pub fn identity(&self) -> usize {
self.identity
}
}
impl<T: Trace + 'static> Clone for GcWeak<T> {
fn clone(&self) -> Self {
GcWeak {
target: self.target,
identity: self.identity,
allocation_token: self.allocation_token,
heap: self.heap.clone(),
}
}
}
impl<T: Trace + 'static> Clone for GcRef<T> {
fn clone(&self) -> Self {
GcRef(self.0)
}
}
impl<T: Trace + 'static> Copy for GcRef<T> {}
impl<T: Trace + 'static> std::ops::Deref for GcRef<T> {
type Target = T;
fn deref(&self) -> &T {
&*self.0
}
}
impl<T: Trace + 'static> AsRef<T> for GcRef<T> {
fn as_ref(&self) -> &T {
&*self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NoRoots;
impl Trace for NoRoots {
fn trace(&self, _m: &mut Marker) {}
}
#[derive(Debug)]
struct Cell0;
impl Trace for Cell0 {
fn trace(&self, _m: &mut Marker) {}
}
#[test]
fn heap_tracked_weak_refs_stop_upgrading_after_sweep() {
let heap = lua_gc::Heap::new();
heap.unpause();
let _guard = lua_gc::HeapGuard::push(&heap);
let strong = GcRef::new(Cell0);
let weak = strong.downgrade();
assert!(weak.upgrade().is_some());
assert_eq!(weak.strong_count(), 1);
heap.full_collect(&NoRoots);
assert!(weak.upgrade().is_none());
assert_eq!(weak.strong_count(), 0);
}
#[test]
#[should_panic(expected = "no active HeapGuard")]
fn guardless_allocation_panics() {
let _ = GcRef::new(Cell0);
}
}
impl<T: PartialEq + Trace + 'static> PartialEq for GcRef<T> {
fn eq(&self, other: &Self) -> bool {
Gc::ptr_eq(self.0, other.0) || **self == **other
}
}