use lua_gc::{Gc, HeapRef, 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)
}
}
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();
match lua_gc::with_current_heap(|heap| {
heap.map(|heap| {
let token = heap.register_allocation_token(identity);
(HeapRef::from_heap(heap), token)
})
}) {
Some((heap, allocation_token)) => GcWeak {
target: self.0,
identity,
allocation_token,
heap: Some(heap),
},
None => panic!(
"GcRef::downgrade::<{}> with no active HeapGuard — a GcRef \
operated outside its owning heap's guard cannot be validated \
against any heap, and reading the box to decide would be a \
use-after-free if the heap has been dropped; push a HeapGuard \
on the entry path",
std::any::type_name::<T>()
),
}
}
pub fn account_buffer(&self, delta: isize) {
if delta == 0 {
return;
}
match lua_gc::with_current_heap(|h| {
h.map(|h| {
self.0.account_buffer(h, delta);
})
}) {
Some(()) => {}
None => panic!(
"GcRef::account_buffer::<{}>({delta}) with no active HeapGuard \
— the charge would be silently dropped and the pacer would \
drift from real memory, and reading the box to decide would be \
a use-after-free if the heap has been dropped; 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::*;
use lua_gc::Marker;
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);
}
#[test]
#[should_panic(expected = "no active HeapGuard")]
fn guardless_downgrade_of_heap_owned_box_panics() {
let heap = lua_gc::Heap::new();
let strong = {
let _guard = lua_gc::HeapGuard::push(&heap);
GcRef::new(Cell0)
};
let _ = strong.downgrade();
}
#[test]
#[should_panic(expected = "no active HeapGuard")]
fn guardless_account_buffer_on_heap_owned_box_panics() {
let heap = lua_gc::Heap::new();
let strong = {
let _guard = lua_gc::HeapGuard::push(&heap);
GcRef::new(Cell0)
};
strong.account_buffer(64);
}
#[test]
fn downgrade_after_close_cannot_resurrect_freed_box() {
let heap = lua_gc::Heap::new();
heap.unpause();
let _guard = lua_gc::HeapGuard::push(&heap);
let stale = GcRef::new(Cell0);
heap.drop_all();
let weak = stale.downgrade();
assert!(
weak.upgrade().is_none(),
"a weak handle minted after close must never upgrade — the box \
is freed and the heap is closed"
);
}
#[test]
fn downgrade_after_close_under_quarantine_reads_no_box() {
let heap = lua_gc::Heap::new_quarantined();
heap.unpause();
let _guard = lua_gc::HeapGuard::push(&heap);
let stale = GcRef::new(Cell0);
heap.drop_all();
let weak = stale.downgrade();
assert!(weak.upgrade().is_none());
}
}
impl<T: PartialEq + Trace + 'static> PartialEq for GcRef<T> {
fn eq(&self, other: &Self) -> bool {
Gc::ptr_eq(self.0, other.0) || **self == **other
}
}