#![allow(clippy::missing_const_for_thread_local)] use std::cell::{Cell, RefCell}; use std::rc::{Rc, Weak}; use rust_cc::*; #[test] fn test_complex() { struct A { b: Cc, } struct B { c: Cc, } struct C { a: RefCell>>, b: RefCell>>, } struct D { c: Cc, } unsafe impl Trace for A { fn trace(&self, ctx: &mut Context<'_>) { self.b.trace(ctx); } } impl Finalize for A {} unsafe impl Trace for B { fn trace(&self, ctx: &mut Context<'_>) { self.c.trace(ctx); } } impl Finalize for B {} unsafe impl Trace for D { fn trace(&self, ctx: &mut Context<'_>) { self.c.trace(ctx); } } impl Finalize for D {} unsafe impl Trace for C { fn trace(&self, ctx: &mut Context<'_>) { self.b.trace(ctx); if let Some(cc) = &*self.a.borrow() { cc.trace(ctx); } } } impl Finalize for C {} let a = Cc::new(A { b: Cc::new(B { c: Cc::new(C { a: RefCell::new(None), b: RefCell::new(None), }), }), }); *a.b.c.a.borrow_mut() = Some(a.clone()); *a.b.c.b.borrow_mut() = Some(a.b.clone()); let d = Cc::new(D { c: a.b.c.clone() }); drop(a); collect_cycles(); if let Some(a) = &*d.c.a.borrow() { let _count = a.strong_count(); } drop(d); collect_cycles(); } /*#[test] fn useless_cyclic() { let _cc = Cc::::new_cyclic(|_| { collect_cycles(); { let _ = Cc::new(42); } collect_cycles(); 10 }); collect_cycles(); drop(_cc); collect_cycles(); }*/ #[cfg(feature = "finalization")] #[test] fn test_finalization() { thread_local! { static FINALIZED: Cell = Cell::new(false); static DROPPED: Cell = Cell::new(false); static FINALIZEDB: Cell = Cell::new(false); static DROPPEDB: Cell = Cell::new(false); } fn assert_not_dropped() { assert!(!DROPPED.with(|cell| cell.get())); assert!(!DROPPEDB.with(|cell| cell.get())); } // C doesn't impl Trace, so it cannot be put inside a Cc struct C { a: RefCell>, } struct A { dropped: Cell, c: Weak, b: RefCell>>, } struct B { dropped: Cell, a: Cc, } unsafe impl Trace for A { fn trace(&self, ctx: &mut Context<'_>) { if let Some(b) = &*self.b.borrow() { b.trace(ctx); } } } unsafe impl Trace for B { fn trace(&self, ctx: &mut Context<'_>) { self.a.trace(ctx); } } impl Finalize for B { fn finalize(&self) { FINALIZEDB.with(|cell| cell.set(true)); } } impl Finalize for A { fn finalize(&self) { FINALIZED.with(|cell| cell.set(true)); if let Some(c) = self.c.upgrade() { *c.a.borrow_mut() = Some(A { dropped: Cell::new(false), c: self.c.clone(), b: RefCell::new(self.b.borrow_mut().take()), }); } } } impl Drop for A { fn drop(&mut self) { assert!(!self.dropped.get()); self.dropped.set(true); DROPPED.with(|cell| cell.set(true)); } } impl Drop for B { fn drop(&mut self) { assert!(!self.dropped.get()); self.dropped.set(true); DROPPEDB.with(|cell| cell.set(true)); } } { let c1 = Rc::new(C { a: RefCell::new(None), }); let a = Cc::new(A { dropped: Cell::new(false), c: Rc::downgrade(&c1), b: RefCell::new(None), }); *a.b.borrow_mut() = Some(Cc::new(B { dropped: Cell::new(false), a: a.clone(), })); assert_eq!(a.strong_count(), 2); assert!(!FINALIZED.with(|cell| cell.get())); assert!(!FINALIZEDB.with(|cell| cell.get())); assert_not_dropped(); collect_cycles(); assert!(!FINALIZED.with(|cell| cell.get())); assert!(!FINALIZEDB.with(|cell| cell.get())); assert_not_dropped(); //let _c = a.c.clone(); assert!(!FINALIZED.with(|cell| cell.get())); assert!(!FINALIZEDB.with(|cell| cell.get())); assert_not_dropped(); drop(a); assert!(!FINALIZED.with(|cell| cell.get())); assert!(!FINALIZEDB.with(|cell| cell.get())); assert_not_dropped(); collect_cycles(); // a dropped here assert!(FINALIZED.with(|cell| cell.get())); assert!(FINALIZEDB.with(|cell| cell.get())); assert_not_dropped(); // Reset DROPPED FINALIZED.with(|cell| cell.set(false)); FINALIZEDB.with(|cell| cell.set(false)); match &*c1.a.borrow() { Some(a) => { assert!(!a.dropped.get()); a.b.borrow().iter().for_each(|b| { assert!(!b.dropped.get()); let _ = b.strong_count(); let _ = b.a.strong_count(); assert!(Weak::ptr_eq(&b.a.c, &a.c)); b.a.b.borrow().iter().for_each(|b| { assert!(!b.dropped.get()); let _ = b.strong_count(); }); }); }, None => panic!("None"), }; assert_not_dropped(); } collect_cycles(); // This tests that finalizers are called only one time per object assert!(!FINALIZED.with(|cell| cell.get())); assert!(!FINALIZEDB.with(|cell| cell.get())); assert!(DROPPED.with(|cell| cell.get())); assert!(DROPPEDB.with(|cell| cell.get())); } #[test] fn test_finalize_drop() { thread_local! { static FINALIZED: Cell = Cell::new(false); static DROPPED: Cell = Cell::new(false); static FINALIZEDB: Cell = Cell::new(false); static DROPPEDB: Cell = Cell::new(false); } fn assert_not_dropped() { assert!(!DROPPED.with(|cell| cell.get())); assert!(!DROPPEDB.with(|cell| cell.get())); } // C doesn't impl Trace, so it cannot be put inside a Cc struct C { _a: RefCell>, } struct A { dropped: Cell, _c: Weak, b: RefCell>>, } struct B { dropped: Cell, a: Cc, } unsafe impl Trace for A { fn trace(&self, ctx: &mut Context<'_>) { if let Some(b) = &*self.b.borrow() { b.trace(ctx); } } } unsafe impl Trace for B { fn trace(&self, ctx: &mut Context<'_>) { self.a.trace(ctx); } } impl Finalize for A { fn finalize(&self) { FINALIZED.with(|cell| cell.set(true)); } } impl Finalize for B { fn finalize(&self) { FINALIZEDB.with(|cell| cell.set(true)); } } impl Drop for A { fn drop(&mut self) { assert!(!self.dropped.get()); self.dropped.set(true); DROPPED.with(|cell| cell.set(true)); } } impl Drop for B { fn drop(&mut self) { assert!(!self.dropped.get()); self.dropped.set(true); DROPPEDB.with(|cell| cell.set(true)); } } let cc = Rc::new_cyclic(|weak| C { _a: RefCell::new(Some(A { dropped: Cell::new(false), _c: weak.clone(), b: RefCell::new(Some(Cc::new(B { dropped: Cell::new(false), a: Cc::new(A { dropped: Cell::new(false), _c: weak.clone(), b: RefCell::new(None), }), }))), })), }); assert!(!FINALIZED.with(|cell| cell.get())); assert!(!FINALIZEDB.with(|cell| cell.get())); assert_not_dropped(); drop(cc); collect_cycles(); if cfg!(feature = "finalization") { assert!(FINALIZED.with(|cell| cell.get())); assert!(FINALIZEDB.with(|cell| cell.get())); } assert!(DROPPED.with(|cell| cell.get())); assert!(DROPPEDB.with(|cell| cell.get())); } #[cfg(feature = "finalization")] #[test] fn finalization_test() { struct Circular { next: RefCell>>, does_finalization: Cell, } unsafe impl Trace for Circular { fn trace(&self, ctx: &mut Context<'_>) { self.next.trace(ctx); } } impl Finalize for Circular { fn finalize(&self) { if self.does_finalization.get() { *self.next.borrow_mut() = Some(Cc::new(Circular { next: self.next.clone(), does_finalization: Cell::new(false), })); self.does_finalization.set(false); } } } { let cc = Cc::new(Circular { next: RefCell::new(Some(Cc::new(Circular { next: RefCell::new(None), does_finalization: Cell::new(false), }))), does_finalization: Cell::new(true), }); *cc.next.borrow().as_ref().unwrap().next.borrow_mut() = Some(cc.clone()); } collect_cycles(); } // Code which created UB when Rc had Trace implemented // Commented to avoid compilation errors since Rc doesn't implement Trace anymore /*#[test] fn rc_test() { let mut rc = None; { let _cc = Cc::>::new_cyclic(|cc| { let rc_ = Rc::new(Cc::new(cc.clone())); rc = Some(rc_.clone()); Box::new(rc_) }); } collect_cycles(); assert!(rc.expect("rc not set").is_valid()); }*/ #[test] fn box_test() { { let cc = Cc::new(RefCell::new(None)); { *cc.borrow_mut() = Some(Box::new(cc.clone()) as Box); } } collect_cycles(); } #[test] fn alignment_test() { macro_rules! define_structs { ($($i:literal),+) => { $({ #[repr(align($i))] struct A { cc: RefCell>>, } unsafe impl Trace for A { fn trace(&self, ctx: &mut Context<'_>) { self.cc.trace(ctx); } } impl Finalize for A {} fn use_struct() { let cc = Cc::new(A { cc: RefCell::new(None), }); *cc.cc.borrow_mut() = Some(cc.clone()); } use_struct(); })+ collect_cycles(); }; } define_structs!(1, 2, 4, 8, 16, 32, 64, 128); }