├── rustfmt.toml ├── .gitattributes ├── tests ├── derive_macro_tests │ ├── invalid_ignore_attribute.rs │ ├── invalid_field_bounds.rs │ ├── invalid_ignore_attribute.stderr │ ├── derive_finalize.rs │ ├── empty_attribute.rs │ ├── derive_trace.rs │ ├── invalid_no_drop_attribute.rs │ ├── invalid_drop_impl.rs │ ├── invalid_no_drop_attribute.stderr │ ├── no_drop.rs │ ├── invalid_attributes.rs │ ├── invalid_drop_impl.stderr │ ├── ignored_variant.rs │ ├── traced_fields_struct.rs │ ├── traced_fields_enum.rs │ ├── invalid_field_bounds.stderr │ └── invalid_attributes.stderr ├── macro_tests.rs ├── weak_upgrade_tests.rs ├── auto_collect.rs └── cc.rs ├── .gitignore ├── derive ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── .github └── workflows │ ├── test.yml │ ├── miri.yml │ ├── build.yml │ └── bench.yml ├── LICENSE-MIT ├── benches ├── benches │ ├── large_linked_list.rs │ ├── binary_trees.rs │ ├── stress_test.rs │ └── binary_trees_with_parent_pointers.rs └── bench.rs ├── Cargo.toml ├── src ├── weak │ └── weak_counter_marker.rs ├── derives.rs ├── tests │ ├── mod.rs │ ├── cleaners │ │ └── mod.rs │ ├── bench_code │ │ └── mod.rs │ ├── counter_marker.rs │ ├── panicking.rs │ └── weak │ │ └── mod.rs ├── utils.rs ├── cleaners │ └── mod.rs ├── counter_marker.rs ├── config.rs ├── state.rs ├── lists.rs └── trace.rs ├── CONTRIBUTING.md ├── README.md └── LICENSE-APACHE /rustfmt.toml: -------------------------------------------------------------------------------- 1 | version = "Two" 2 | 3 | match_block_trailing_comma = true 4 | group_imports = "StdExternalCrate" 5 | max_width = 100 6 | chain_width = 100 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform normalization 2 | * text=auto 3 | 4 | *.rs text diff=rust 5 | *.md text diff=markdown 6 | *.toml text diff=toml 7 | LICENSE text 8 | .gitignore text 9 | .gitattributes text 10 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_ignore_attribute.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Trace, Finalize)] 4 | #[rust_cc(ignore)] 5 | struct MyStruct { 6 | } 7 | 8 | #[derive(Trace, Finalize)] 9 | #[rust_cc(ignore)] 10 | enum MyEnum { 11 | A(), 12 | } 13 | 14 | fn main() { 15 | } 16 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_field_bounds.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | struct DoesNotImplementTrace; 4 | 5 | #[derive(Trace, Finalize)] 6 | struct MyStruct1 { 7 | field: DoesNotImplementTrace, 8 | } 9 | 10 | #[derive(Trace, Finalize)] 11 | enum MyEnum3 { 12 | A(DoesNotImplementTrace), 13 | } 14 | 15 | fn main() { 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | .idea/ 13 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_ignore_attribute.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute position 2 | --> tests/derive_macro_tests/invalid_ignore_attribute.rs:4:11 3 | | 4 | 4 | #[rust_cc(ignore)] 5 | | ^^^^^^ 6 | 7 | error: Invalid attribute position 8 | --> tests/derive_macro_tests/invalid_ignore_attribute.rs:9:11 9 | | 10 | 9 | #[rust_cc(ignore)] 11 | | ^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/derive_finalize.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Finalize)] 4 | struct MyStruct { 5 | a: (), 6 | } 7 | 8 | #[derive(Finalize)] // Finalize is required by Trace 9 | enum MyEnum { 10 | A(), 11 | B(), 12 | } 13 | 14 | fn main() { 15 | fn test(_t: T) { 16 | } 17 | 18 | test(MyStruct { 19 | a: (), 20 | }); 21 | 22 | test(MyEnum::A()); 23 | } 24 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/empty_attribute.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Trace, Finalize)] 4 | #[rust_cc()] 5 | struct MyStruct { 6 | #[rust_cc()] 7 | a: (), 8 | } 9 | 10 | #[derive(Trace, Finalize)] 11 | #[rust_cc()] 12 | enum MyEnum { 13 | #[rust_cc()] 14 | A(#[rust_cc()] i32), 15 | #[rust_cc()] 16 | B { 17 | #[rust_cc()] 18 | b: i32, 19 | } 20 | } 21 | 22 | fn main() { 23 | } 24 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/derive_trace.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Trace)] 4 | struct MyStruct { 5 | a: (), 6 | } 7 | 8 | impl Finalize for MyStruct { 9 | } 10 | 11 | #[derive(Trace)] 12 | enum MyEnum { 13 | A(), 14 | B(), 15 | } 16 | 17 | impl Finalize for MyEnum { 18 | } 19 | 20 | fn main() { 21 | fn test(_t: T) { 22 | } 23 | 24 | test(MyStruct { 25 | a: (), 26 | }); 27 | test(MyEnum::A()); 28 | } 29 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_no_drop_attribute.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Trace, Finalize)] 4 | struct MyStruct { 5 | #[rust_cc(unsafe_no_drop)] 6 | a: (), 7 | } 8 | 9 | #[derive(Trace, Finalize)] 10 | enum MyEnum1 { 11 | #[rust_cc(unsafe_no_drop)] 12 | A(), 13 | } 14 | 15 | #[derive(Trace, Finalize)] 16 | enum MyEnum2 { 17 | A { 18 | #[rust_cc(unsafe_no_drop)] 19 | a: (), 20 | }, 21 | } 22 | 23 | fn main() { 24 | } 25 | -------------------------------------------------------------------------------- /derive/README.md: -------------------------------------------------------------------------------- 1 | # rust-cc-derive 2 | 3 | Derive macros for the `rust-cc` crate. 4 | 5 | ## Example Usage 6 | 7 | ```rust 8 | #[derive(Trace, Finalize)] 9 | struct A { 10 | a: Cc, 11 | #[rust_cc(ignore)] // The b field won't be traced, safe to use! 12 | b: i32, 13 | } 14 | 15 | #[derive(Trace, Finalize)] 16 | #[rust_cc(unsafe_no_drop)] // Allows to implement Drop for B, unsafe to use! (see Trace docs) 17 | struct B { 18 | // fields 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_drop_impl.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Trace)] 4 | struct MyStruct { 5 | a: (), 6 | } 7 | 8 | impl Finalize for MyStruct { 9 | } 10 | 11 | impl Drop for MyStruct { 12 | fn drop(&mut self) { 13 | } 14 | } 15 | 16 | #[derive(Trace)] 17 | enum MyEnum { 18 | A(), 19 | B(), 20 | } 21 | 22 | impl Finalize for MyEnum { 23 | } 24 | 25 | impl Drop for MyEnum { 26 | fn drop(&mut self) { 27 | } 28 | } 29 | 30 | fn main() { 31 | fn test(_t: T) { 32 | } 33 | 34 | test(MyStruct { 35 | a: (), 36 | }); 37 | test(MyEnum::A()); 38 | } 39 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_no_drop_attribute.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute position 2 | --> tests/derive_macro_tests/invalid_no_drop_attribute.rs:5:15 3 | | 4 | 5 | #[rust_cc(unsafe_no_drop)] 5 | | ^^^^^^^^^^^^^^ 6 | 7 | error: Invalid attribute position 8 | --> tests/derive_macro_tests/invalid_no_drop_attribute.rs:11:15 9 | | 10 | 11 | #[rust_cc(unsafe_no_drop)] 11 | | ^^^^^^^^^^^^^^ 12 | 13 | error: Invalid attribute position 14 | --> tests/derive_macro_tests/invalid_no_drop_attribute.rs:18:19 15 | | 16 | 18 | #[rust_cc(unsafe_no_drop)] 17 | | ^^^^^^^^^^^^^^ 18 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-cc-derive" 3 | description = "Derive macro for rust-cc" 4 | version.workspace = true 5 | authors.workspace = true 6 | readme = "README.md" 7 | repository.workspace = true 8 | categories = ["memory-management"] 9 | keywords = ["cycle-collector", "garbage-collector", "gc", "reference-counting", "macro"] 10 | license.workspace = true 11 | edition.workspace = true 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | syn = { version = "2.0", default-features = false, features = ["derive", "parsing", "printing"] } 18 | proc-macro2 = "1.0" 19 | quote = "1.0" 20 | proc-macro-error = "1.0" 21 | synstructure = "0.13.1" 22 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/no_drop.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Trace, Finalize)] // Finalize is required by Trace 4 | #[rust_cc(unsafe_no_drop)] 5 | struct MyStruct { 6 | a: (), 7 | } 8 | 9 | impl Drop for MyStruct { 10 | fn drop(&mut self) { 11 | } 12 | } 13 | 14 | #[derive(Trace, Finalize)] // Finalize is required by Trace 15 | #[rust_cc(unsafe_no_drop)] 16 | enum MyEnum { 17 | A(), 18 | B(), 19 | } 20 | 21 | impl Drop for MyEnum { 22 | fn drop(&mut self) { 23 | } 24 | } 25 | 26 | fn main() { 27 | fn test(_t: T) { 28 | } 29 | 30 | test(MyStruct { 31 | a: (), 32 | }); 33 | test(MyEnum::A()); 34 | } 35 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_attributes.rs: -------------------------------------------------------------------------------- 1 | use rust_cc::*; 2 | 3 | #[derive(Trace, Finalize)] 4 | #[rust_cc] 5 | #[rust_cc = ""] 6 | struct MyStruct1 { 7 | } 8 | 9 | #[derive(Trace, Finalize)] 10 | struct MyStruct2 { 11 | #[rust_cc] 12 | #[rust_cc = ""] 13 | a: (), 14 | } 15 | 16 | #[derive(Trace, Finalize)] 17 | #[rust_cc] 18 | #[rust_cc = ""] 19 | enum MyEnum1 { 20 | } 21 | 22 | #[derive(Trace, Finalize)] 23 | enum MyEnum3 { 24 | #[rust_cc] 25 | #[rust_cc = ""] 26 | A(#[rust_cc] #[rust_cc = ""] i32), 27 | #[rust_cc] 28 | #[rust_cc = ""] 29 | B { 30 | #[rust_cc] 31 | #[rust_cc = ""] 32 | b: i32, 33 | } 34 | } 35 | 36 | fn main() { 37 | } 38 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_drop_impl.stderr: -------------------------------------------------------------------------------- 1 | error[E0119]: conflicting implementations of trait `Drop` for type `MyStruct` 2 | --> tests/derive_macro_tests/invalid_drop_impl.rs:3:10 3 | | 4 | 3 | #[derive(Trace)] 5 | | ^^^^^ conflicting implementation for `MyStruct` 6 | ... 7 | 11 | impl Drop for MyStruct { 8 | | ---------------------- first implementation here 9 | | 10 | = note: this error originates in the derive macro `Trace` (in Nightly builds, run with -Z macro-backtrace for more info) 11 | 12 | error[E0119]: conflicting implementations of trait `Drop` for type `MyEnum` 13 | --> tests/derive_macro_tests/invalid_drop_impl.rs:16:10 14 | | 15 | 16 | #[derive(Trace)] 16 | | ^^^^^ conflicting implementation for `MyEnum` 17 | ... 18 | 25 | impl Drop for MyEnum { 19 | | -------------------- first implementation here 20 | | 21 | = note: this error originates in the derive macro `Trace` (in Nightly builds, run with -Z macro-backtrace for more info) 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 8 | 9 | jobs: 10 | on-stable: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: dtolnay/rust-toolchain@stable 15 | - uses: taiki-e/install-action@cargo-hack 16 | - name: Run tests 17 | # Keep "std" feature always enabled on stable to avoid needing the no-std related nightly features 18 | run: | 19 | cargo hack test --feature-powerset --ignore-unknown-features --workspace --skip nightly --verbose -F std 20 | on-nightly: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: dtolnay/rust-toolchain@nightly 25 | - uses: taiki-e/install-action@cargo-hack 26 | - name: Run tests 27 | run: cargo hack test --feature-powerset --ignore-unknown-features --workspace --verbose -F nightly 28 | -------------------------------------------------------------------------------- /tests/macro_tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(not(miri), feature = "derive", not(feature = "nightly")))] 2 | // No need to run under miri. Also, don't run with the nightly compiler, 3 | // since error messages might have changed, hence failing the CI 4 | 5 | #[test] 6 | fn macro_tests() { 7 | let t = trybuild::TestCases::new(); 8 | t.pass("tests/derive_macro_tests/derive_finalize.rs"); 9 | t.pass("tests/derive_macro_tests/derive_trace.rs"); 10 | t.pass("tests/derive_macro_tests/traced_fields_struct.rs"); 11 | t.pass("tests/derive_macro_tests/traced_fields_enum.rs"); 12 | t.pass("tests/derive_macro_tests/ignored_variant.rs"); 13 | t.pass("tests/derive_macro_tests/no_drop.rs"); 14 | t.pass("tests/derive_macro_tests/empty_attribute.rs"); 15 | t.compile_fail("tests/derive_macro_tests/invalid_attributes.rs"); 16 | t.compile_fail("tests/derive_macro_tests/invalid_ignore_attribute.rs"); 17 | t.compile_fail("tests/derive_macro_tests/invalid_no_drop_attribute.rs"); 18 | t.compile_fail("tests/derive_macro_tests/invalid_drop_impl.rs"); 19 | t.compile_fail("tests/derive_macro_tests/invalid_field_bounds.rs"); 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 fren_gor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/ignored_variant.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use rust_cc::*; 3 | 4 | #[derive(Finalize)] 5 | struct ToTrace { 6 | has_been_traced: Cell, 7 | } 8 | 9 | unsafe impl Trace for ToTrace { 10 | fn trace(&self, _: &mut Context<'_>) { 11 | self.has_been_traced.set(true); 12 | } 13 | } 14 | 15 | impl ToTrace { 16 | fn new() -> Cc { 17 | Cc::new(ToTrace { 18 | has_been_traced: Cell::new(false), 19 | }) 20 | } 21 | } 22 | 23 | #[derive(Trace, Finalize)] // Finalize is required by Trace 24 | enum MyEnum { 25 | #[rust_cc(ignore)] 26 | A { 27 | cyclic: RefCell>>, 28 | ignored: Cc, 29 | } 30 | } 31 | 32 | fn main() { 33 | let my_struct = Cc::new(MyEnum::A { 34 | cyclic: RefCell::new(None), 35 | ignored: ToTrace::new(), 36 | }); 37 | 38 | let MyEnum::A {cyclic, ignored} = &*my_struct; 39 | 40 | *cyclic.borrow_mut() = Some(my_struct.clone()); 41 | 42 | // Drop an instance and collect 43 | let _ = my_struct.clone(); 44 | collect_cycles(); 45 | 46 | assert!(!ignored.has_been_traced.get()); 47 | } 48 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/traced_fields_struct.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use rust_cc::*; 3 | 4 | #[derive(Finalize)] 5 | struct ToTrace { 6 | has_been_traced: Cell, 7 | } 8 | 9 | unsafe impl Trace for ToTrace { 10 | fn trace(&self, _: &mut Context<'_>) { 11 | self.has_been_traced.set(true); 12 | } 13 | } 14 | 15 | impl ToTrace { 16 | fn new() -> Cc { 17 | Cc::new(ToTrace { 18 | has_been_traced: Cell::new(false), 19 | }) 20 | } 21 | } 22 | 23 | #[derive(Trace, Finalize)] // Finalize is required by Trace 24 | struct MyStruct { 25 | cyclic: RefCell>>, 26 | traced: Cc, 27 | #[rust_cc(ignore)] 28 | ignored: Cc, 29 | } 30 | 31 | fn main() { 32 | let my_struct = Cc::new(MyStruct { 33 | cyclic: RefCell::new(None), 34 | traced: ToTrace::new(), 35 | ignored: ToTrace::new(), 36 | }); 37 | 38 | *my_struct.cyclic.borrow_mut() = Some(my_struct.clone()); 39 | 40 | // Drop an instance and collect 41 | let _ = my_struct.clone(); 42 | collect_cycles(); 43 | 44 | assert!(my_struct.traced.has_been_traced.get()); 45 | assert!(!my_struct.ignored.has_been_traced.get()); 46 | } 47 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/traced_fields_enum.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use rust_cc::*; 3 | 4 | #[derive(Finalize)] 5 | struct ToTrace { 6 | has_been_traced: Cell, 7 | } 8 | 9 | unsafe impl Trace for ToTrace { 10 | fn trace(&self, _: &mut Context<'_>) { 11 | self.has_been_traced.set(true); 12 | } 13 | } 14 | 15 | impl ToTrace { 16 | fn new() -> Cc { 17 | Cc::new(ToTrace { 18 | has_been_traced: Cell::new(false), 19 | }) 20 | } 21 | } 22 | 23 | #[derive(Trace, Finalize)] // Finalize is required by Trace 24 | enum MyEnum { 25 | A { 26 | cyclic: RefCell>>, 27 | traced: Cc, 28 | #[rust_cc(ignore)] 29 | ignored: Cc, 30 | } 31 | } 32 | 33 | fn main() { 34 | let my_struct = Cc::new(MyEnum::A { 35 | cyclic: RefCell::new(None), 36 | traced: ToTrace::new(), 37 | ignored: ToTrace::new(), 38 | }); 39 | 40 | let MyEnum::A {cyclic, traced, ignored} = &*my_struct; 41 | 42 | *cyclic.borrow_mut() = Some(my_struct.clone()); 43 | 44 | // Drop an instance and collect 45 | let _ = my_struct.clone(); 46 | collect_cycles(); 47 | 48 | assert!(traced.has_been_traced.get()); 49 | assert!(!ignored.has_been_traced.get()); 50 | } 51 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_field_bounds.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `DoesNotImplementTrace: _::rust_cc::Trace` is not satisfied 2 | --> tests/derive_macro_tests/invalid_field_bounds.rs:7:12 3 | | 4 | 7 | field: DoesNotImplementTrace, 5 | | ^^^^^^^^^^^^^^^^^^^^^ the trait `_::rust_cc::Trace` is not implemented for `DoesNotImplementTrace` 6 | | 7 | = help: the following other types implement trait `_::rust_cc::Trace`: 8 | () 9 | (A, B) 10 | (A, B, C) 11 | (A, B, C, D) 12 | (A, B, C, D, E) 13 | (A, B, C, D, E, F) 14 | (A, B, C, D, E, F, G) 15 | (A, B, C, D, E, F, G, H) 16 | and $N others 17 | 18 | error[E0277]: the trait bound `DoesNotImplementTrace: _::rust_cc::Trace` is not satisfied 19 | --> tests/derive_macro_tests/invalid_field_bounds.rs:12:7 20 | | 21 | 12 | A(DoesNotImplementTrace), 22 | | ^^^^^^^^^^^^^^^^^^^^^ the trait `_::rust_cc::Trace` is not implemented for `DoesNotImplementTrace` 23 | | 24 | = help: the following other types implement trait `_::rust_cc::Trace`: 25 | () 26 | (A, B) 27 | (A, B, C) 28 | (A, B, C, D) 29 | (A, B, C, D, E) 30 | (A, B, C, D, E, F) 31 | (A, B, C, D, E, F, G) 32 | (A, B, C, D, E, F, G, H) 33 | and $N others 34 | -------------------------------------------------------------------------------- /benches/benches/large_linked_list.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use rust_cc::*; 4 | 5 | pub fn large_linked_list(size: usize) -> Vec { 6 | let mut res = Vec::new(); 7 | for _ in 0..30 { 8 | let mut list = List::new(); 9 | for _ in 0..size { 10 | list.add(); 11 | } 12 | res.push(list.len()); 13 | } 14 | collect_cycles(); 15 | res 16 | } 17 | 18 | struct List { 19 | head: Cc, 20 | } 21 | 22 | impl List { 23 | fn new() -> List { 24 | List { 25 | head: Cc::new(Node::Nil), 26 | } 27 | } 28 | 29 | fn add(&mut self) { 30 | let cons = Cc::new(Node::Cons { 31 | next: self.head.clone(), 32 | previous: RefCell::new(None), 33 | }); 34 | if let Node::Cons { previous, .. } = &*self.head { 35 | *previous.borrow_mut() = Some(cons.clone()); 36 | } 37 | self.head = cons; 38 | if let Node::Cons { next, .. } = &*self.head { 39 | next.mark_alive(); 40 | } 41 | } 42 | 43 | fn len(&self) -> usize { 44 | self.head.len() 45 | } 46 | } 47 | 48 | #[derive(Trace, Finalize)] 49 | enum Node { 50 | Cons { next: Cc, previous: RefCell>> }, 51 | Nil, 52 | } 53 | 54 | impl Node { 55 | fn len(&self) -> usize { 56 | match self { 57 | Self::Cons { next, .. } => { 58 | next.len() + 1 59 | }, 60 | _ => 0, 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /benches/benches/binary_trees.rs: -------------------------------------------------------------------------------- 1 | //! Benchmark adapted from the shredder crate, released under MIT license. Src: https://github.com/Others/shredder/blob/266de5a3775567463ee82febc42eed1c9a8b6197/benches/shredder_benchmark.rs 2 | 3 | use rust_cc::*; 4 | 5 | // BENCHMARK 2: It's binary-trees from the benchmarks game! 6 | 7 | pub fn count_binary_trees(max_size: usize) -> Vec { 8 | let mut res = Vec::new(); 9 | { 10 | let min_size = 4; 11 | 12 | for depth in (min_size..max_size).step_by(2) { 13 | let iterations = 1 << (max_size - depth + min_size); 14 | let mut check = 0; 15 | 16 | for _ in 1..=iterations { 17 | check += Cc::new(TreeNode::new(depth)).check(); 18 | } 19 | 20 | res.push(check); 21 | } 22 | } 23 | collect_cycles(); 24 | res 25 | } 26 | 27 | #[derive(Trace, Finalize)] 28 | enum TreeNode { 29 | Nested { 30 | left: Cc, 31 | right: Cc, 32 | }, 33 | End, 34 | } 35 | 36 | impl TreeNode { 37 | fn new(depth: usize) -> Self { 38 | if depth == 0 { 39 | return Self::End; 40 | } 41 | 42 | Self::Nested { 43 | left: Cc::new(Self::new(depth - 1)), 44 | right: Cc::new(Self::new(depth - 1)), 45 | } 46 | } 47 | 48 | fn check(&self) -> usize { 49 | match self { 50 | Self::End => 1, 51 | Self::Nested { left, right } => left.check() + right.check() + 1, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /benches/benches/stress_test.rs: -------------------------------------------------------------------------------- 1 | //! Benchmark adapted from the shredder crate, released under MIT license. Src: https://github.com/Others/shredder/blob/266de5a3775567463ee82febc42eed1c9a8b6197/benches/shredder_benchmark.rs 2 | 3 | use std::cell::RefCell; 4 | use rand::rngs::StdRng; 5 | use rand::SeedableRng; 6 | use rand::seq::SliceRandom; 7 | 8 | use rust_cc::*; 9 | 10 | // BENCHMARK 1: My janky stress test 11 | // (It basically creates a graph where every node is rooted, then de-roots some nodes a few at a time) 12 | #[derive(Trace, Finalize)] 13 | struct DirectedGraphNode { 14 | _label: String, 15 | edges: Vec>>, 16 | } 17 | 18 | const NODE_COUNT: usize = 1 << 15; 19 | const EDGE_COUNT: usize = 1 << 15; 20 | const SHRINK_DIV: usize = 1 << 10; 21 | 22 | pub fn stress_test(seed: u64) -> Vec { 23 | let rng = &mut StdRng::seed_from_u64(seed); 24 | let mut res = Vec::new(); 25 | { 26 | let mut nodes = Vec::new(); 27 | 28 | for i in 0..=NODE_COUNT { 29 | nodes.push(Cc::new(RefCell::new(DirectedGraphNode { 30 | _label: format!("Node {}", i), 31 | edges: Vec::new(), 32 | }))); 33 | } 34 | 35 | for _ in 0..=EDGE_COUNT { 36 | let a = nodes.choose(rng).unwrap(); 37 | let b = nodes.choose(rng).unwrap(); 38 | 39 | a.borrow_mut().edges.push(Cc::clone(b)); 40 | } 41 | 42 | for i in 0..NODE_COUNT { 43 | if i % SHRINK_DIV == 0 { 44 | nodes.truncate(NODE_COUNT - i); 45 | collect_cycles(); 46 | res.push(nodes.len()); 47 | } 48 | } 49 | } 50 | collect_cycles(); 51 | res 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/miri.yml: -------------------------------------------------------------------------------- 1 | name: Miri 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 8 | 9 | jobs: 10 | test-with-miri-stable: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | # From Miri documentation: https://github.com/rust-lang/miri#running-miri-on-ci 15 | - name: Install Miri 16 | run: | 17 | rustup toolchain install nightly --component miri 18 | rustup override set nightly 19 | cargo miri setup 20 | - uses: taiki-e/install-action@cargo-hack 21 | # Always skip "derive" since derive macro tests are skipped on Miri 22 | # Also always keep "pedantic-debug-assertions" enabled to reduce build times 23 | # Note: no need to use --workspace here, since there's no unsafe in rust-cc-derive 24 | - name: Run tests 25 | # Keep "std" feature always enabled here to avoid needing the no-std related nightly features 26 | run: cargo hack miri test --feature-powerset --skip nightly,derive --verbose -F std,pedantic-debug-assertions 27 | test-with-miri-nightly: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v3 31 | # From Miri documentation: https://github.com/rust-lang/miri#running-miri-on-ci 32 | - name: Install Miri 33 | run: | 34 | rustup toolchain install nightly --component miri 35 | rustup override set nightly 36 | cargo miri setup 37 | - uses: taiki-e/install-action@cargo-hack 38 | # Always skip "derive" since derive macro tests are skipped on Miri 39 | # Also always keep "pedantic-debug-assertions" enabled to reduce build times 40 | # Note: no need to use --workspace here, since there's no unsafe in rust-cc-derive 41 | - name: Run tests (nightly) 42 | run: cargo hack miri test --feature-powerset --skip derive --verbose -F nightly,pedantic-debug-assertions 43 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | //! Same benchmarks of [rust-cc-benchmarks](https://github.com/frengor/rust-cc-benchmarks), but run with [iai-callgrind](https://github.com/iai-callgrind/iai-callgrind). 2 | 3 | mod benches { 4 | pub(super) mod stress_test; 5 | pub(super) mod binary_trees; 6 | pub(super) mod binary_trees_with_parent_pointers; 7 | pub(super) mod large_linked_list; 8 | } 9 | 10 | use std::hint::black_box; 11 | use iai_callgrind::{library_benchmark, library_benchmark_group, LibraryBenchmarkConfig, main}; 12 | use crate::benches::binary_trees::count_binary_trees; 13 | use crate::benches::binary_trees_with_parent_pointers::count_binary_trees_with_parent; 14 | use crate::benches::large_linked_list::large_linked_list; 15 | use crate::benches::stress_test::stress_test; 16 | 17 | #[library_benchmark] 18 | #[bench::seed(0xCAFE)] 19 | fn stress_test_bench(seed: u64) -> Vec { 20 | black_box(stress_test(seed)) 21 | } 22 | 23 | #[library_benchmark] 24 | #[bench::depth(11)] 25 | fn count_binary_trees_bench(depth: usize) -> Vec { 26 | black_box(count_binary_trees(depth)) 27 | } 28 | 29 | #[library_benchmark] 30 | #[bench::depth(11)] 31 | fn count_binary_trees_with_parent_bench(depth: usize) -> Vec { 32 | black_box(count_binary_trees_with_parent(depth)) 33 | } 34 | 35 | #[library_benchmark] 36 | #[bench::size(4096)] 37 | fn large_linked_list_bench(size: usize) -> Vec { 38 | black_box(large_linked_list(size)) 39 | } 40 | 41 | library_benchmark_group!( 42 | name = stress_tests_group; 43 | benchmarks = stress_test_bench 44 | ); 45 | 46 | library_benchmark_group!( 47 | name = binary_trees_group; 48 | benchmarks = count_binary_trees_bench, count_binary_trees_with_parent_bench 49 | ); 50 | 51 | library_benchmark_group!( 52 | name = linked_lists_group; 53 | benchmarks = large_linked_list_bench 54 | ); 55 | 56 | main!( 57 | config = LibraryBenchmarkConfig::default().raw_callgrind_args(["--branch-sim=yes"]); 58 | library_benchmark_groups = stress_tests_group, binary_trees_group, linked_lists_group 59 | ); 60 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 8 | RUSTDOCFLAGS: -D warnings 9 | 10 | jobs: 11 | on-stable: 12 | runs-on: ubuntu-latest 13 | env: 14 | RUSTFLAGS: -D warnings 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: dtolnay/rust-toolchain@stable 18 | with: 19 | components: clippy 20 | - uses: taiki-e/install-action@cargo-hack 21 | - name: Check and Clippy 22 | # Keep "std" feature always enabled on stable to avoid needing the no-std related nightly features 23 | run: | 24 | cargo hack check --all-targets --feature-powerset --ignore-unknown-features --workspace --skip nightly --clean-per-run --verbose -F std 25 | cargo hack check --all-targets --feature-powerset --ignore-unknown-features --workspace --skip nightly --clean-per-run --verbose -F std --release 26 | cargo hack clippy --all-targets --feature-powerset --ignore-unknown-features --workspace --skip nightly --clean-per-run --verbose -F std -- -D warnings 27 | on-nightly: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v3 31 | - uses: dtolnay/rust-toolchain@nightly 32 | with: 33 | components: clippy 34 | - uses: taiki-e/install-action@cargo-hack 35 | - name: Check and Clippy (nightly) 36 | run: | 37 | cargo hack check --all-targets --feature-powerset --ignore-unknown-features --workspace --clean-per-run --verbose -F nightly 38 | cargo hack clippy --all-targets --feature-powerset --ignore-unknown-features --workspace --clean-per-run --verbose -F nightly 39 | # cargo hack clippy --all-targets --feature-powerset --ignore-unknown-features --workspace --clean-per-run --verbose -F nightly -- -D warnings 40 | docs: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v3 44 | - uses: dtolnay/rust-toolchain@nightly 45 | - name: Build docs 46 | run: cargo doc --no-deps --all-features --verbose 47 | -------------------------------------------------------------------------------- /tests/derive_macro_tests/invalid_attributes.stderr: -------------------------------------------------------------------------------- 1 | error: Invalid attribute 2 | --> tests/derive_macro_tests/invalid_attributes.rs:4:3 3 | | 4 | 4 | #[rust_cc] 5 | | ^^^^^^^ 6 | 7 | error: Invalid attribute 8 | --> tests/derive_macro_tests/invalid_attributes.rs:5:3 9 | | 10 | 5 | #[rust_cc = ""] 11 | | ^^^^^^^^^^^^ 12 | 13 | error: Invalid attribute 14 | --> tests/derive_macro_tests/invalid_attributes.rs:11:7 15 | | 16 | 11 | #[rust_cc] 17 | | ^^^^^^^ 18 | 19 | error: Invalid attribute 20 | --> tests/derive_macro_tests/invalid_attributes.rs:12:7 21 | | 22 | 12 | #[rust_cc = ""] 23 | | ^^^^^^^^^^^^ 24 | 25 | error: Invalid attribute 26 | --> tests/derive_macro_tests/invalid_attributes.rs:17:3 27 | | 28 | 17 | #[rust_cc] 29 | | ^^^^^^^ 30 | 31 | error: Invalid attribute 32 | --> tests/derive_macro_tests/invalid_attributes.rs:18:3 33 | | 34 | 18 | #[rust_cc = ""] 35 | | ^^^^^^^^^^^^ 36 | 37 | error: Invalid attribute 38 | --> tests/derive_macro_tests/invalid_attributes.rs:26:9 39 | | 40 | 26 | A(#[rust_cc] #[rust_cc = ""] i32), 41 | | ^^^^^^^ 42 | 43 | error: Invalid attribute 44 | --> tests/derive_macro_tests/invalid_attributes.rs:26:20 45 | | 46 | 26 | A(#[rust_cc] #[rust_cc = ""] i32), 47 | | ^^^^^^^^^^^^ 48 | 49 | error: Invalid attribute 50 | --> tests/derive_macro_tests/invalid_attributes.rs:30:11 51 | | 52 | 30 | #[rust_cc] 53 | | ^^^^^^^ 54 | 55 | error: Invalid attribute 56 | --> tests/derive_macro_tests/invalid_attributes.rs:31:11 57 | | 58 | 31 | #[rust_cc = ""] 59 | | ^^^^^^^^^^^^ 60 | 61 | error: Invalid attribute 62 | --> tests/derive_macro_tests/invalid_attributes.rs:24:7 63 | | 64 | 24 | #[rust_cc] 65 | | ^^^^^^^ 66 | 67 | error: Invalid attribute 68 | --> tests/derive_macro_tests/invalid_attributes.rs:25:7 69 | | 70 | 25 | #[rust_cc = ""] 71 | | ^^^^^^^^^^^^ 72 | 73 | error: Invalid attribute 74 | --> tests/derive_macro_tests/invalid_attributes.rs:27:7 75 | | 76 | 27 | #[rust_cc] 77 | | ^^^^^^^ 78 | 79 | error: Invalid attribute 80 | --> tests/derive_macro_tests/invalid_attributes.rs:28:7 81 | | 82 | 28 | #[rust_cc = ""] 83 | | ^^^^^^^^^^^^ 84 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-cc" 3 | description = "A cycle collector for Rust programs" 4 | version.workspace = true 5 | authors.workspace = true 6 | readme = "README.md" 7 | repository.workspace = true 8 | categories.workspace = true 9 | keywords = ["cycle-collector", "garbage-collector", "gc", "reference-counting", "memory"] 10 | license.workspace = true 11 | edition.workspace = true 12 | 13 | [workspace] 14 | members = ["derive"] 15 | 16 | [workspace.package] 17 | version = "0.6.2" # Also update in [dependencies.rust-cc-derive.version] 18 | authors = ["fren_gor "] 19 | repository = "https://github.com/frengor/rust-cc" 20 | categories = ["memory-management", "no-std"] 21 | license = "MIT OR Apache-2.0" 22 | edition = "2021" 23 | 24 | [features] 25 | default = ["auto-collect", "finalization", "derive", "std"] 26 | 27 | # Enables support for nightly-only features 28 | nightly = [] 29 | 30 | # Enables the derive macros for the Trace and Finalize traits 31 | derive = ["dep:rust-cc-derive"] 32 | 33 | # Enables automatic executions of the collection algorithm 34 | auto-collect = [] 35 | 36 | # Enables finalization 37 | finalization = [] 38 | 39 | # Enables weak pointers 40 | weak-ptrs = [] 41 | 42 | # Enables cleaners 43 | cleaners = ["dep:slotmap", "weak-ptrs"] 44 | 45 | # Enables support for stdlib, disable for no-std support (requires ELF TLS and nightly) 46 | std = ["slotmap?/std", "thiserror/std"] 47 | 48 | # (Internal use only) Enables more debug assertions useful for debugging 49 | pedantic-debug-assertions = [] 50 | 51 | [dependencies] 52 | rust-cc-derive = { path = "./derive", version = "=0.6.2", optional = true } 53 | slotmap = { version = "1.0", optional = true } 54 | thiserror = { version = "2.0", default-features = false } 55 | 56 | [dev-dependencies] 57 | iai-callgrind = "=0.12.2" 58 | rand = "0.8.3" 59 | trybuild = "1.0.85" 60 | test-case = "3.3.1" 61 | 62 | [[bench]] 63 | name = "bench" 64 | harness = false 65 | required-features = ["std", "derive"] 66 | 67 | [profile.bench] 68 | debug = true # Required by iai-callgrind 69 | strip = false # Required by iai-callgrind 70 | 71 | [lints.rust] 72 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(doc_auto_cfg)'] } 73 | 74 | [package.metadata.docs.rs] 75 | all-features = true 76 | rustdoc-args = ["--cfg", "doc_auto_cfg", "--generate-link-to-definition"] 77 | -------------------------------------------------------------------------------- /src/weak/weak_counter_marker.rs: -------------------------------------------------------------------------------- 1 | use core::cell::Cell; 2 | use crate::utils; 3 | 4 | const ACCESSIBLE_MASK: u16 = 1u16 << (u16::BITS - 1); 5 | const COUNTER_MASK: u16 = !ACCESSIBLE_MASK; 6 | const INITIAL_VALUE: u16 = 0; 7 | const INITIAL_VALUE_ACCESSIBLE: u16 = INITIAL_VALUE | ACCESSIBLE_MASK; 8 | 9 | // pub(crate) to make it available in tests 10 | pub(crate) const MAX: u16 = !ACCESSIBLE_MASK; // First 15 bits to 1 11 | 12 | /// Internal representation: 13 | /// ```text 14 | /// +-----------+-----------+ 15 | /// | A: 1 bits | B: 15 bit | Total: 16 bits 16 | /// +-----------+-----------+ 17 | /// ``` 18 | /// 19 | /// * `A` is `1` when the `CcBox` is accessible (i.e., not deallocated), `0` otherwise 20 | /// * `B` is the weak counter 21 | #[derive(Clone, Debug)] 22 | pub(crate) struct WeakCounterMarker { 23 | weak_counter: Cell, 24 | } 25 | 26 | pub(crate) struct OverflowError; 27 | 28 | impl WeakCounterMarker { 29 | #[inline] 30 | #[must_use] 31 | pub(crate) fn new(accessible: bool) -> WeakCounterMarker { 32 | WeakCounterMarker { 33 | weak_counter: Cell::new(if accessible { 34 | INITIAL_VALUE_ACCESSIBLE 35 | } else { 36 | INITIAL_VALUE 37 | }) 38 | } 39 | } 40 | 41 | #[inline] 42 | pub(crate) fn increment_counter(&self) -> Result<(), OverflowError> { 43 | if self.counter() == MAX { 44 | utils::cold(); // This branch of the if is rarely taken 45 | Err(OverflowError) 46 | } else { 47 | self.weak_counter.set(self.weak_counter.get() + 1); 48 | Ok(()) 49 | } 50 | } 51 | 52 | #[inline] 53 | pub(crate) fn decrement_counter(&self) -> Result<(), OverflowError> { 54 | if self.counter() == 0 { 55 | utils::cold(); // This branch of the if is rarely taken 56 | Err(OverflowError) 57 | } else { 58 | self.weak_counter.set(self.weak_counter.get() - 1); 59 | Ok(()) 60 | } 61 | } 62 | 63 | #[inline] 64 | pub(crate) fn counter(&self) -> u16 { 65 | self.weak_counter.get() & COUNTER_MASK 66 | } 67 | 68 | #[inline] 69 | pub(crate) fn is_accessible(&self) -> bool { 70 | (self.weak_counter.get() & ACCESSIBLE_MASK) != 0 71 | } 72 | 73 | #[inline] 74 | pub(crate) fn set_accessible(&self, accessible: bool) { 75 | if accessible { 76 | self.weak_counter.set(self.weak_counter.get() | ACCESSIBLE_MASK); 77 | } else { 78 | self.weak_counter.set(self.weak_counter.get() & !ACCESSIBLE_MASK); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /benches/benches/binary_trees_with_parent_pointers.rs: -------------------------------------------------------------------------------- 1 | //! Benchmark adapted from the shredder crate, released under MIT license. Src: https://github.com/Others/shredder/blob/266de5a3775567463ee82febc42eed1c9a8b6197/benches/shredder_benchmark.rs 2 | 3 | use std::cell::RefCell; 4 | 5 | use rust_cc::*; 6 | 7 | // BENCHMARK 3: Same as benchmark 2, but with parent pointers. Added by rust-cc 8 | 9 | pub fn count_binary_trees_with_parent(max_size: usize) -> Vec { 10 | let mut res = Vec::new(); 11 | { 12 | let min_size = 4; 13 | 14 | for depth in (min_size..max_size).step_by(2) { 15 | let iterations = 1 << (max_size - depth + min_size); 16 | let mut check = 0; 17 | 18 | for _ in 1..=iterations { 19 | check += (TreeNodeWithParent::new(depth)).check(); 20 | } 21 | 22 | res.push(check); 23 | } 24 | } 25 | collect_cycles(); 26 | res 27 | } 28 | 29 | #[derive(Trace, Finalize)] 30 | enum TreeNodeWithParent { 31 | Root { 32 | left: Cc, 33 | right: Cc, 34 | }, 35 | Nested { 36 | parent: RefCell>>, 37 | left: Cc, 38 | right: Cc, 39 | }, 40 | End, 41 | } 42 | 43 | impl TreeNodeWithParent { 44 | fn new(depth: usize) -> Cc { 45 | if depth == 0 { 46 | return Cc::new(Self::End); 47 | } 48 | 49 | let root = Cc::new(Self::Root { 50 | left: Self::new_nested(depth - 1), 51 | right: Self::new_nested(depth - 1), 52 | }); 53 | 54 | if let Self::Root{ left, right } = &*root { 55 | if let Self::Nested { parent, ..} = &**left { 56 | *parent.borrow_mut() = Some(root.clone()); 57 | } 58 | 59 | if let Self::Nested { parent, ..} = &**right { 60 | *parent.borrow_mut() = Some(root.clone()); 61 | } 62 | } 63 | 64 | root 65 | } 66 | 67 | fn new_nested(depth: usize) -> Cc { 68 | if depth == 0 { 69 | return Cc::new(Self::End); 70 | } 71 | 72 | let cc = Cc::new(Self::Nested { 73 | left: Self::new_nested(depth - 1), 74 | right: Self::new_nested(depth - 1), 75 | parent: RefCell::new(None), 76 | }); 77 | 78 | if let Self::Nested{ left, right, .. } = &*cc { 79 | if let Self::Nested { parent, ..} = &**left { 80 | *parent.borrow_mut() = Some(cc.clone()); 81 | } 82 | 83 | if let Self::Nested { parent, ..} = &**right { 84 | *parent.borrow_mut() = Some(cc.clone()); 85 | } 86 | } 87 | 88 | cc 89 | } 90 | 91 | fn check(&self) -> usize { 92 | match self { 93 | Self::Root { left, right, .. } 94 | | Self::Nested { left, right, .. } => left.check() + right.check() + 1, 95 | Self::End => 1, 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/weak_upgrade_tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(miri))] // This test leaks memory, so it can't be run by Miri 2 | #![cfg(all(feature = "weak-ptrs", feature = "derive"))] 3 | //! This module tests that `Weak::upgrade` returns None when called in destructors while collecting. 4 | 5 | use std::cell::RefCell; 6 | use std::panic::{AssertUnwindSafe, catch_unwind}; 7 | use rust_cc::*; 8 | use rust_cc::weak::*; 9 | 10 | use test_case::test_case; 11 | 12 | struct Ignored { 13 | weak: Weak, 14 | should_panic: bool, 15 | } 16 | 17 | impl Drop for Ignored { 18 | fn drop(&mut self) { 19 | // This is safe to implement 20 | assert!(self.weak.upgrade().is_none()); 21 | 22 | assert!(!self.should_panic, "Expected panic"); 23 | } 24 | } 25 | 26 | #[test_case(false)] 27 | #[test_case(true)] 28 | fn acyclic_upgrade(should_panic: bool) { 29 | #[derive(Trace, Finalize)] 30 | struct Allocated { 31 | #[rust_cc(ignore)] 32 | _ignored: Ignored, 33 | } 34 | 35 | let cc = Cc::new_cyclic(|weak| Allocated { 36 | _ignored: Ignored { 37 | weak: weak.clone(), 38 | should_panic, 39 | }, 40 | }); 41 | 42 | let weak = cc.downgrade(); 43 | 44 | let res = catch_unwind(AssertUnwindSafe(|| { 45 | drop(cc); 46 | })); 47 | 48 | assert_eq!(should_panic, res.is_err()); 49 | 50 | assert!(weak.upgrade().is_none()); 51 | } 52 | 53 | #[test_case(false)] 54 | #[test_case(true)] 55 | fn cyclic_upgrade(should_panic: bool) { 56 | #[derive(Trace, Finalize)] 57 | struct Allocated { 58 | #[rust_cc(ignore)] 59 | _ignored: Ignored, 60 | cyclic: RefCell>>, 61 | } 62 | 63 | let cc1 = Cc::new_cyclic(|weak| Allocated { 64 | _ignored: Ignored { 65 | weak: weak.clone(), 66 | should_panic, 67 | }, 68 | cyclic: RefCell::new(None), 69 | }); 70 | let cc2 = Cc::new_cyclic(|weak| Allocated { 71 | _ignored: Ignored { 72 | weak: weak.clone(), 73 | should_panic, 74 | }, 75 | cyclic: RefCell::new(None), 76 | }); 77 | let cc3 = Cc::new_cyclic(|weak| Allocated { 78 | _ignored: Ignored { 79 | weak: weak.clone(), 80 | should_panic, 81 | }, 82 | cyclic: RefCell::new(None), 83 | }); 84 | 85 | *cc1.cyclic.borrow_mut() = Some(cc2.clone()); 86 | *cc2.cyclic.borrow_mut() = Some(cc3.clone()); 87 | *cc3.cyclic.borrow_mut() = Some(cc1.clone()); 88 | 89 | let weak1 = cc1.downgrade(); 90 | let weak2 = cc2.downgrade(); 91 | let weak3 = cc3.downgrade(); 92 | 93 | drop(cc1); 94 | drop(cc2); 95 | drop(cc3); 96 | 97 | let res = catch_unwind(AssertUnwindSafe(|| { 98 | collect_cycles(); 99 | })); 100 | 101 | assert_eq!(should_panic, res.is_err()); 102 | 103 | assert!(weak1.upgrade().is_none()); 104 | assert!(weak2.upgrade().is_none()); 105 | assert!(weak3.upgrade().is_none()); 106 | } 107 | -------------------------------------------------------------------------------- /src/derives.rs: -------------------------------------------------------------------------------- 1 | //! Derive macros documentation. 2 | 3 | /// Utility for deriving empty finalizers. 4 | /// 5 | /// See the [`Finalize`][`trait@crate::Finalize`] trait for more information. 6 | /// 7 | /// # Example 8 | /// ```rust 9 | ///# use rust_cc::*; 10 | ///# use rust_cc_derive::*; 11 | /// #[derive(Finalize)] 12 | /// struct Foo { 13 | /// // ... 14 | /// } 15 | /// ``` 16 | pub use rust_cc_derive::Finalize; 17 | 18 | /// Derive macro for safely deriving [`Trace`][`trait@crate::Trace`] implementations. 19 | /// 20 | /// The derived implementation calls the [`trace`][`method@crate::Trace::trace`] method on every field of the implementing type. 21 | /// 22 | /// # Ignoring fields 23 | /// The `#[rust_cc(ignore)]` attribute can be used to avoid tracing a field (or variant, in case of an enum). 24 | /// This may be useful, for example, if the field's type doesn't implement [`Trace`][`trait@crate::Trace`], like external library types or some types from std. 25 | /// 26 | /// Not tracing a field is *safe*, although it may lead to memory leaks if the ignored field contains any [`Cc`]. 27 | /// 28 | /// # Automatic `Drop` implementation 29 | /// This macro enforces the [`Drop`]-related safety requirements of [`Trace`][`trait@crate::Trace`] by always emitting an empty [`Drop`] 30 | /// implementation for the implementing type. 31 | /// 32 | /// The `#[rust_cc(unsafe_no_drop)]` attribute can be used to suppress the automatic [`Drop`] implementation, allowing to implement a custom one. Using this attribute 33 | /// is considered **unsafe** and **must** respect the safety requirements of [`Trace`][`trait@crate::Trace`]. 34 | /// 35 | /// Safe alternatives to `#[rust_cc(unsafe_no_drop)]` are [finalizers][`trait@crate::Finalize`] and [cleaners][`crate::cleaners`]. 36 | /// 37 | /// # Example 38 | /// ```rust 39 | ///# use rust_cc::*; 40 | ///# use rust_cc_derive::*; 41 | ///# #[derive(Finalize)] 42 | /// #[derive(Trace)] 43 | /// struct Foo { 44 | /// a_field: Cc, 45 | /// another_field: Cc, 46 | /// } 47 | /// ``` 48 | /// Ignoring a field: 49 | /// ```rust 50 | ///# use std::cell::Cell; 51 | ///# use rust_cc::*; 52 | ///# use rust_cc_derive::*; 53 | ///# #[derive(Finalize)] 54 | /// #[derive(Trace)] 55 | /// struct Foo { 56 | /// traced_field: Cc, 57 | /// #[rust_cc(ignore)] // Cell doesn't implement Trace, let's ignore it 58 | /// ignored_field: Cell, // ignored_field doesn't contain any Cc, so there will be no memory leak 59 | /// } 60 | /// 61 | ///# #[derive(Finalize)] 62 | /// #[derive(Trace)] 63 | /// enum Bar { 64 | /// #[rust_cc(ignore)] // Ignores the A variant 65 | /// A { 66 | /// // ... 67 | /// }, 68 | /// B(Cc, #[rust_cc(ignore)] Cell), // Only the Cell is ignored 69 | /// } 70 | /// ``` 71 | /// Implementing a custom [`Drop`] implementation: 72 | /// ```rust 73 | ///# use rust_cc::*; 74 | ///# use rust_cc_derive::*; 75 | ///# #[derive(Finalize)] 76 | /// #[derive(Trace)] 77 | /// #[rust_cc(unsafe_no_drop)] // UNSAFE!!! 78 | /// struct Foo { 79 | /// // ... 80 | /// } 81 | /// 82 | /// impl Drop for Foo { 83 | /// fn drop(&mut self) { 84 | /// // MUST respect the safety requirements of Trace 85 | /// } 86 | /// } 87 | /// ``` 88 | /// 89 | /// [`Cc`]: crate::Cc 90 | /// [`Drop`]: core::ops::Drop 91 | pub use rust_cc_derive::Trace; 92 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | IAI_CALLGRIND_COLOR: never 10 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 11 | 12 | jobs: 13 | bench: 14 | if: contains(github.event.pull_request.labels.*.name, 'run benchmarks') 15 | permissions: 16 | pull-requests: write 17 | env: 18 | WORKSPACE: ${{ github.workspace }} 19 | URL: ${{ github.event.pull_request.comments_url }} 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | ref: ${{ github.event.pull_request.base.ref }} 25 | - uses: dtolnay/rust-toolchain@stable 26 | - uses: taiki-e/install-action@valgrind 27 | - uses: taiki-e/install-action@cargo-binstall 28 | - name: Install iai-callgrind-runner 29 | run: | 30 | version=$(cargo metadata --format-version=1 |\ 31 | jq '.packages[] | select(.name == "iai-callgrind").version' |\ 32 | tr -d '"' 33 | ) 34 | cargo binstall --no-confirm --no-symlinks iai-callgrind-runner --version "${version}" 35 | - name: Bench base branch 36 | run: | 37 | cargo update 38 | cargo bench -q --bench bench > "${WORKSPACE}"/____old_results.txt 39 | - uses: actions/checkout@v3 40 | with: 41 | ref: ${{ github.event.pull_request.head.ref }} 42 | clean: false 43 | - name: Bench head branch 44 | run: | 45 | cargo update 46 | cargo bench -q --bench bench > "${WORKSPACE}"/____results.txt 47 | - uses: actions/checkout@v3 48 | with: 49 | ref: ${{ github.event.pull_request.base.ref }} 50 | clean: false 51 | - name: Bench base branch no finalization 52 | run: | 53 | cargo clean 54 | cargo update 55 | cargo bench --no-default-features -F std,derive,auto-collect -q --bench bench > "${WORKSPACE}"/____old_results_no_finalization.txt 56 | - uses: actions/checkout@v3 57 | with: 58 | ref: ${{ github.event.pull_request.head.ref }} 59 | clean: false 60 | - name: Bench head branch no finalization 61 | run: | 62 | cargo update 63 | cargo bench --no-default-features -F std,derive,auto-collect -q --bench bench > "${WORKSPACE}"/____results_no_finalization.txt 64 | - name: Write comment 65 | run: | 66 | { 67 | echo 'Benchmark results:' 68 | echo '' 69 | echo '```txt' 70 | cat "${WORKSPACE}"/____results.txt 71 | echo '```' 72 | echo '' 73 | echo '
Old results:

' 74 | echo '' 75 | echo '```txt' 76 | cat "${WORKSPACE}"/____old_results.txt 77 | echo '```' 78 | echo '

' 79 | echo '' 80 | echo '---' 81 | echo '' 82 | echo '
Results without finalization:

' 83 | echo '' 84 | echo '```txt' 85 | cat "${WORKSPACE}"/____results_no_finalization.txt 86 | echo '```' 87 | echo '' 88 | echo '

Old results without finalization:

' 89 | echo '' 90 | echo '```txt' 91 | cat "${WORKSPACE}"/____old_results_no_finalization.txt 92 | echo '```' 93 | echo '

' 94 | } > "${WORKSPACE}"/__result.txt 95 | - uses: thollander/actions-comment-pull-request@v2.3.1 96 | with: 97 | filePath: '__result.txt' 98 | mode: recreate 99 | - name: Remove label 100 | uses: actions-ecosystem/action-remove-labels@v1.3.0 101 | with: 102 | labels: "run benchmarks" 103 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `rust-cc` 2 | 3 | Feel free to open issues or pull requests. Feature requests can be done opening an issue, the enhancement tag will be applied by maintainers. 4 | 5 | For pull requests, use [rustftm](https://github.com/rust-lang/rustfmt) to format your files and make sure to 6 | [allow edits by maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). 7 | Also, remember to open the pull requests toward the `dev` branch. The `main` branch is only for releases! 8 | 9 | ## The collection algorithm 10 | 11 | The core idea behind the algorithm is the same as the one presented by Lins in ["Cyclic Reference Counting With Lazy Mark-Scan"](https://kar.kent.ac.uk/22347/1/CyclicLin.pdf) 12 | and by Bacon and Rajan in ["Concurrent Cycle Collection in Reference Counted Systems"](https://pages.cs.wisc.edu/~cymen/misc/interests/Bacon01Concurrent.pdf). 13 | However, the implementation differs in order to make the collector faster and more resilient to panics and failures. 14 | 15 | > [!IMPORTANT] 16 | > `rust-cc` is *not* strictly an implementation of the algorithm shown in the linked papers and it's never been 17 | > intended as such. Feel free to propose any kind of improvement! 18 | 19 | The following is a summarized version of the collection algorithm: 20 | When a `Cc` smart pointer is dropped, the reference count (RC) is decreased by 1. If it reaches 0, then the allocated 21 | object pointed by the `Cc` (called `CcBox`) is dropped, otherwise the `Cc` is put into the `POSSIBLE_CYCLES` list. 22 | The `POSSIBLE_CYCLES` is an (intrusive) list which contains the possible roots of cyclic garbage. 23 | Sometimes (see [`crate::trigger_collection`](./src/lib.rs)), when creating a new `Cc` or when `collect_cycles` is called, 24 | the objects inside the `POSSIBLE_CYCLES` list are checked to see if they are part of a garbage cycle. 25 | 26 | Therefore, they undergo two tracing phases: 27 | - **Trace Counting:** during this phase, a breadth-first traversal of the heap is performed starting from the elements inside 28 | `POSSIBLE_CYCLES` to count the number of pointers to each `CcBox` that is reachable from the list's `Cc`s. 29 | The `tracing_counter` "field" (see the [`counter_marker` module](./src/counter_marker.rs) for more info) is used to keep track of this number. 30 |
31 | About tracing_counter 32 |

In the papers, Lins, Bacon and Rajan decrement the RC itself instead of using another counter. However, if during tracing there was a panic, 33 | it would be hard for `rust-cc` to restore the RC correctly. This is the reason for the choice of having another counter. 34 | The invariant regarding this second counter is that it must always be between 0 and RC (inclusively). 35 |

36 |
37 | - **Trace Roots:** now, every `CcBox` which has the RC strictly greater than `tracing_counter` can be considered a root, 38 | since it must exist a `Cc` pointer which points at it that hasn't been traced in the previous phase. Thus, a trace is started from these roots, 39 | and all objects not reached during this trace are finalized/deallocated (this is actually more complex due to possible 40 | object resurrections, see the comments in [lib.rs](./src/lib.rs)). 41 | Note that this second phase is correct only if the graph formed by the pointers is not changed between the two phases. Thus, 42 | this is a key requirement of the `Trace` trait and one of the reasons it is marked `unsafe`. 43 | 44 | ## Using lists and queues 45 | 46 | When debug assertions are enabled, the `add` method may panic before actually updating the list to contain the added object. 47 | Similarly, the `remove` method may panic after having removed the object from the list. 48 | 49 | Thus, marking should be done only: 50 | - after the call to the `add` method 51 | - before the call to the `remove` method. 52 | 53 | ## Writing tests 54 | 55 | Every unit test (which makes use of a part of the collector) should start with a call to `tests::reset_state()` to make 56 | sure errors in other tests don't impact the current one. 57 | 58 | ## Writing documentation 59 | 60 | Docs are always built with every feature enabled. This makes it easier to write and maintain the documentation. 61 | 62 | However, this also makes it more difficult to write examples, as those must pass CI even when a features they require is disabled. 63 | As such, examples are marked as `ignore`d if a feature they need is missing: 64 | ```rust 65 | #![cfg_attr( 66 | feature = "derive", 67 | doc = r"```rust" 68 | )] 69 | #![cfg_attr( 70 | not(feature = "derive"), 71 | doc = r"```rust,ignore" 72 | )] 73 | #![doc = r"# use rust_cc::*; 74 | # use rust_cc_derive::*; 75 | 76 | // Example code 77 | 78 | ```"] 79 | ``` 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-cc 2 | [![Build status main branch](https://img.shields.io/github/check-runs/frengor/rust-cc/main?style=flat&label=main)](https://github.com/frengor/rust-cc/tree/main) 3 | [![Build status dev branch](https://img.shields.io/github/check-runs/frengor/rust-cc/dev?style=flat&label=dev)](https://github.com/frengor/rust-cc/tree/dev) 4 | [![docs.rs](https://img.shields.io/docsrs/rust-cc?style=flat)](https://docs.rs/rust-cc/latest/rust_cc/) 5 | [![Crates.io Version](https://img.shields.io/crates/v/rust-cc?style=flat&color=blue)](https://crates.io/crates/rust-cc) 6 | [![License](https://img.shields.io/crates/l/rust-cc?color=orange)](https://github.com/frengor/rust-cc#license) 7 | 8 | A fast garbage collector based on cycle collection for Rust programs. 9 | 10 | This crate provides a `Cc` (Cycle Collected) smart pointer, which is basically a `Rc` that automatically detects and 11 | deallocates reference cycles. If there are no reference cycles, then `Cc` behaves like `Rc` and deallocates 12 | immediately when the reference counter drops to zero. 13 | 14 | Currently, the cycle collector is not concurrent. As such, `Cc` doesn't implement `Send` nor `Sync`. 15 | 16 | ## Features 17 | 18 | * Fully customizable with [Cargo features](https://lib.rs/crates/rust-cc/features) 19 | * Automatic execution of collections 20 | * Finalization 21 | * Weak pointers 22 | * Cleaners 23 | * No-std support (requires ELF TLS due to thread locals) 24 | 25 | ## Basic usage example 26 | 27 | ```rust 28 | #[derive(Trace, Finalize)] 29 | struct Data { 30 | a: Cc, 31 | b: RefCell>>, 32 | } 33 | 34 | // Rc-like API 35 | let my_cc = Cc::new(Data { 36 | a: Cc::new(42), 37 | b: RefCell::new(None), 38 | }); 39 | 40 | let my_cc_2 = my_cc.clone(); 41 | let pointed: &Data = &*my_cc_2; 42 | drop(my_cc_2); 43 | 44 | // Create a cycle! 45 | *my_cc.b.borrow_mut() = Some(my_cc.clone()); 46 | 47 | // Here, the allocated Data instance doesn't get immediately deallocated, since there is a cycle. 48 | drop(my_cc); 49 | // We have to call the cycle collector 50 | collect_cycles(); 51 | // collect_cycles() is automatically called from time to time when creating new Ccs, 52 | // calling it directly only ensures that a collection is run (like at the end of the program) 53 | ``` 54 | 55 | The derive macro for the `Finalize` trait generates an empty finalizer. To write custom finalizers implement the `Finalize` trait manually: 56 | 57 | ```rust 58 | impl Finalize for Data { 59 | fn finalize(&self) { 60 | // Finalization code called when a Data object is about to be deallocated 61 | // to allow resource clean up (like closing file descriptors, etc) 62 | } 63 | } 64 | ``` 65 | 66 | For more information read [the docs](https://docs.rs/rust-cc/latest/rust_cc/). 67 | 68 | ## The collection algorithm 69 | 70 | The main idea is to discover the roots (i.e. objects which are surely not garbage) making use of 71 | the information contained in the reference counter, instead of having them from another source. 72 | 73 | Usually, in garbage collected languages, the runtime has always a way to know which objects are roots (knowing the roots allows 74 | the garbage collector to know which objects are still accessible by the program and therefore which can and cannot be deallocated). 75 | However, since Rust has no runtime, this information isn't available! For this reason, garbage collectors implemented 76 | in Rust for Rust programs have a very difficult time figuring out which objects can be deallocated and which 77 | cannot because they can still be accessed by the program. 78 | 79 | rust-cc, using the reference counters, is able to find the roots at runtime while collecting, eliminating the need to 80 | constantly keep track of them. This is also the reason why the standard `RefCell` (instead of a custom one) can be 81 | safely used inside cycle collected objects for interior mutability. 82 | 83 | Moreover, the implemented cycle collector should be generally faster than mark-and-sweep garbage collectors on big 84 | (and fragmented) heaps, since there's no need to trace the whole heap every time it runs. 85 | 86 | If you're interested in reading the source code, the algorithm is described more deeply in [CONTRIBUTING.md](./CONTRIBUTING.md#the-collection-algorithm). 87 | 88 | ## Benchmarks 89 | 90 | Benchmarks comparing rust-cc to other collectors can be found at . 91 | 92 | ## License 93 | 94 | This project is licensed under either of 95 | 96 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) 97 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 98 | 99 | at your option. 100 | 101 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, 102 | as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 103 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | use proc_macro_error::{abort_if_dirty, emit_error, proc_macro_error}; 4 | use quote::quote; 5 | use syn::{Attribute, Data, Meta, MetaList, Token}; 6 | use syn::punctuated::Punctuated; 7 | use synstructure::{AddBounds, decl_derive, Structure}; 8 | 9 | const IGNORE: &str = "ignore"; 10 | const UNSAFE_NO_DROP: &str = "unsafe_no_drop"; 11 | const ALLOWED_ATTR_META_ITEMS: [&str; 2] = [IGNORE, UNSAFE_NO_DROP]; 12 | 13 | decl_derive!([Trace, attributes(rust_cc)] => #[proc_macro_error] derive_trace_trait); 14 | 15 | fn derive_trace_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream { 16 | // Check if the struct is annotated with #[rust_cc(unsafe_no_drop)] 17 | let no_drop = s.ast().attrs 18 | .iter() 19 | .any(|attr| attr_contains(attr, UNSAFE_NO_DROP)); 20 | 21 | // Ignore every field and variant annotated with #[rust_cc(ignore)] 22 | // Filter fields before variants to be able to emit all the errors in case of wrong attributes in ignored variants 23 | s.filter(|bi| { 24 | !bi.ast().attrs 25 | .iter() 26 | .any(|attr| attr_contains(attr, IGNORE)) 27 | }); 28 | 29 | // Filter variants only in case of enums 30 | if let Data::Enum(_) = s.ast().data { 31 | s.filter_variants(|vi| { 32 | !vi.ast().attrs 33 | .iter() 34 | .any(|attr| attr_contains(attr, IGNORE)) 35 | }); 36 | } 37 | 38 | // Abort if errors has been emitted 39 | abort_if_dirty(); 40 | 41 | // Identifier for the ctx parameter of Trace::trace(...) 42 | // Shouldn't clash with any other identifier 43 | let ctx = quote::format_ident!("__rust_cc__Trace__ctx__"); 44 | 45 | // There's no .len() method, so this is the only way to know if there are any traced fields 46 | let mut has_no_variants = true; // See inline_attr below 47 | 48 | let body = s.each(|bi| { 49 | has_no_variants = false; 50 | 51 | let ty = &bi.ast().ty; 52 | quote! { 53 | <#ty as rust_cc::Trace>::trace(#bi, #ctx); 54 | } 55 | }); 56 | 57 | // Generate an #[inline(always)] if no field is being traced 58 | let inline_attr = if has_no_variants { 59 | quote! { #[inline(always)] } 60 | } else { 61 | quote! { #[inline] } 62 | }; 63 | 64 | s.underscore_const(true); 65 | 66 | s.add_bounds(AddBounds::Fields); 67 | let trace_impl = s.gen_impl(quote! { 68 | extern crate rust_cc; 69 | 70 | gen unsafe impl rust_cc::Trace for @Self { 71 | #inline_attr 72 | #[allow(non_snake_case)] 73 | fn trace(&self, #ctx: &mut rust_cc::Context<'_>) { 74 | match *self { #body } 75 | } 76 | } 77 | }); 78 | 79 | if no_drop { 80 | return trace_impl; 81 | } 82 | 83 | s.add_bounds(AddBounds::None); // Don't generate bounds for Drop 84 | let drop_impl = s.gen_impl(quote! { 85 | extern crate core; 86 | 87 | gen impl core::ops::Drop for @Self { 88 | #[inline(always)] 89 | fn drop(&mut self) { 90 | } 91 | } 92 | }); 93 | 94 | quote! { 95 | #trace_impl 96 | #drop_impl 97 | } 98 | } 99 | 100 | fn get_meta_items(attr: &Attribute) -> Option<&MetaList> { 101 | if attr.path().is_ident("rust_cc") { 102 | match &attr.meta { 103 | Meta::List(meta) => Some(meta), 104 | err => { 105 | emit_error!(err, "Invalid attribute"); 106 | None 107 | }, 108 | } 109 | } else { 110 | None 111 | } 112 | } 113 | 114 | fn attr_contains(attr: &Attribute, ident: &str) -> bool { 115 | let Some(meta_list) = get_meta_items(attr) else { 116 | return false; 117 | }; 118 | 119 | let nested = match meta_list.parse_args_with(Punctuated::::parse_terminated) { 120 | Ok(nested) => nested, 121 | Err(err) => { 122 | emit_error!(meta_list, "Invalid attribute: {}", err); 123 | return false; 124 | }, 125 | }; 126 | 127 | for meta in nested { 128 | match meta { 129 | Meta::Path(path) if path.is_ident(ident) => { 130 | return true; 131 | }, 132 | Meta::Path(path) if ALLOWED_ATTR_META_ITEMS.iter().any(|id| path.is_ident(id)) => { 133 | emit_error!(path, "Invalid attribute position"); 134 | }, 135 | Meta::Path(path) => { 136 | emit_error!(path, "Unrecognized attribute"); 137 | }, 138 | err => { 139 | emit_error!(err, "Invalid attribute"); 140 | }, 141 | } 142 | } 143 | 144 | false 145 | } 146 | 147 | decl_derive!([Finalize] => derive_finalize_trait); 148 | 149 | fn derive_finalize_trait(mut s: Structure<'_>) -> proc_macro2::TokenStream { 150 | s.underscore_const(true); 151 | s.add_bounds(AddBounds::None); // Don't generate bounds for Finalize 152 | s.gen_impl(quote! { 153 | extern crate rust_cc; 154 | 155 | gen impl rust_cc::Finalize for @Self { 156 | } 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use std::rc::Rc; 4 | use std::cell::{Cell, RefCell}; 5 | use std::ops::{Deref, DerefMut}; 6 | 7 | use crate::trace::Trace; 8 | use crate::{state, Cc, Context, Finalize, POSSIBLE_CYCLES}; 9 | use crate::state::state; 10 | 11 | mod bench_code; 12 | mod cc; 13 | mod lists; 14 | mod panicking; 15 | mod counter_marker; 16 | 17 | #[cfg(feature = "weak-ptrs")] 18 | mod weak; 19 | 20 | #[cfg(feature = "cleaners")] 21 | mod cleaners; 22 | 23 | pub(crate) fn reset_state() { 24 | POSSIBLE_CYCLES.with(|pc| pc.reset()); 25 | state::reset_state(); 26 | 27 | #[cfg(feature = "auto-collect")] 28 | { 29 | use super::config::{config, Config}; 30 | config(|config| *config = Config::default()).expect("Couldn't reset the config."); 31 | } 32 | } 33 | 34 | pub(crate) struct Droppable { 35 | inner: T, 36 | #[allow(unused)] 37 | finalize: Rc>, 38 | drop: Rc>, 39 | } 40 | 41 | impl Droppable { 42 | pub(crate) fn new(t: T) -> (Droppable, DropChecker) { 43 | let finalize = Rc::new(Cell::new(false)); 44 | let drop = Rc::new(Cell::new(false)); 45 | ( 46 | Droppable { 47 | inner: t, 48 | finalize: finalize.clone(), 49 | drop: drop.clone(), 50 | }, 51 | DropChecker { finalize, drop }, 52 | ) 53 | } 54 | } 55 | 56 | impl Deref for Droppable { 57 | type Target = T; 58 | 59 | fn deref(&self) -> &Self::Target { 60 | &self.inner 61 | } 62 | } 63 | 64 | impl DerefMut for Droppable { 65 | fn deref_mut(&mut self) -> &mut Self::Target { 66 | &mut self.inner 67 | } 68 | } 69 | 70 | unsafe impl Trace for Droppable { 71 | fn trace(&self, ctx: &mut Context<'_>) { 72 | assert_collecting(); 73 | assert_tracing(); 74 | self.inner.trace(ctx); 75 | } 76 | } 77 | 78 | impl Finalize for Droppable { 79 | fn finalize(&self) { 80 | #[cfg(not(feature = "finalization"))] 81 | panic!("Finalized called with finalization feature disabled!"); 82 | 83 | #[cfg(feature = "finalization")] 84 | { 85 | assert_finalizing(); 86 | self.finalize.set(true); 87 | } 88 | } 89 | } 90 | 91 | impl Drop for Droppable { 92 | fn drop(&mut self) { 93 | assert_dropping(); 94 | // Set arc value to true 95 | self.drop.set(true); 96 | } 97 | } 98 | 99 | pub(crate) struct DropChecker { 100 | #[allow(unused)] 101 | finalize: Rc>, 102 | drop: Rc>, 103 | } 104 | 105 | impl DropChecker { 106 | pub(crate) fn assert_finalized(&self) { 107 | #[cfg(feature = "finalization")] 108 | assert!(self.finalize.get(), "Expected finalized!"); 109 | } 110 | 111 | pub(crate) fn assert_not_finalized(&self) { 112 | #[cfg(feature = "finalization")] 113 | assert!(!self.finalize.get(), "Expected not finalized!"); 114 | } 115 | 116 | pub(crate) fn assert_dropped(&self) { 117 | assert!(self.drop.get(), "Expected dropped!"); 118 | } 119 | 120 | pub(crate) fn assert_not_dropped(&self) { 121 | assert!(!self.drop.get(), "Expected not dropped!"); 122 | } 123 | } 124 | 125 | pub(crate) fn assert_empty() { 126 | assert!(POSSIBLE_CYCLES.with(|pc| pc.is_empty())); 127 | } 128 | 129 | pub(crate) fn assert_collecting() { 130 | state(|state| { 131 | assert!(state.is_collecting()); 132 | }); 133 | } 134 | 135 | pub(crate) fn assert_tracing() { 136 | assert!(state::is_tracing().ok().unwrap()); 137 | state(|state| { 138 | assert!(state.is_tracing()); 139 | 140 | #[cfg(feature = "finalization")] 141 | assert!(!state.is_finalizing()); 142 | 143 | assert!(!state.is_dropping()); 144 | }); 145 | } 146 | 147 | #[cfg(feature = "finalization")] 148 | pub(crate) fn assert_finalizing() { 149 | assert!(!state::is_tracing().ok().unwrap()); 150 | state(|state| { 151 | assert!(!state.is_tracing()); 152 | assert!(state.is_finalizing()); 153 | assert!(!state.is_dropping()); 154 | }); 155 | } 156 | 157 | pub(crate) fn assert_dropping() { 158 | assert!(!state::is_tracing().ok().unwrap()); 159 | state(|state| { 160 | assert!(!state.is_tracing()); 161 | 162 | #[cfg(feature = "finalization")] 163 | assert!(!state.is_finalizing()); 164 | 165 | assert!(state.is_dropping()); 166 | }); 167 | } 168 | 169 | pub(crate) fn assert_state_not_collecting() { 170 | assert!(!state::is_tracing().ok().unwrap()); 171 | state(|state| { 172 | assert!(!state.is_collecting()); 173 | assert!(!state.is_tracing()); 174 | 175 | #[cfg(feature = "finalization")] 176 | assert!(!state.is_finalizing()); 177 | 178 | assert!(!state.is_dropping()); 179 | }); 180 | } 181 | 182 | #[test] 183 | fn make_sure_droppable_is_finalizable() { 184 | reset_state(); 185 | 186 | let (droppable, checker) = Droppable::new(()); 187 | 188 | { 189 | let _ = Cc::new(droppable); 190 | } 191 | 192 | #[cfg(feature = "finalization")] 193 | checker.assert_finalized(); 194 | 195 | checker.assert_dropped(); 196 | } 197 | -------------------------------------------------------------------------------- /tests/auto_collect.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "auto-collect")] 2 | 3 | use std::cell::RefCell; 4 | use std::num::NonZeroUsize; 5 | 6 | use rust_cc::{Cc, collect_cycles, Context, Finalize, Trace}; 7 | use rust_cc::config::config; 8 | use rust_cc::state::executions_count; 9 | 10 | struct Traceable { 11 | inner: RefCell>>, 12 | _big: Big, 13 | } 14 | 15 | struct Big { 16 | _array: [i64; 4096], 17 | } 18 | 19 | impl Default for Big { 20 | fn default() -> Self { 21 | Big { _array: [0; 4096] } 22 | } 23 | } 24 | 25 | unsafe impl Trace for Traceable { 26 | fn trace(&self, ctx: &mut Context<'_>) { 27 | self.inner.trace(ctx); 28 | } 29 | } 30 | 31 | impl Finalize for Traceable {} 32 | 33 | impl Traceable { 34 | fn new() -> Cc { 35 | let traceable = Cc::new(Traceable { 36 | inner: RefCell::new(None), 37 | _big: Default::default(), 38 | }); 39 | *traceable.inner.borrow_mut() = Some(Cc::new(Traceable { 40 | inner: RefCell::new(Some(traceable.clone())), 41 | _big: Default::default(), 42 | })); 43 | traceable 44 | } 45 | } 46 | 47 | #[test] 48 | fn test_auto_collect() { 49 | { 50 | let traceable = Traceable::new(); 51 | 52 | let executions_counter = executions_count().unwrap(); 53 | drop(traceable); 54 | assert_eq!(executions_counter, executions_count().unwrap(), "Collected but shouldn't have collected."); 55 | 56 | let _ = Cc::new(Traceable { 57 | inner: RefCell::new(None), 58 | _big: Default::default(), 59 | }); // Collection should be triggered by allocations 60 | assert_ne!(executions_counter, executions_count().unwrap(), "Didn't collected"); 61 | } 62 | collect_cycles(); // Make sure to don't leak test's memory 63 | } 64 | 65 | #[test] 66 | fn test_disable_auto_collect() { 67 | config(|config| config.set_auto_collect(false)).expect("Couldn't disable auto-collect"); 68 | 69 | // Always re-enable auto-collect, even with panics 70 | struct DropGuard; 71 | impl Drop for DropGuard { 72 | fn drop(&mut self) { 73 | config(|config| config.set_auto_collect(true)).expect("Couldn't re-enable auto-collect"); 74 | } 75 | } 76 | let _drop_guard = DropGuard; 77 | 78 | { 79 | let executions_counter = executions_count().unwrap(); 80 | let traceable = Traceable::new(); 81 | 82 | assert_eq!(executions_counter, executions_count().unwrap(), "Collected but shouldn't have collected."); 83 | drop(traceable); 84 | assert_eq!(executions_counter, executions_count().unwrap(), "Collected but shouldn't have collected."); 85 | 86 | let _ = Cc::new(Traceable { 87 | inner: RefCell::new(None), 88 | _big: Default::default(), 89 | }); // Collection should be triggered by allocations 90 | assert_eq!(executions_counter, executions_count().unwrap(), "Collected but shouldn't have collected."); 91 | } 92 | collect_cycles(); // Make sure to don't leak test's memory 93 | } 94 | 95 | #[test] 96 | fn test_buffered_threshold_auto_collect() { 97 | const MAX_BUFFERED_OBJS: usize = 4; 98 | 99 | // Always reset buffered objs threshold and adjustment percent, even with panics 100 | struct DropGuard(f64, Option); 101 | impl Drop for DropGuard { 102 | fn drop(&mut self) { 103 | config(|config| { 104 | config.set_adjustment_percent(self.0); 105 | config.set_buffered_objects_threshold(self.1); 106 | }).expect("Couldn't reset buffered objs threshold and adjustment percent"); 107 | } 108 | } 109 | let _drop_guard = config(|config| { 110 | let guard = DropGuard(config.adjustment_percent(), config.buffered_objects_threshold()); 111 | config.set_adjustment_percent(0.0); 112 | config.set_buffered_objects_threshold(Some(NonZeroUsize::new(3).unwrap())); 113 | guard 114 | }).expect("Couldn't set buffered objs threshold and adjustment percent"); 115 | 116 | struct Cyclic { 117 | cyclic: RefCell>>>, 118 | _t: T, 119 | } 120 | 121 | unsafe impl Trace for Cyclic { 122 | fn trace(&self, ctx: &mut Context<'_>) { 123 | self.cyclic.trace(ctx); 124 | } 125 | } 126 | 127 | impl Finalize for Cyclic { 128 | } 129 | 130 | fn new() -> Cc> { 131 | let cc = Cc::new(Cyclic { 132 | cyclic: RefCell::new(None), 133 | _t: Default::default(), 134 | }); 135 | *cc.cyclic.borrow_mut() = Some(cc.clone()); 136 | cc 137 | } 138 | 139 | // Increase bytes_threshold 140 | { 141 | let _big = new::(); 142 | collect_cycles(); 143 | } 144 | collect_cycles(); 145 | 146 | let executions_counter = executions_count().unwrap(); 147 | 148 | assert_eq!(0, rust_cc::state::buffered_objects_count().unwrap()); 149 | 150 | for _ in 0..MAX_BUFFERED_OBJS { 151 | let _ = new::<()>(); 152 | 153 | assert_eq!(executions_counter, executions_count().unwrap(), "Collected but shouldn't have collected."); 154 | } 155 | 156 | let _ = new::<()>(); 157 | 158 | assert_eq!(executions_counter + 1, executions_count().unwrap(), "Didn't collected"); 159 | collect_cycles(); // Make sure to don't leak test's memory 160 | } 161 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::{alloc, dealloc, handle_alloc_error, Layout}; 2 | use core::ptr::NonNull; 3 | 4 | use crate::{CcBox, Trace}; 5 | use crate::counter_marker::Mark; 6 | use crate::state::State; 7 | 8 | #[inline] 9 | pub(crate) unsafe fn cc_alloc(layout: Layout, state: &State) -> NonNull> { 10 | state.record_allocation(layout); 11 | match NonNull::new(alloc(layout) as *mut CcBox) { 12 | Some(ptr) => ptr, 13 | None => handle_alloc_error(layout), 14 | } 15 | } 16 | 17 | #[inline] 18 | pub(crate) unsafe fn cc_dealloc( 19 | ptr: NonNull>, 20 | layout: Layout, 21 | state: &State 22 | ) { 23 | state.record_deallocation(layout); 24 | dealloc(ptr.cast().as_ptr(), layout); 25 | } 26 | 27 | #[cfg(any(feature = "weak-ptrs", feature = "cleaners"))] 28 | #[inline] 29 | pub(crate) unsafe fn alloc_other() -> NonNull { 30 | let layout = Layout::new::(); 31 | match NonNull::new(alloc(layout) as *mut T) { 32 | Some(ptr) => ptr, 33 | None => handle_alloc_error(layout), 34 | } 35 | } 36 | 37 | #[cfg(any(feature = "weak-ptrs", feature = "cleaners"))] 38 | #[inline] 39 | pub(crate) unsafe fn dealloc_other(ptr: NonNull) { 40 | let layout = Layout::new::(); 41 | dealloc(ptr.cast().as_ptr(), layout); 42 | } 43 | 44 | #[inline(always)] 45 | #[cold] 46 | pub(crate) fn cold() {} 47 | 48 | pub(crate) struct ResetMarkDropGuard { 49 | ptr: NonNull>, 50 | } 51 | 52 | impl ResetMarkDropGuard { 53 | #[inline] 54 | pub(crate) fn new(ptr: NonNull>) -> Self { 55 | Self { ptr } 56 | } 57 | } 58 | 59 | impl Drop for ResetMarkDropGuard { 60 | #[inline] 61 | fn drop(&mut self) { 62 | unsafe { self.ptr.as_ref() }.counter_marker().mark(Mark::NonMarked); 63 | } 64 | } 65 | 66 | #[cfg(feature = "std")] 67 | pub(crate) use std::thread_local as rust_cc_thread_local; // Use the std's macro when std is enabled 68 | 69 | #[cfg(not(feature = "std"))] 70 | macro_rules! rust_cc_thread_local { 71 | // Same cases as std's thread_local macro 72 | 73 | () => {}; 74 | 75 | ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const { $init:expr }; $($rest:tt)*) => ( 76 | $crate::utils::rust_cc_thread_local!($(#[$attr])* $vis static $name: $t = const { $init }); 77 | $crate::utils::rust_cc_thread_local!($($rest)*); 78 | ); 79 | 80 | ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const { $init:expr }) => ( 81 | $crate::utils::rust_cc_thread_local!($(#[$attr])* $vis static $name: $t = $init); 82 | ); 83 | 84 | ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => ( 85 | $crate::utils::rust_cc_thread_local!($(#[$attr])* $vis static $name: $t = $init); 86 | $crate::utils::rust_cc_thread_local!($($rest)*); 87 | ); 88 | 89 | ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => ( 90 | #[allow(clippy::declare_interior_mutable_const)] 91 | const INIT: $t = $init; 92 | 93 | #[thread_local] 94 | $(#[$attr])* $vis static $name: $crate::utils::NoStdLocalKey<$t> = $crate::utils::NoStdLocalKey::new(INIT); 95 | ); 96 | } 97 | 98 | #[cfg(not(feature = "std"))] 99 | pub(crate) use { 100 | rust_cc_thread_local, // When std is not enabled, use the custom macro which uses the #[thread_local] attribute 101 | no_std_thread_locals::*, 102 | }; 103 | 104 | #[cfg(not(feature = "std"))] 105 | #[allow(dead_code)] 106 | mod no_std_thread_locals { 107 | // Implementation of LocalKey for no-std to allow to easily switch from the std's thread_local macro to rust_cc_thread_local 108 | 109 | #[non_exhaustive] 110 | #[derive(Clone, Copy, Eq, PartialEq, Debug)] 111 | pub(crate) struct AccessError; 112 | 113 | pub(crate) struct NoStdLocalKey { 114 | value: T, 115 | } 116 | 117 | impl NoStdLocalKey { 118 | #[inline(always)] 119 | pub(crate) const fn new(value: T) -> Self { 120 | NoStdLocalKey { value } 121 | } 122 | 123 | #[inline] 124 | pub(crate) fn with(&self, f: F) -> R 125 | where 126 | F: FnOnce(&T) -> R, 127 | { 128 | f(&self.value) 129 | } 130 | 131 | #[inline] 132 | pub(crate) fn try_with(&self, f: F) -> Result 133 | where 134 | F: FnOnce(&T) -> R, 135 | { 136 | Ok(f(&self.value)) 137 | } 138 | } 139 | } 140 | 141 | #[cfg(all(test, not(feature = "std")))] 142 | mod no_std_tests { 143 | use core::cell::Cell; 144 | use super::no_std_thread_locals::NoStdLocalKey; 145 | 146 | rust_cc_thread_local! { 147 | static VAL: Cell = Cell::new(3); 148 | } 149 | 150 | #[test] 151 | fn check_type() { 152 | // Make sure we're using the right macro 153 | fn a(_: &NoStdLocalKey>) {} 154 | a(&VAL); 155 | } 156 | 157 | #[test] 158 | fn test_with() { 159 | let i = VAL.with(|i| { 160 | i.get() 161 | }); 162 | assert_eq!(3, i); 163 | let i = VAL.with(|i| { 164 | i.set(i.get() + 1); 165 | i.get() 166 | }); 167 | assert_eq!(4, i); 168 | } 169 | 170 | #[test] 171 | fn test_try_with() { 172 | let i = VAL.try_with(|i| { 173 | i.get() 174 | }).unwrap(); 175 | assert_eq!(3, i); 176 | let i = VAL.try_with(|i| { 177 | i.set(i.get() + 1); 178 | i.get() 179 | }).unwrap(); 180 | assert_eq!(4, i); 181 | } 182 | 183 | #[test] 184 | fn test_with_nested() { 185 | let i = VAL.with(|i| { 186 | i.set(VAL.with(|ii| ii.get()) + 1); 187 | i.get() 188 | }); 189 | assert_eq!(4, i); 190 | } 191 | 192 | #[test] 193 | fn test_try_with_nested() { 194 | let i = VAL.try_with(|i| { 195 | i.set(VAL.try_with(|ii| ii.get()).unwrap() + 1); 196 | i.get() 197 | }).unwrap(); 198 | assert_eq!(4, i); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/tests/cleaners/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::rc::Rc; 3 | use crate::{Cc, collect_cycles, Context, Finalize, Trace}; 4 | use super::reset_state; 5 | use crate::cleaners::{Cleanable, Cleaner}; 6 | 7 | #[cfg(not(miri))] // Used by tests run only when not on miri 8 | use std::panic::{AssertUnwindSafe, catch_unwind}; 9 | 10 | #[test] 11 | fn clean_after_drop() { 12 | reset_state(); 13 | 14 | struct ToClean { 15 | cleaner: Cleaner, 16 | } 17 | 18 | unsafe impl Trace for ToClean { 19 | fn trace(&self, ctx: &mut Context<'_>) { 20 | self.cleaner.trace(ctx); 21 | } 22 | } 23 | 24 | impl Finalize for ToClean {} 25 | 26 | let to_clean = Cc::new(ToClean { 27 | cleaner: Cleaner::new(), 28 | }); 29 | 30 | let already_cleaned = Rc::new(Cell::new(false)); 31 | 32 | let cleaner = register_cleaner(&already_cleaned, &to_clean.cleaner); 33 | 34 | assert!(!already_cleaned.get()); 35 | 36 | drop(to_clean); // Should call the clean function 37 | 38 | assert!(already_cleaned.get()); 39 | 40 | cleaner.clean(); // This should be a no-op 41 | 42 | assert!(already_cleaned.get()); 43 | } 44 | 45 | #[test] 46 | fn clean_before_drop() { 47 | reset_state(); 48 | 49 | struct ToClean { 50 | cleaner: Cleaner, 51 | } 52 | 53 | unsafe impl Trace for ToClean { 54 | fn trace(&self, ctx: &mut Context<'_>) { 55 | self.cleaner.trace(ctx); 56 | } 57 | } 58 | 59 | impl Finalize for ToClean {} 60 | 61 | let to_clean = Cc::new(ToClean { 62 | cleaner: Cleaner::new(), 63 | }); 64 | 65 | let already_cleaned = Rc::new(Cell::new(false)); 66 | 67 | let cleaner = register_cleaner(&already_cleaned, &to_clean.cleaner); 68 | 69 | assert!(!already_cleaned.get()); 70 | 71 | cleaner.clean(); // Clean immediately after 72 | 73 | assert!(already_cleaned.get()); 74 | 75 | drop(to_clean); // Should call the clean function 76 | 77 | assert!(already_cleaned.get()); 78 | } 79 | 80 | fn register_cleaner(already_cleaned: &Rc>, cleaner: &Cleaner) -> Cleanable { 81 | assert!(!cleaner.has_allocated()); 82 | 83 | let already_cleaned = already_cleaned.clone(); 84 | let cleanable = cleaner.register(move || { 85 | assert!(!already_cleaned.get(), "Already cleaned!"); 86 | already_cleaned.set(true); 87 | }); 88 | 89 | assert!(cleaner.has_allocated()); 90 | 91 | cleanable 92 | } 93 | 94 | #[test] 95 | fn clean_with_cyclic_cc() { 96 | reset_state(); 97 | 98 | struct ToClean { 99 | cleaner: Cleaner, 100 | } 101 | 102 | unsafe impl Trace for ToClean { 103 | fn trace(&self, ctx: &mut Context<'_>) { 104 | self.cleaner.trace(ctx); 105 | } 106 | } 107 | 108 | impl Finalize for ToClean {} 109 | 110 | let to_clean = Cc::new(ToClean { 111 | cleaner: Cleaner::new(), 112 | }); 113 | 114 | let already_cleaned = Rc::new(Cell::new(false)); 115 | 116 | assert!(!to_clean.cleaner.has_allocated()); 117 | 118 | let cleaner = { 119 | let cloned = to_clean.clone(); 120 | let cloned_ac = already_cleaned.clone(); 121 | to_clean.cleaner.register(move || { 122 | let _cc = cloned; // Move to_clone inside the closure 123 | assert!(!cloned_ac.get(), "Already cleaned!"); 124 | cloned_ac.set(true); 125 | }) 126 | }; 127 | 128 | assert!(to_clean.cleaner.has_allocated()); 129 | 130 | assert!(!already_cleaned.get()); 131 | 132 | drop(to_clean); 133 | 134 | assert!(!already_cleaned.get()); 135 | 136 | collect_cycles(); 137 | 138 | assert!(!already_cleaned.get()); 139 | 140 | cleaner.clean(); 141 | 142 | assert!(already_cleaned.get()); 143 | } 144 | 145 | #[cfg(not(miri))] // Don't run on Miri due to leaks 146 | #[test] 147 | fn simple_panic_on_clean() { 148 | reset_state(); 149 | 150 | let cleaner = Cleaner::new(); 151 | 152 | let already_cleaned = Rc::new(Cell::new(false)); 153 | 154 | let already_cleaned_clone = already_cleaned.clone(); 155 | let cleanable = cleaner.register(move || { 156 | already_cleaned_clone.set(true); 157 | panic!("Panic inside registered cleaner!"); 158 | }); 159 | 160 | assert!(catch_unwind(AssertUnwindSafe(|| { 161 | cleanable.clean(); 162 | })).is_err()); 163 | 164 | assert!(already_cleaned.get()); 165 | } 166 | 167 | #[cfg(not(miri))] // Don't run on Miri due to leaks 168 | #[test] 169 | fn simple_panic_on_cleaner_drop() { 170 | reset_state(); 171 | 172 | let cleaner = Cleaner::new(); 173 | 174 | let already_cleaned = Rc::new(Cell::new(false)); 175 | 176 | let already_cleaned_clone = already_cleaned.clone(); 177 | cleaner.register(move || { 178 | already_cleaned_clone.set(true); 179 | panic!("Panic inside registered cleaner!"); 180 | }); 181 | 182 | assert!(catch_unwind(AssertUnwindSafe(|| { 183 | drop(cleaner); 184 | })).is_err()); 185 | 186 | assert!(already_cleaned.get()); 187 | } 188 | 189 | #[cfg(not(miri))] // Don't run on Miri due to leaks 190 | #[test] 191 | fn panic_on_clean() { 192 | reset_state(); 193 | 194 | struct ToClean { 195 | cleaner: Cleaner, 196 | } 197 | 198 | unsafe impl Trace for ToClean { 199 | fn trace(&self, ctx: &mut Context<'_>) { 200 | self.cleaner.trace(ctx); 201 | } 202 | } 203 | 204 | impl Finalize for ToClean {} 205 | 206 | let to_clean = Cc::new(ToClean { 207 | cleaner: Cleaner::new(), 208 | }); 209 | 210 | let already_cleaned = Rc::new(Cell::new(false)); 211 | 212 | let already_cleaned_clone = already_cleaned.clone(); 213 | to_clean.cleaner.register(move || { 214 | already_cleaned_clone.set(true); 215 | panic!("Panic inside registered cleaner!"); 216 | }); 217 | 218 | assert!(catch_unwind(AssertUnwindSafe(|| { 219 | drop(to_clean); 220 | })).is_err()); 221 | 222 | assert!(already_cleaned.get()); 223 | } 224 | 225 | #[test] 226 | fn clean_multiple_times() { 227 | reset_state(); 228 | 229 | let rc = Rc::new(Cell::new(false)); 230 | 231 | let cleaner = Cleaner::new(); 232 | 233 | let cleanable = cleaner.register({ 234 | let rc = rc.clone(); 235 | move || { 236 | assert!(!rc.replace(true)); 237 | } 238 | }); 239 | 240 | cleanable.clean(); 241 | 242 | assert!(rc.get()); 243 | 244 | cleanable.clean(); 245 | 246 | assert!(rc.get()); 247 | } 248 | -------------------------------------------------------------------------------- /src/cleaners/mod.rs: -------------------------------------------------------------------------------- 1 | //! Execute cleaning actions on object destruction. 2 | //! 3 | //! [`Cleaner`]s can be used to [`register`][`Cleaner::register`] cleaning actions, 4 | //! which are executed when the [`Cleaner`] in which they're registered is dropped. 5 | //! 6 | //! Adding a [`Cleaner`] field to a struct makes it possible to execute cleaning actions on object destruction. 7 | //! 8 | //! # Cleaning actions 9 | //! 10 | //! A cleaning action is provided as a closure to the [`register`][`Cleaner::register`] method, which returns 11 | //! a [`Cleanable`] that can be used to manually run the action. 12 | //! 13 | //! Every cleaning action is executed at maximum once. Thus, any manually-run action will not be executed 14 | //! when their [`Cleaner`] is dropped. The same also applies to cleaning actions run manually after the [`Cleaner`] 15 | //! in which they were registered is dropped, as they have already been executed. 16 | //! 17 | //! # Avoiding memory leaks 18 | //! 19 | //! Usually, [`Cleaner`]s are stored inside a cycle-collected object. Make sure to **never capture** a reference to the container object 20 | //! inside the cleaning action closure, otherwise the object will be leaked and the cleaning action will never be executed. 21 | //! 22 | //! # Cleaners vs finalization 23 | //! 24 | //! [`Cleaner`]s provide a faster alternative to [`finalization`][`crate::Finalize`]. 25 | //! As such, *when possible* it's suggested to prefer cleaners and disable finalization. 26 | 27 | use alloc::boxed::Box; 28 | use core::fmt::{self, Debug, Formatter}; 29 | use core::cell::{RefCell, UnsafeCell}; 30 | use slotmap::{new_key_type, SlotMap}; 31 | 32 | use crate::{Cc, Context, Finalize, Trace}; 33 | use crate::weak::Weak; 34 | 35 | new_key_type! { 36 | struct CleanerKey; 37 | } 38 | 39 | struct CleanerMap { 40 | map: RefCell>, 41 | } 42 | 43 | unsafe impl Trace for CleanerMap { 44 | #[inline(always)] 45 | fn trace(&self, _: &mut Context<'_>) { 46 | } 47 | } 48 | 49 | impl Finalize for CleanerMap {} 50 | 51 | struct CleaningAction(Option>); 52 | 53 | impl Drop for CleaningAction { 54 | #[inline] 55 | fn drop(&mut self) { 56 | if let Some(fun) = self.0.take() { 57 | fun(); 58 | } 59 | } 60 | } 61 | 62 | /// A type capable of [`register`][`Cleaner::register`]ing cleaning actions. 63 | /// 64 | /// All the cleaning actions registered in a `Cleaner` are run when it is dropped, unless they have been manually executed before. 65 | pub struct Cleaner { 66 | cleaner_map: UnsafeCell>>, // The Option is used to avoid allocating until a cleaning action is registered 67 | } 68 | 69 | impl Cleaner { 70 | /// Creates a new [`Cleaner`]. 71 | #[inline] 72 | pub fn new() -> Cleaner { 73 | Cleaner { 74 | cleaner_map: UnsafeCell::new(None), 75 | } 76 | } 77 | 78 | /// Registers a new cleaning action inside a [`Cleaner`]. 79 | /// 80 | /// This method returns a [`Cleanable`], which can be used to manually run the cleaning action. 81 | /// 82 | /// # Avoiding memory leaks 83 | /// Usually, [`Cleaner`]s are stored inside a cycle-collected object. Make sure to **never capture** 84 | /// a reference to the container object inside the `action` closure, otherwise the object will 85 | /// be leaked and the cleaning action will never be executed. 86 | #[inline] 87 | pub fn register(&self, action: impl FnOnce() + 'static) -> Cleanable { 88 | let cc = { 89 | // SAFETY: no reference to the Option already exists 90 | let map = unsafe { &mut *self.cleaner_map.get() }; 91 | 92 | map.get_or_insert_with(|| Cc::new(CleanerMap { 93 | map: RefCell::new(SlotMap::with_capacity_and_key(3)), 94 | })) 95 | }; 96 | 97 | let map_key = cc.map.borrow_mut().insert(CleaningAction(Some(Box::new(action)))); 98 | 99 | Cleanable { 100 | cleaner_map: cc.downgrade(), 101 | key: map_key, 102 | } 103 | } 104 | 105 | #[cfg(all(test, feature = "std"))] // Only used in unit tests 106 | pub(crate) fn has_allocated(&self) -> bool { 107 | // SAFETY: no reference to the Option already exists 108 | unsafe { (*self.cleaner_map.get()).is_some() } 109 | } 110 | } 111 | 112 | unsafe impl Trace for Cleaner { 113 | #[inline(always)] 114 | fn trace(&self, _: &mut Context<'_>) { 115 | // DO NOT TRACE self.cleaner_map, it would be unsound! 116 | // If self.cleaner_map would be traced here, it would be possible to have cleaning actions called 117 | // with a reference to the cleaned object accessible from inside the cleaning action itself. 118 | // This would be unsound, since cleaning actions are called from the Drop implementation of Ccs (see the Trace trait safety section) 119 | } 120 | } 121 | 122 | impl Finalize for Cleaner {} 123 | 124 | impl Default for Cleaner { 125 | #[inline] 126 | fn default() -> Self { 127 | Cleaner::new() 128 | } 129 | } 130 | 131 | impl Debug for Cleaner { 132 | #[inline] 133 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 134 | f.debug_struct("Cleaner").finish_non_exhaustive() 135 | } 136 | } 137 | 138 | /// A `Cleanable` represents a cleaning action registered in a [`Cleaner`]. 139 | pub struct Cleanable { 140 | cleaner_map: Weak, 141 | key: CleanerKey, 142 | } 143 | 144 | impl Cleanable { 145 | /// Executes the cleaning action manually. 146 | /// 147 | /// As cleaning actions are never run twice, if it has already been executed then this method will not run it again. 148 | #[inline] 149 | pub fn clean(&self) { 150 | // Try upgrading to see if the CleanerMap hasn't been deallocated 151 | let Some(cc) = self.cleaner_map.upgrade() else { return }; 152 | 153 | // Just return in case try_borrow_mut fails 154 | let Ok(mut map) = cc.map.try_borrow_mut() else { 155 | crate::utils::cold(); // Should never happen 156 | return; 157 | }; 158 | let _ = map.remove(self.key); 159 | } 160 | } 161 | 162 | unsafe impl Trace for Cleanable { 163 | #[inline(always)] 164 | fn trace(&self, _: &mut Context<'_>) { 165 | } 166 | } 167 | 168 | impl Finalize for Cleanable {} 169 | 170 | impl Debug for Cleanable { 171 | #[inline] 172 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 173 | f.debug_struct("Cleanable").finish_non_exhaustive() 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/tests/bench_code/mod.rs: -------------------------------------------------------------------------------- 1 | //! Code of an old benchmark, modified and kept for testing 2 | 3 | use std::cell::RefCell; 4 | 5 | use crate::tests::reset_state; 6 | use crate::{collect_cycles, Cc, Context, Trace}; 7 | 8 | thread_local! { 9 | static FREED_LIST: RefCell = RefCell::new(String::with_capacity(10)); 10 | } 11 | 12 | #[test] 13 | fn test() { 14 | reset_state(); 15 | 16 | FREED_LIST.with(|str| str.borrow_mut().clear()); 17 | one(); 18 | FREED_LIST.with(|str| { 19 | let mut str = str.borrow_mut(); 20 | let Some((first, second)) = str.split_once('-') else { 21 | panic!("String doesn't contains a -."); 22 | }; 23 | assert_eq!(first.len(), 2); 24 | assert_eq!(second.len(), 3); 25 | assert!(first.contains('C')); 26 | assert!(first.contains('D')); 27 | assert!(second.contains('A')); 28 | assert!(second.contains('B')); 29 | assert!(second.contains('E')); 30 | 31 | str.clear(); 32 | }); 33 | 34 | reset_state(); 35 | 36 | #[cfg(feature = "finalization")] 37 | { 38 | two(); 39 | FREED_LIST.with(|str| { 40 | let mut str = str.borrow_mut(); 41 | let Some((first, second)) = str.split_once('-') else { 42 | panic!("String doesn't contains a -."); 43 | }; 44 | assert_eq!(first.len(), 0); 45 | let Some((second, third)) = second.split_once('-') else { 46 | panic!("String doesn't contains a second -."); 47 | }; 48 | assert_eq!(second.len(), 5); 49 | assert_eq!(third.len(), 1); 50 | assert!(second.contains('A')); 51 | assert!(second.contains('B')); 52 | assert!(second.contains('C')); 53 | assert!(second.contains('D')); 54 | assert!(second.contains('E')); 55 | assert!(third.contains('D')); 56 | str.clear(); 57 | }); 58 | } 59 | } 60 | 61 | fn one() { 62 | GLOBAL_CC.with(|global| { 63 | // Don't make ::finalize move D into GLOBAL_CC! 64 | // That is tested in function two() 65 | let _unused = global.borrow_mut(); 66 | 67 | { 68 | let root1 = build(); 69 | collect_cycles(); 70 | let _root2 = root1.clone(); 71 | collect_cycles(); 72 | *root1.c.borrow_mut() = None; 73 | collect_cycles(); 74 | } 75 | FREED_LIST.with(|str| { 76 | str.borrow_mut().push('-'); 77 | }); 78 | collect_cycles(); 79 | }); 80 | } 81 | 82 | thread_local! { 83 | static GLOBAL_CC: RefCell>> = RefCell::new(None); 84 | } 85 | 86 | #[cfg(feature = "finalization")] 87 | fn two() { 88 | { 89 | let root1 = build(); 90 | collect_cycles(); 91 | let _root2 = root1.clone(); 92 | collect_cycles(); 93 | *root1.b.borrow_mut() = None; 94 | collect_cycles(); 95 | } 96 | FREED_LIST.with(|str| { 97 | str.borrow_mut().push('-'); 98 | collect_cycles(); 99 | }); 100 | FREED_LIST.with(|str| { 101 | str.borrow_mut().push('-'); 102 | }); 103 | GLOBAL_CC.with(|global| { 104 | // Drop the Cc, if present 105 | // Note that this shouldn't require to call collect_cycles() 106 | if let Some(mut cc) = global.take() { 107 | assert!(cc.already_finalized()); 108 | cc.finalize_again(); 109 | } 110 | }); 111 | } 112 | 113 | fn build() -> Cc
{ 114 | let root1 = Cc::new(A { 115 | c: RefCell::new(Some(Cc::new(C { 116 | d: RefCell::new(Some(Cc::new(D { _value: 0xD }))), 117 | b: Cc::new(B { 118 | e: Cc::new(E { _value: 0xE }), 119 | b: RefCell::new(None), 120 | a: RefCell::new(None), 121 | }), 122 | }))), 123 | b: RefCell::new(None), 124 | }); 125 | if let Some(ref c) = *root1.c.borrow_mut() { 126 | *root1.b.borrow_mut() = Some(c.b.clone()); 127 | *c.b.b.borrow_mut() = Some(c.b.clone()); 128 | *c.b.a.borrow_mut() = Some(root1.clone()); 129 | } 130 | root1 131 | } 132 | 133 | struct A { 134 | c: RefCell>>, 135 | b: RefCell>>, 136 | } 137 | struct B { 138 | e: Cc, 139 | b: RefCell>>, 140 | a: RefCell>>, 141 | } 142 | struct C { 143 | d: RefCell>>, 144 | b: Cc, 145 | } 146 | struct D { 147 | _value: u64, 148 | } 149 | struct E { 150 | _value: i64, 151 | } 152 | 153 | unsafe impl Trace for A { 154 | fn trace(&self, ctx: &mut Context<'_>) { 155 | if let Some(ref c) = *self.c.borrow() { 156 | c.trace(ctx); 157 | } 158 | if let Some(ref b) = *self.b.borrow() { 159 | b.trace(ctx); 160 | } 161 | } 162 | } 163 | 164 | unsafe impl Trace for B { 165 | fn trace(&self, ctx: &mut Context<'_>) { 166 | self.e.trace(ctx); 167 | if let Some(ref b) = *self.b.borrow() { 168 | b.trace(ctx); 169 | } 170 | if let Some(ref a) = *self.a.borrow() { 171 | a.trace(ctx); 172 | } 173 | } 174 | } 175 | 176 | unsafe impl Trace for C { 177 | fn trace(&self, ctx: &mut Context<'_>) { 178 | if let Some(d) = &*self.d.borrow() { 179 | d.trace(ctx); 180 | } 181 | self.b.trace(ctx); 182 | } 183 | } 184 | 185 | unsafe impl Trace for D { 186 | fn trace(&self, _: &mut Context<'_>) {} 187 | } 188 | 189 | unsafe impl Trace for E { 190 | fn trace(&self, _: &mut Context<'_>) {} 191 | } 192 | 193 | macro_rules! finalize_or_drop { 194 | (impl Finalize/Drop for $id:ident { fn finalize/drop(&$selfId:ident) $body:block }) => { 195 | #[cfg(feature = "finalization")] 196 | impl $crate::Finalize for $id { 197 | fn finalize(&$selfId) { 198 | $body 199 | } 200 | } 201 | 202 | #[cfg(not(feature = "finalization"))] 203 | impl $crate::Finalize for $id { 204 | fn finalize(&$selfId) {} 205 | } 206 | 207 | #[cfg(not(feature = "finalization"))] 208 | impl ::std::ops::Drop for $id { 209 | fn drop(&mut $selfId) { 210 | $body 211 | } 212 | } 213 | }; 214 | } 215 | 216 | finalize_or_drop! { 217 | impl Finalize/Drop for A { 218 | fn finalize/drop(&self) { 219 | FREED_LIST.with(|str| { 220 | str.borrow_mut().push('A'); 221 | }); 222 | } 223 | } 224 | } 225 | 226 | finalize_or_drop! { 227 | impl Finalize/Drop for B { 228 | fn finalize/drop(&self) { 229 | FREED_LIST.with(|str| { 230 | str.borrow_mut().push('B'); 231 | }); 232 | } 233 | } 234 | } 235 | 236 | finalize_or_drop! { 237 | impl Finalize/Drop for C { 238 | fn finalize/drop(&self) { 239 | FREED_LIST.with(|str| { 240 | str.borrow_mut().push('C'); 241 | }); 242 | 243 | #[cfg(feature = "finalization")] 244 | GLOBAL_CC.with(|global| { 245 | if let Ok(mut global) = global.try_borrow_mut() { 246 | *global = self.d.take(); 247 | } 248 | }); 249 | } 250 | } 251 | } 252 | 253 | finalize_or_drop! { 254 | impl Finalize/Drop for D { 255 | fn finalize/drop(&self) { 256 | FREED_LIST.with(|str| { 257 | str.borrow_mut().push('D'); 258 | }); 259 | } 260 | } 261 | } 262 | 263 | finalize_or_drop! { 264 | impl Finalize/Drop for E { 265 | fn finalize/drop(&self) { 266 | FREED_LIST.with(|str| { 267 | str.borrow_mut().push('E'); 268 | }); 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/counter_marker.rs: -------------------------------------------------------------------------------- 1 | use core::cell::Cell; 2 | 3 | use crate::utils; 4 | 5 | const NON_MARKED: u16 = 0u16; 6 | const IN_POSSIBLE_CYCLES: u16 = 1u16 << (u16::BITS - 2); 7 | const IN_LIST: u16 = 2u16 << (u16::BITS - 2); 8 | const IN_QUEUE: u16 = 3u16 << (u16::BITS - 2); 9 | 10 | const COUNTER_MASK: u16 = 0b11111111111111u16; // First 14 bits set to 1 11 | const FIRST_BIT_MASK: u16 = 1u16 << (u16::BITS - 1); 12 | const FINALIZED_MASK: u16 = 1u16 << (u16::BITS - 2); 13 | const BITS_MASK: u16 = !COUNTER_MASK; 14 | 15 | const INITIAL_VALUE: u16 = 1u16; 16 | const INITIAL_VALUE_TRACING_COUNTER: u16 = INITIAL_VALUE | NON_MARKED; 17 | const INITIAL_VALUE_FINALIZED: u16 = INITIAL_VALUE | FINALIZED_MASK; 18 | 19 | // pub(crate) to make it available in tests 20 | pub(crate) const MAX: u16 = COUNTER_MASK - 1; 21 | 22 | /// Internal representation: 23 | /// ```text 24 | /// +-----------+------------+ +----------+----------+------------+ 25 | /// | A: 2 bits | B: 14 bits | | C: 1 bit | D: 1 bit | E: 14 bits | Total: 32 bits (16 + 16) 26 | /// +-----------+------------+ +----------+----------+------------+ 27 | /// ``` 28 | /// 29 | /// * `A` has 4 possible states: 30 | /// * `NON_MARKED` 31 | /// * `IN_POSSIBLE_CYCLES`: in `possible_cycles` list (implies `NON_MARKED`) 32 | /// * `IN_LIST`: in `root_list` or `non_root_list` 33 | /// * `IN_QUEUE`: in queue to be traced 34 | /// * `B` is the tracing counter. The max value (the one with every bit set to 1) is reserved 35 | /// and indicates that the allocated value has already been dropped (but not yet deallocated) 36 | /// * `C` is `1` when metadata has been allocated, `0` otherwise 37 | /// * `D` is `1` when the element inside `CcBox` has already been finalized, `0` otherwise 38 | /// * `E` is the reference counter. The max value (the one with every bit set to 1) is reserved and should not be used 39 | #[derive(Clone, Debug)] 40 | pub(crate) struct CounterMarker { 41 | tracing_counter: Cell, 42 | counter: Cell, 43 | } 44 | 45 | pub(crate) struct OverflowError; 46 | 47 | impl CounterMarker { 48 | #[inline] 49 | #[must_use] 50 | pub(crate) fn new_with_counter_to_one(already_finalized: bool) -> CounterMarker { 51 | CounterMarker { 52 | tracing_counter: Cell::new(INITIAL_VALUE_TRACING_COUNTER), 53 | counter: Cell::new(if !already_finalized { 54 | INITIAL_VALUE 55 | } else { 56 | INITIAL_VALUE_FINALIZED 57 | }), 58 | } 59 | } 60 | 61 | #[inline] 62 | pub(crate) fn increment_counter(&self) -> Result<(), OverflowError> { 63 | debug_assert!(self.counter() != COUNTER_MASK); // Check for reserved value 64 | 65 | if self.counter() == MAX { 66 | utils::cold(); // This branch of the if is rarely taken 67 | Err(OverflowError) 68 | } else { 69 | self.counter.set(self.counter.get() + 1); 70 | Ok(()) 71 | } 72 | } 73 | 74 | #[inline] 75 | pub(crate) fn decrement_counter(&self) -> Result<(), OverflowError> { 76 | debug_assert!(self.counter() != COUNTER_MASK); // Check for reserved value 77 | 78 | if self.counter() == 0 { 79 | utils::cold(); // This branch of the if is rarely taken 80 | Err(OverflowError) 81 | } else { 82 | self.counter.set(self.counter.get() - 1); 83 | Ok(()) 84 | } 85 | } 86 | 87 | #[inline] 88 | pub(crate) fn increment_tracing_counter(&self) -> Result<(), OverflowError> { 89 | debug_assert!(self.tracing_counter() != COUNTER_MASK); // Check for reserved value 90 | 91 | if self.tracing_counter() == MAX { 92 | utils::cold(); // This branch of the if is rarely taken 93 | Err(OverflowError) 94 | } else { 95 | // Increment trace_counter and not counter 96 | self.tracing_counter.set(self.tracing_counter.get() + 1); 97 | Ok(()) 98 | } 99 | } 100 | 101 | #[inline] 102 | pub(crate) fn _decrement_tracing_counter(&self) -> Result<(), OverflowError> { 103 | debug_assert!(self.tracing_counter() != COUNTER_MASK); // Check for reserved value 104 | 105 | if self.tracing_counter() == 0 { 106 | utils::cold(); // This branch of the if is rarely taken 107 | Err(OverflowError) 108 | } else { 109 | // Decrement trace_counter and not counter 110 | self.tracing_counter.set(self.tracing_counter.get() - 1); 111 | Ok(()) 112 | } 113 | } 114 | 115 | #[inline] 116 | pub(crate) fn counter(&self) -> u16 { 117 | let rc = self.counter.get() & COUNTER_MASK; 118 | debug_assert!(rc != COUNTER_MASK); // Check for reserved value 119 | rc 120 | } 121 | 122 | #[inline] 123 | pub(crate) fn tracing_counter(&self) -> u16 { 124 | let tc = self.tracing_counter.get() & COUNTER_MASK; 125 | debug_assert!(tc != COUNTER_MASK); // Check for reserved value 126 | tc 127 | } 128 | 129 | #[inline] 130 | pub(crate) fn reset_tracing_counter(&self) { 131 | debug_assert!(self.tracing_counter() != COUNTER_MASK); // Check for reserved value 132 | self.tracing_counter.set(self.tracing_counter.get() & !COUNTER_MASK); 133 | } 134 | 135 | #[cfg(feature = "finalization")] 136 | #[inline] 137 | pub(crate) fn needs_finalization(&self) -> bool { 138 | (self.counter.get() & FINALIZED_MASK) == 0u16 139 | } 140 | 141 | #[cfg(feature = "finalization")] 142 | #[inline] 143 | pub(crate) fn set_finalized(&self, finalized: bool) { 144 | Self::set_bits(&self.counter, finalized, FINALIZED_MASK); 145 | } 146 | 147 | #[cfg(feature = "weak-ptrs")] 148 | #[inline] 149 | pub(crate) fn has_allocated_for_metadata(&self) -> bool { 150 | (self.counter.get() & FIRST_BIT_MASK) == FIRST_BIT_MASK 151 | } 152 | 153 | #[cfg(feature = "weak-ptrs")] 154 | #[inline] 155 | pub(crate) fn set_allocated_for_metadata(&self, allocated_for_metadata: bool) { 156 | Self::set_bits(&self.counter, allocated_for_metadata, FIRST_BIT_MASK); 157 | } 158 | 159 | #[cfg(feature = "weak-ptrs")] 160 | #[inline] 161 | pub(crate) fn is_dropped(&self) -> bool { 162 | (self.tracing_counter.get() & COUNTER_MASK) == COUNTER_MASK 163 | } 164 | 165 | #[cfg(feature = "weak-ptrs")] 166 | #[inline] 167 | pub(crate) fn set_dropped(&self, dropped: bool) { 168 | Self::set_bits(&self.tracing_counter, dropped, COUNTER_MASK); 169 | } 170 | 171 | #[inline] 172 | pub(crate) fn is_not_marked(&self) -> bool { 173 | // true if (self.counter & BITS_MASK) is equal to 01 or 00, 174 | // so if the first bit is 0 175 | (self.tracing_counter.get() & FIRST_BIT_MASK) == 0u16 176 | } 177 | 178 | #[inline] 179 | pub(crate) fn is_in_possible_cycles(&self) -> bool { 180 | (self.tracing_counter.get() & BITS_MASK) == IN_POSSIBLE_CYCLES 181 | } 182 | 183 | #[inline] 184 | pub(crate) fn is_in_list(&self) -> bool { 185 | (self.tracing_counter.get() & BITS_MASK) == IN_LIST 186 | } 187 | 188 | #[inline] 189 | pub(crate) fn _is_in_queue(&self) -> bool { 190 | (self.tracing_counter.get() & BITS_MASK) == IN_QUEUE 191 | } 192 | 193 | #[inline] 194 | pub(crate) fn is_in_list_or_queue(&self) -> bool { 195 | // true if (self.counter & BITS_MASK) is equal to 10 or 11, 196 | // so if the first bit is 1 197 | (self.tracing_counter.get() & FIRST_BIT_MASK) == FIRST_BIT_MASK 198 | } 199 | 200 | #[inline] 201 | pub(crate) fn mark(&self, new_mark: Mark) { 202 | self.tracing_counter.set((self.tracing_counter.get() & !BITS_MASK) | (new_mark as u16)); 203 | } 204 | 205 | #[cfg(any(feature = "weak-ptrs", feature = "finalization"))] 206 | #[inline(always)] 207 | fn set_bits(cell: &Cell, value: bool, mask: u16) { 208 | if value { 209 | cell.set(cell.get() | mask); 210 | } else { 211 | cell.set(cell.get() & !mask); 212 | } 213 | } 214 | } 215 | 216 | #[derive(Copy, Clone, Debug)] 217 | #[repr(u16)] 218 | pub(crate) enum Mark { 219 | NonMarked = NON_MARKED, 220 | PossibleCycles = IN_POSSIBLE_CYCLES, 221 | InList = IN_LIST, 222 | InQueue = IN_QUEUE, 223 | } 224 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration of the garbage collector. 2 | //! 3 | //! The configuration can be accessed using the [`config`][`fn@config`] function. 4 | //! 5 | //! # Automatic collection executions 6 | //! 7 | //! Collections can be automatically started if [`auto_collect`][`fn@Config::auto_collect`] is set to `true`. 8 | //! 9 | //! To determine whether to start a collection, a *threshold* is kept over the number of allocated bytes. 10 | //! When calling a function which may start a collection (e.g. [`Cc::new`][`crate::Cc::new`]), 11 | //! if the number of allocated bytes exceeds the *threshold* a collection is started. 12 | //! 13 | //! At the end of the automatically started collection, if the *threshold* is still lower than the number of allocated bytes 14 | //! then it is doubled until it exceed it. 15 | //! 16 | //! Instead, if the number of allocated bytes exceed the *threshold* multiplied by the [`adjustment_percent`][`fn@Config::adjustment_percent`], 17 | //! then the *threshold* is halved until the condition becomes true. 18 | //! 19 | //! Finally, a collection may also happen if the number of objects buffered to be processed in the next collection (see [`Cc::mark_alive`][`crate::Cc::mark_alive`]) 20 | //! exceeds the [`buffered_objects_threshold`][`fn@Config::buffered_objects_threshold`]. This parameter is disabled by default, but can be enabled by 21 | //! using [`set_buffered_objects_threshold`][`fn@Config::set_buffered_objects_threshold`]. 22 | 23 | use alloc::rc::Rc; 24 | use core::cell::RefCell; 25 | use core::num::NonZeroUsize; 26 | use core::marker::PhantomData; 27 | 28 | use thiserror::Error; 29 | use crate::lists::PossibleCycles; 30 | use crate::state::State; 31 | use crate::utils; 32 | 33 | const DEFAULT_BYTES_THRESHOLD: usize = 100; 34 | 35 | utils::rust_cc_thread_local! { 36 | pub(crate) static CONFIG: RefCell = const { RefCell::new(Config::new()) }; 37 | } 38 | 39 | /// Access the configuration. 40 | /// 41 | /// Returns [`Err`] if the configuration is already being accessed. 42 | /// 43 | /// # Panics 44 | /// 45 | /// Panics if the provided closure panics. 46 | /// 47 | /// # Example 48 | /// ```rust 49 | ///# use rust_cc::config::config; 50 | /// let res = config(|config| { 51 | /// // Edit the configuration 52 | /// }).unwrap(); 53 | /// ``` 54 | pub fn config(f: F) -> Result 55 | where 56 | F: FnOnce(&mut Config) -> R, 57 | { 58 | CONFIG.try_with(|config| { 59 | config 60 | .try_borrow_mut() 61 | .or(Err(ConfigAccessError::ConcurrentAccessError)) 62 | .map(|mut config| f(&mut config)) 63 | }).unwrap_or(Err(ConfigAccessError::AccessError)) 64 | } 65 | 66 | /// An error returned by [`config`][`fn@config`]. 67 | #[non_exhaustive] 68 | #[derive(Error, Debug)] 69 | pub enum ConfigAccessError { 70 | /// The configuration couldn't be accessed. 71 | #[error("couldn't access the configuration")] 72 | AccessError, 73 | /// The configuration was already being accessed. 74 | #[error("the configuration is already being accessed")] 75 | ConcurrentAccessError, 76 | } 77 | 78 | /// The configuration of the garbage collector. 79 | #[derive(Debug, Clone)] 80 | pub struct Config { 81 | // The invariant is: 82 | // bytes_threshold * adjustment_percent < allocated_bytes < bytes_threshold 83 | bytes_threshold: usize, 84 | adjustment_percent: f64, 85 | buffered_threshold: Option, 86 | auto_collect: bool, 87 | _phantom: PhantomData>, // Make Config !Send and !Sync 88 | } 89 | 90 | impl Config { 91 | #[inline] 92 | const fn new() -> Self { 93 | Self { 94 | bytes_threshold: DEFAULT_BYTES_THRESHOLD, 95 | adjustment_percent: 0.1, 96 | buffered_threshold: None, 97 | auto_collect: true, 98 | _phantom: PhantomData, 99 | } 100 | } 101 | 102 | /// Returns `true` if collections can be automatically started, `false` otherwise. 103 | #[inline] 104 | pub fn auto_collect(&self) -> bool { 105 | self.auto_collect 106 | } 107 | 108 | /// Sets whether collections can be automatically started. 109 | #[inline] 110 | pub fn set_auto_collect(&mut self, auto_collect: bool) { 111 | self.auto_collect = auto_collect; 112 | } 113 | 114 | /// Returns the threshold adjustment percent. 115 | /// 116 | /// See the [module-level documentation][`mod@crate::config`] for more details. 117 | #[inline] 118 | pub fn adjustment_percent(&self) -> f64 { 119 | self.adjustment_percent 120 | } 121 | 122 | /// Sets the threshold adjustment percent. 123 | /// 124 | /// See the [module-level documentation][`mod@crate::config`] for more details. 125 | /// 126 | /// # Panics 127 | /// 128 | /// Panics if the provided `percent` isn't between 0 and 1 (included). 129 | #[inline] 130 | #[track_caller] 131 | pub fn set_adjustment_percent(&mut self, percent: f64) { 132 | assert!( 133 | (0f64..=1f64).contains(&percent), 134 | "percent must be between 0 and 1" 135 | ); 136 | self.adjustment_percent = percent; 137 | } 138 | 139 | /// Returns the buffered-objects threshold (see [`Cc::mark_alive`][`crate::Cc::mark_alive`]). 140 | /// 141 | /// Returns [`None`] if this parameter isn't used to start a collection. 142 | /// 143 | /// See the [module-level documentation][`mod@crate::config`] for more details. 144 | #[inline] 145 | pub fn buffered_objects_threshold(&self) -> Option { 146 | self.buffered_threshold 147 | } 148 | 149 | /// Sets the buffered-objects threshold (see [`Cc::mark_alive`][`crate::Cc::mark_alive`]). 150 | /// 151 | /// If the provided `threshold` is [`None`], then this parameter will not be used to start a collection. 152 | /// 153 | /// See the [module-level documentation][`mod@crate::config`] for more details. 154 | #[inline] 155 | #[track_caller] 156 | pub fn set_buffered_objects_threshold(&mut self, threshold: Option) { 157 | self.buffered_threshold = threshold; 158 | } 159 | 160 | #[inline(always)] 161 | pub(super) fn should_collect(&mut self, state: &State, possible_cycles: &PossibleCycles) -> bool { 162 | if !self.auto_collect { 163 | return false; 164 | } 165 | 166 | if state.allocated_bytes() > self.bytes_threshold { 167 | return true; 168 | } 169 | 170 | if let Some(buffered_threshold) = self.buffered_threshold { 171 | possible_cycles.size() > buffered_threshold.get() 172 | } else { 173 | false 174 | } 175 | } 176 | 177 | #[inline(always)] 178 | pub(super) fn adjust(&mut self, state: &State) { 179 | // First case: the threshold might have to be increased 180 | if state.allocated_bytes() >= self.bytes_threshold { 181 | 182 | loop { 183 | let Some(new_threshold) = self.bytes_threshold.checked_shl(1) else { break; }; 184 | self.bytes_threshold = new_threshold; 185 | if state.allocated_bytes() < self.bytes_threshold { 186 | break; 187 | } 188 | } 189 | 190 | return; // Skip the other case 191 | } 192 | 193 | // Second case: the threshold might have to be decreased 194 | let allocated = state.allocated_bytes() as f64; 195 | 196 | // If adjustment_percent or the result of the multiplication is 0 do nothing 197 | if ((self.bytes_threshold as f64) * self.adjustment_percent) == 0.0 { 198 | return; 199 | } 200 | 201 | // No more cases after this, there's no need to use an additional if as above 202 | while allocated <= ((self.bytes_threshold as f64) * self.adjustment_percent) { 203 | let new_threshold = self.bytes_threshold >> 1; 204 | if state.allocated_bytes() >= new_threshold { 205 | break; // If the shift produces a threshold <= allocated, then don't update bytes_threshold to maintain the invariant 206 | } 207 | if new_threshold <= DEFAULT_BYTES_THRESHOLD { 208 | self.bytes_threshold = DEFAULT_BYTES_THRESHOLD; 209 | break; 210 | } 211 | self.bytes_threshold = new_threshold; 212 | } 213 | } 214 | } 215 | 216 | impl Default for Config { 217 | #[inline] 218 | fn default() -> Self { 219 | Self::new() 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | //! Information about the garbage collector state. 2 | 3 | use alloc::alloc::Layout; 4 | use alloc::rc::Rc; 5 | use core::cell::Cell; 6 | use core::marker::PhantomData; 7 | use thiserror::Error; 8 | use crate::utils; 9 | 10 | utils::rust_cc_thread_local! { 11 | static STATE: State = const { State::new() }; 12 | } 13 | 14 | #[inline] 15 | pub(crate) fn state(f: impl FnOnce(&State) -> R) -> R { 16 | // Use try_state instead of state.with(...) since with is not marked as inline 17 | try_state(f).unwrap_or_else(|_| panic!("Couldn't access the state")) 18 | } 19 | 20 | #[inline] 21 | pub(crate) fn try_state(f: impl FnOnce(&State) -> R) -> Result { 22 | STATE.try_with(|state| Ok(f(state))).unwrap_or(Err(StateAccessError::AccessError)) 23 | } 24 | 25 | /// An error returned by functions accessing the garbage collector state. 26 | #[non_exhaustive] 27 | #[derive(Error, Debug)] 28 | pub enum StateAccessError { 29 | /// The garbage collector state couldn't be accessed. 30 | #[error("couldn't access the state")] 31 | AccessError, 32 | } 33 | 34 | #[cfg(all(test, feature = "std"))] // Only used in unit tests 35 | pub(crate) fn reset_state() { 36 | state(|state| { 37 | state.collecting.set(false); 38 | 39 | #[cfg(feature = "finalization")] 40 | state.finalizing.set(false); 41 | 42 | state.dropping.set(false); 43 | state.allocated_bytes.set(0); 44 | state.executions_counter.set(0); 45 | }); 46 | } 47 | 48 | pub(crate) struct State { 49 | collecting: Cell, 50 | 51 | #[cfg(feature = "finalization")] 52 | finalizing: Cell, 53 | 54 | dropping: Cell, 55 | allocated_bytes: Cell, 56 | executions_counter: Cell, 57 | 58 | _phantom: PhantomData>, // Make State !Send and !Sync 59 | } 60 | 61 | impl State { 62 | #[inline] 63 | const fn new() -> Self { 64 | Self { 65 | collecting: Cell::new(false), 66 | 67 | #[cfg(feature = "finalization")] 68 | finalizing: Cell::new(false), 69 | 70 | dropping: Cell::new(false), 71 | allocated_bytes: Cell::new(0), 72 | executions_counter: Cell::new(0), 73 | 74 | _phantom: PhantomData, 75 | } 76 | } 77 | 78 | #[inline] 79 | pub(crate) fn allocated_bytes(&self) -> usize { 80 | self.allocated_bytes.get() 81 | } 82 | 83 | #[inline] 84 | pub(crate) fn record_allocation(&self, layout: Layout) { 85 | self.allocated_bytes.set(self.allocated_bytes.get() + layout.size()); 86 | } 87 | 88 | #[inline] 89 | pub(crate) fn record_deallocation(&self, layout: Layout) { 90 | self.allocated_bytes.set(self.allocated_bytes.get() - layout.size()); 91 | } 92 | 93 | #[inline] 94 | pub(crate) fn executions_count(&self) -> usize { 95 | self.executions_counter.get() 96 | } 97 | 98 | #[inline] 99 | pub(super) fn increment_executions_count(&self) { 100 | self.executions_counter.set(self.executions_counter.get() + 1); 101 | } 102 | 103 | #[inline] 104 | pub(crate) fn is_collecting(&self) -> bool { 105 | self.collecting.get() 106 | } 107 | 108 | #[inline] 109 | pub(crate) fn set_collecting(&self, value: bool) { 110 | self.collecting.set(value); 111 | } 112 | 113 | #[cfg(feature = "finalization")] 114 | #[inline] 115 | pub(crate) fn is_finalizing(&self) -> bool { 116 | self.finalizing.get() 117 | } 118 | 119 | #[cfg(feature = "finalization")] 120 | #[inline] 121 | pub(crate) fn set_finalizing(&self, value: bool) { 122 | self.finalizing.set(value); 123 | } 124 | 125 | #[inline] 126 | pub(crate) fn is_dropping(&self) -> bool { 127 | self.dropping.get() 128 | } 129 | 130 | #[inline] 131 | pub(crate) fn set_dropping(&self, value: bool) { 132 | self.dropping.set(value); 133 | } 134 | 135 | #[inline] 136 | #[allow(dead_code)] // Currently used only inside #[cfg(debug_assertions)], but always keep it 137 | pub(crate) fn is_tracing(&self) -> bool { 138 | #[cfg(feature = "finalization")] 139 | { 140 | self.collecting.get() && !self.finalizing.get() && !self.dropping.get() 141 | } 142 | 143 | #[cfg(not(feature = "finalization"))] 144 | { 145 | self.collecting.get() && !self.dropping.get() 146 | } 147 | } 148 | } 149 | 150 | impl Default for State { 151 | #[inline] 152 | fn default() -> Self { 153 | Self::new() 154 | } 155 | } 156 | 157 | /// Returns the number of objects buffered to be processed in the next collection. 158 | /// 159 | /// See [`Cc::mark_alive`][`crate::Cc::mark_alive`] for more details. 160 | #[inline] 161 | pub fn buffered_objects_count() -> Result { 162 | // Expose this in state module even though the count is kept inside POSSIBLE_CYCLES 163 | // The error returned in case of failed access is a generic StateAccessError::AccessError 164 | crate::POSSIBLE_CYCLES.try_with(|pc| Ok(pc.size())).unwrap_or(Err(StateAccessError::AccessError)) 165 | } 166 | 167 | /// Returns the number of allocated bytes managed by the garbage collector. 168 | #[inline] 169 | pub fn allocated_bytes() -> Result { 170 | try_state(|state| Ok(state.allocated_bytes()))? 171 | } 172 | 173 | /// Returns the total number of executed collections. 174 | #[inline] 175 | pub fn executions_count() -> Result { 176 | try_state(|state| Ok(state.executions_count()))? 177 | } 178 | 179 | /// Returns `true` if the garbage collector is in a tracing phase, `false` otherwise. 180 | /// 181 | /// See [`Trace`][`trait@crate::Trace`] for more details. 182 | #[inline] 183 | pub fn is_tracing() -> Result { 184 | try_state(|state| Ok(state.is_tracing()))? 185 | } 186 | 187 | /// Utility macro used internally to implement drop guards that accesses the state 188 | macro_rules! replace_state_field { 189 | (dropping, $value:expr, $state:ident) => { 190 | $crate::state::replace_state_field!(__internal is_dropping, set_dropping, bool, $value, $state) 191 | }; 192 | (finalizing, $value:expr, $state:ident) => { 193 | $crate::state::replace_state_field!(__internal is_finalizing, set_finalizing, bool, $value, $state) 194 | }; 195 | (__internal $is_name:ident, $set_name:ident, $field_type:ty, $value:expr, $state:ident) => { 196 | { 197 | let old_value: $field_type = $crate::state::State::$is_name($state); 198 | $crate::state::State::$set_name($state, $value); 199 | 200 | #[must_use = "the drop guard shouldn't be dropped instantly"] 201 | struct DropGuard<'a> { 202 | state: &'a $crate::state::State, 203 | old_value: $field_type, 204 | } 205 | 206 | impl<'a> ::core::ops::Drop for DropGuard<'a> { 207 | #[inline] 208 | fn drop(&mut self) { 209 | $crate::state::State::$set_name(self.state, self.old_value); 210 | } 211 | } 212 | 213 | #[allow(clippy::redundant_field_names)] 214 | DropGuard { state: $state, old_value } 215 | } 216 | }; 217 | } 218 | 219 | // This makes replace_state_field macro usable across modules 220 | pub(crate) use replace_state_field; 221 | 222 | #[cfg(test)] 223 | mod tests { 224 | use crate::state::{state}; 225 | 226 | #[test] 227 | fn test_replace_state_field() { 228 | state(|state| { 229 | // Test state.dropping = true 230 | state.set_dropping(true); 231 | { 232 | let _finalizing_guard = replace_state_field!(dropping, false, state); 233 | assert!(!state.is_dropping()); 234 | } 235 | assert!(state.is_dropping()); 236 | 237 | // Test state.dropping = false 238 | state.set_dropping(false); 239 | { 240 | let _dropping_guard = replace_state_field!(dropping, true, state); 241 | assert!(state.is_dropping()); 242 | } 243 | assert!(!state.is_dropping()); 244 | 245 | #[cfg(feature = "finalization")] 246 | { 247 | // Test state.finalizing = true 248 | state.set_finalizing(true); 249 | { 250 | let _finalizing_guard = replace_state_field!(finalizing, false, state); 251 | assert!(!state.is_finalizing()); 252 | } 253 | assert!(state.is_finalizing()); 254 | 255 | // Test state.finalizing = false 256 | state.set_finalizing(false); 257 | { 258 | let _finalizing_guard = replace_state_field!(finalizing, true, state); 259 | assert!(state.is_finalizing()); 260 | } 261 | assert!(!state.is_finalizing()); 262 | } 263 | }); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/tests/counter_marker.rs: -------------------------------------------------------------------------------- 1 | use crate::counter_marker::*; 2 | 3 | fn assert_not_marked(counter: &CounterMarker) { 4 | assert!(counter.is_not_marked()); 5 | assert!(!counter.is_in_possible_cycles()); 6 | assert!(!counter.is_in_list()); 7 | assert!(!counter._is_in_queue()); 8 | assert!(!counter.is_in_list_or_queue()); 9 | assert!(!counter.is_in_list_or_queue()); 10 | } 11 | 12 | fn assert_default_settings(_counter: &CounterMarker) { 13 | #[cfg(feature = "finalization")] 14 | assert!(_counter.needs_finalization()); 15 | 16 | #[cfg(feature = "weak-ptrs")] 17 | { 18 | assert!(!_counter.has_allocated_for_metadata()); 19 | assert!(!_counter.is_dropped()); 20 | } 21 | } 22 | 23 | #[test] 24 | fn test_new() { 25 | fn test(counter: CounterMarker) { 26 | assert_not_marked(&counter); 27 | assert_default_settings(&counter); 28 | 29 | assert_eq!(counter.counter(), 1); 30 | assert_eq!(counter.tracing_counter(), 1); 31 | } 32 | 33 | test(CounterMarker::new_with_counter_to_one(false)); 34 | test(CounterMarker::new_with_counter_to_one(false)); 35 | } 36 | 37 | #[cfg(feature = "finalization")] 38 | #[test] 39 | fn test_is_to_finalize() { 40 | fn assert_not_marked_fin(counter: &CounterMarker) { 41 | assert_not_marked(counter); 42 | #[cfg(feature = "weak-ptrs")] 43 | { 44 | assert!(!counter.has_allocated_for_metadata()); 45 | assert!(!counter.is_dropped()); 46 | } 47 | } 48 | 49 | fn test(already_fin: bool) { 50 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 51 | assert_not_marked_fin(&counter); 52 | assert_eq!(!already_fin, counter.needs_finalization()); 53 | 54 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 55 | assert_not_marked_fin(&counter); 56 | counter.set_finalized(true); 57 | assert!(!counter.needs_finalization()); 58 | 59 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 60 | assert_not_marked_fin(&counter); 61 | counter.set_finalized(false); 62 | assert!(counter.needs_finalization()); 63 | } 64 | 65 | test(true); 66 | test(false); 67 | } 68 | 69 | #[cfg(feature = "weak-ptrs")] 70 | #[test] 71 | fn test_weak_ptrs_exists() { 72 | fn assert_not_marked_weak_ptrs(counter: &CounterMarker, _already_fin: bool) { 73 | assert_not_marked(counter); 74 | 75 | assert!(!counter.is_dropped()); 76 | 77 | #[cfg(feature = "finalization")] 78 | assert_eq!(!_already_fin, counter.needs_finalization()); 79 | } 80 | 81 | fn test(already_fin: bool) { 82 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 83 | assert_not_marked_weak_ptrs(&counter, already_fin); 84 | assert!(!counter.has_allocated_for_metadata()); 85 | 86 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 87 | assert_not_marked_weak_ptrs(&counter, already_fin); 88 | counter.set_allocated_for_metadata(true); 89 | assert!(counter.has_allocated_for_metadata()); 90 | 91 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 92 | assert_not_marked_weak_ptrs(&counter, already_fin); 93 | counter.set_allocated_for_metadata(false); 94 | assert!(!counter.has_allocated_for_metadata()); 95 | } 96 | 97 | test(true); 98 | test(false); 99 | } 100 | 101 | #[cfg(feature = "weak-ptrs")] 102 | #[test] 103 | fn test_dropped() { 104 | fn assert_not_marked_dropped(counter: &CounterMarker, _already_fin: bool) { 105 | assert_not_marked(counter); 106 | 107 | assert!(!counter.has_allocated_for_metadata()); 108 | 109 | #[cfg(feature = "finalization")] 110 | assert_eq!(!_already_fin, counter.needs_finalization()); 111 | } 112 | 113 | fn test(already_fin: bool) { 114 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 115 | assert_not_marked_dropped(&counter, already_fin); 116 | assert!(!counter.is_dropped()); 117 | 118 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 119 | assert_not_marked_dropped(&counter, already_fin); 120 | counter.set_dropped(true); 121 | assert!(counter.is_dropped()); 122 | 123 | let counter = CounterMarker::new_with_counter_to_one(already_fin); 124 | assert_not_marked_dropped(&counter, already_fin); 125 | counter.set_dropped(false); 126 | assert!(!counter.is_dropped()); 127 | } 128 | 129 | test(true); 130 | test(false); 131 | } 132 | 133 | #[test] 134 | fn test_increment_decrement() { 135 | fn test(counter: CounterMarker) { 136 | assert_not_marked(&counter); 137 | assert_default_settings(&counter); 138 | 139 | assert_eq!(counter.counter(), 1); 140 | 141 | assert_not_marked(&counter); 142 | assert_default_settings(&counter); 143 | 144 | assert_eq!(counter.tracing_counter(), 1); 145 | 146 | assert_not_marked(&counter); 147 | assert_default_settings(&counter); 148 | 149 | assert!(counter.increment_counter().is_ok()); 150 | 151 | assert_not_marked(&counter); 152 | assert_default_settings(&counter); 153 | 154 | assert_eq!(counter.counter(), 2); 155 | assert_eq!(counter.tracing_counter(), 1); 156 | 157 | assert!(counter.increment_tracing_counter().is_ok()); 158 | 159 | assert_not_marked(&counter); 160 | assert_default_settings(&counter); 161 | 162 | assert_eq!(counter.counter(), 2); 163 | assert_eq!(counter.tracing_counter(), 2); 164 | 165 | assert!(counter.decrement_counter().is_ok()); 166 | 167 | assert_not_marked(&counter); 168 | assert_default_settings(&counter); 169 | 170 | assert_eq!(counter.counter(), 1); 171 | assert!(counter._decrement_tracing_counter().is_ok()); 172 | 173 | assert_not_marked(&counter); 174 | assert_default_settings(&counter); 175 | 176 | assert_eq!(counter.counter(), 1); 177 | assert_eq!(counter.tracing_counter(), 1); 178 | 179 | // Don't run this under MIRI since it slows down tests by a lot. Moreover, there's no 180 | // unsafe code used in the functions down below, so MIRI isn't really necessary here 181 | #[cfg(not(miri))] 182 | { 183 | while counter.counter() < MAX { 184 | assert!(counter.increment_counter().is_ok()); 185 | } 186 | assert!(counter.increment_counter().is_err()); 187 | 188 | while counter.tracing_counter() < MAX { 189 | assert!(counter.increment_tracing_counter().is_ok()); 190 | } 191 | assert!(counter.increment_tracing_counter().is_err()); 192 | 193 | while counter.counter() > 0 { 194 | assert!(counter.decrement_counter().is_ok()); 195 | } 196 | assert!(counter.decrement_counter().is_err()); 197 | 198 | while counter.tracing_counter() > 0 { 199 | assert!(counter._decrement_tracing_counter().is_ok()); 200 | } 201 | assert!(counter._decrement_tracing_counter().is_err()); 202 | } 203 | 204 | assert_not_marked(&counter); 205 | assert_default_settings(&counter); 206 | } 207 | 208 | test(CounterMarker::new_with_counter_to_one(false)); 209 | test(CounterMarker::new_with_counter_to_one(false)); 210 | } 211 | 212 | #[test] 213 | fn test_marks() { 214 | fn test(counter: CounterMarker) { 215 | assert_not_marked(&counter); 216 | assert_default_settings(&counter); 217 | 218 | counter.mark(Mark::NonMarked); 219 | 220 | assert_not_marked(&counter); 221 | assert_default_settings(&counter); 222 | 223 | counter.mark(Mark::PossibleCycles); 224 | 225 | assert!(counter.is_not_marked()); 226 | assert!(counter.is_in_possible_cycles()); 227 | assert!(!counter.is_in_list()); 228 | assert!(!counter._is_in_queue()); 229 | assert!(!counter.is_in_list_or_queue()); 230 | assert_default_settings(&counter); 231 | 232 | counter.mark(Mark::InList); 233 | 234 | assert!(!counter.is_not_marked()); 235 | assert!(!counter.is_in_possible_cycles()); 236 | assert!(counter.is_in_list()); 237 | assert!(!counter._is_in_queue()); 238 | assert!(counter.is_in_list_or_queue()); 239 | assert_default_settings(&counter); 240 | 241 | counter.mark(Mark::InQueue); 242 | 243 | assert!(!counter.is_not_marked()); 244 | assert!(!counter.is_in_possible_cycles()); 245 | assert!(!counter.is_in_list()); 246 | assert!(counter._is_in_queue()); 247 | assert!(counter.is_in_list_or_queue()); 248 | assert_default_settings(&counter); 249 | 250 | counter.mark(Mark::NonMarked); 251 | 252 | assert_not_marked(&counter); 253 | assert_default_settings(&counter); 254 | } 255 | 256 | test(CounterMarker::new_with_counter_to_one(false)); 257 | test(CounterMarker::new_with_counter_to_one(false)); 258 | } 259 | 260 | #[test] 261 | fn test_reset_tracing_counter() { 262 | fn test(counter: CounterMarker) { 263 | let _ = counter.increment_tracing_counter(); 264 | let _ = counter.increment_tracing_counter(); 265 | let _ = counter.increment_tracing_counter(); 266 | let _ = counter.increment_tracing_counter(); 267 | 268 | assert_ne!(counter.tracing_counter(), 0); 269 | assert_default_settings(&counter); 270 | 271 | counter.reset_tracing_counter(); 272 | 273 | assert_eq!(counter.tracing_counter(), 0); 274 | assert_default_settings(&counter); 275 | } 276 | 277 | test(CounterMarker::new_with_counter_to_one(false)); 278 | test(CounterMarker::new_with_counter_to_one(false)); 279 | } 280 | -------------------------------------------------------------------------------- /src/tests/panicking.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Cell, RefCell}; 2 | use std::mem; 3 | 4 | use crate::tests::{assert_state_not_collecting, reset_state}; 5 | use crate::{collect_cycles, Cc, Context, Finalize, Trace}; 6 | 7 | fn register_panicking(#[allow(unused_variables)] cc: &Cc) { // The unused_variables warning is triggered when not running on Miri 8 | #[cfg(miri)] 9 | extern "Rust" { 10 | /// From Miri documentation:
11 | /// _Miri-provided extern function to mark the block `ptr` points to as a "root" 12 | /// for some static memory. This memory and everything reachable by it is not 13 | /// considered leaking even if it still exists when the program terminates._
14 | fn miri_static_root(ptr: *const u8); 15 | } 16 | 17 | #[cfg(miri)] 18 | // Use miri_static_root to avoid failures caused by leaks. Leaks are expected, 19 | // since this module tests panics (which leaks memory to prevent UB) 20 | unsafe { 21 | use core::mem::transmute; 22 | 23 | miri_static_root(*transmute::<_, &*const u8>(cc)); 24 | } 25 | } 26 | 27 | fn panicking_collect_cycles(on_panic: impl FnOnce()) { 28 | struct DropGuard 29 | where 30 | F: FnOnce(), 31 | { 32 | on_panic: Option, 33 | } 34 | 35 | impl Drop for DropGuard { 36 | fn drop(&mut self) { 37 | if let Some(f) = self.on_panic.take() { 38 | f(); 39 | } 40 | } 41 | } 42 | 43 | let drop_guard = DropGuard { 44 | on_panic: Some(on_panic), 45 | }; 46 | 47 | collect_cycles(); 48 | 49 | mem::forget(drop_guard); // Don't execute on_panic when collect_cycles doesn't panic 50 | } 51 | 52 | struct Panicking { 53 | trace_counter: Cell, 54 | panic_on_trace: Cell, 55 | panic_on_finalize: Cell, 56 | panic_on_drop: Cell, 57 | cc: RefCell>>, 58 | } 59 | 60 | unsafe impl Trace for Panicking { 61 | fn trace(&self, ctx: &mut Context<'_>) { 62 | let counter = self.trace_counter.get(); 63 | if self.panic_on_trace.get() { 64 | if counter == 0 { 65 | panic!("Test panic on Trace"); 66 | } else { 67 | self.trace_counter.set(counter - 1); 68 | } 69 | } 70 | self.cc.trace(ctx); 71 | } 72 | } 73 | 74 | impl Finalize for Panicking { 75 | fn finalize(&self) { 76 | if self.panic_on_finalize.get() { 77 | panic!("Test panic on Finalize"); 78 | } 79 | } 80 | } 81 | 82 | impl Drop for Panicking { 83 | fn drop(&mut self) { 84 | if self.panic_on_drop.get() { 85 | panic!("Test panic on Drop"); 86 | } 87 | } 88 | } 89 | 90 | #[test] 91 | #[should_panic = "Test panic on Trace"] 92 | fn test_panicking_tracing_counting() { 93 | reset_state(); 94 | 95 | { 96 | let cc = Cc::new(Panicking { 97 | trace_counter: Cell::new(0), 98 | panic_on_trace: Cell::new(true), 99 | panic_on_finalize: Cell::new(false), 100 | panic_on_drop: Cell::new(false), 101 | cc: RefCell::new(None), 102 | }); 103 | *cc.cc.borrow_mut() = Some(cc.clone()); 104 | register_panicking(&cc); 105 | } 106 | panicking_collect_cycles(assert_state_not_collecting); 107 | } 108 | 109 | #[test] 110 | #[should_panic = "Test panic on Trace"] 111 | fn test_panicking_tracing_root() { 112 | reset_state(); 113 | 114 | // Leave a root alive to trigger root tracing 115 | let _root = { 116 | let root = Cc::new(Panicking { 117 | trace_counter: Cell::new(1), 118 | panic_on_trace: Cell::new(true), 119 | panic_on_finalize: Cell::new(false), 120 | panic_on_drop: Cell::new(false), 121 | cc: RefCell::new(None), 122 | }); 123 | *root.cc.borrow_mut() = Some(Cc::new(Panicking { 124 | trace_counter: Cell::new(usize::MAX), 125 | panic_on_trace: Cell::new(false), 126 | panic_on_finalize: Cell::new(false), 127 | panic_on_drop: Cell::new(false), 128 | cc: RefCell::new(Some(root.clone())), 129 | })); 130 | register_panicking(&root); 131 | register_panicking(root.cc.borrow().as_ref().unwrap()); 132 | #[allow(clippy::redundant_clone)] 133 | root.clone() 134 | }; 135 | panicking_collect_cycles(assert_state_not_collecting); 136 | } 137 | 138 | #[test] 139 | #[cfg_attr(feature = "finalization", should_panic = "Test panic on Finalize")] 140 | fn test_panicking_finalize() { 141 | reset_state(); 142 | 143 | { 144 | let cc = Cc::new(Panicking { 145 | trace_counter: Cell::new(usize::MAX), 146 | panic_on_trace: Cell::new(false), 147 | panic_on_finalize: Cell::new(true), 148 | panic_on_drop: Cell::new(false), 149 | cc: RefCell::new(None), 150 | }); 151 | *cc.cc.borrow_mut() = Some(cc.clone()); 152 | register_panicking(&cc); 153 | } 154 | panicking_collect_cycles(assert_state_not_collecting); 155 | } 156 | 157 | #[test] 158 | #[should_panic = "Test panic on Drop"] 159 | fn test_panicking_drop() { 160 | reset_state(); 161 | 162 | // Cannot use Panicking since Panicking implements Finalize 163 | struct DropPanicking { 164 | cyclic: RefCell>>, 165 | } 166 | 167 | unsafe impl Trace for DropPanicking { 168 | fn trace(&self, ctx: &mut Context<'_>) { 169 | self.cyclic.trace(ctx); 170 | } 171 | } 172 | 173 | impl Finalize for DropPanicking { 174 | } 175 | 176 | impl Drop for DropPanicking { 177 | fn drop(&mut self) { 178 | panic!("Test panic on Drop"); 179 | } 180 | } 181 | 182 | { 183 | let cc = Cc::new(DropPanicking { 184 | cyclic: RefCell::new(None), 185 | }); 186 | *cc.cyclic.borrow_mut() = Some(cc.clone()); 187 | register_panicking(&cc); 188 | } 189 | panicking_collect_cycles(assert_state_not_collecting); 190 | } 191 | 192 | #[test] 193 | #[should_panic = "Test panic on Drop"] 194 | fn test_panicking_drop_and_finalize() { 195 | reset_state(); 196 | 197 | { 198 | let cc = Cc::new(Panicking { 199 | trace_counter: Cell::new(usize::MAX), 200 | panic_on_trace: Cell::new(false), 201 | panic_on_finalize: Cell::new(false), 202 | panic_on_drop: Cell::new(true), 203 | cc: RefCell::new(None), 204 | }); 205 | *cc.cc.borrow_mut() = Some(cc.clone()); 206 | register_panicking(&cc); 207 | } 208 | panicking_collect_cycles(assert_state_not_collecting); 209 | } 210 | 211 | #[test] 212 | #[cfg_attr(feature = "finalization", should_panic = "Test panic on Trace")] 213 | fn test_panicking_tracing_drop() { 214 | reset_state(); 215 | 216 | { 217 | // (See usage of this constant below for more context) 218 | // Since our objects are marked as non-roots they are traced a first time using 219 | // counting tracing, then they are NOT traced during root tracing and they are 220 | // then traced again during dropping tracing. So, 1 means that the second trace 221 | // call will panic, which is during dropping tracing 222 | const CELL_VALUE: usize = 1; 223 | 224 | let root = Cc::new(Panicking { 225 | trace_counter: Cell::new(CELL_VALUE), 226 | panic_on_trace: Cell::new(true), 227 | panic_on_finalize: Cell::new(false), 228 | panic_on_drop: Cell::new(false), 229 | cc: RefCell::new(None), 230 | }); 231 | *root.cc.borrow_mut() = Some(Cc::new(Panicking { 232 | trace_counter: Cell::new(CELL_VALUE), 233 | panic_on_trace: Cell::new(true), 234 | panic_on_finalize: Cell::new(false), 235 | panic_on_drop: Cell::new(false), 236 | cc: RefCell::new(Some(root.clone())), 237 | })); 238 | 239 | register_panicking(&root); 240 | register_panicking(root.cc.borrow().as_ref().unwrap()); 241 | } 242 | panicking_collect_cycles(assert_state_not_collecting); 243 | } 244 | 245 | #[test] 246 | #[cfg_attr(feature = "finalization", should_panic = "Test panic on Trace")] 247 | fn test_panicking_tracing_resurrecting() { 248 | reset_state(); 249 | 250 | thread_local! { 251 | static RESURRECTED: Cell>> = Cell::new(None); 252 | } 253 | 254 | struct DropGuard; // Used to clean up RESURRECTED 255 | 256 | impl Drop for DropGuard { 257 | fn drop(&mut self) { 258 | fn reset_panicking(panicking: &Panicking) { 259 | panicking.panic_on_trace.set(false); 260 | panicking.panic_on_finalize.set(false); 261 | panicking.panic_on_drop.set(false); 262 | } 263 | 264 | if let Some(replaced) = RESURRECTED.with(|cell| cell.replace(None)) { 265 | reset_panicking(&replaced); 266 | reset_panicking(replaced.cc.borrow().as_ref().unwrap()); 267 | // replaced is dropped here 268 | } 269 | collect_cycles(); // Reclaim memory 270 | } 271 | } 272 | 273 | let _drop_guard = DropGuard; 274 | 275 | struct Resurrecter { 276 | panicking: Cc, 277 | cyclic: RefCell>>, 278 | } 279 | 280 | unsafe impl Trace for Resurrecter { 281 | fn trace(&self, ctx: &mut Context<'_>) { 282 | self.panicking.trace(ctx); 283 | self.cyclic.trace(ctx); 284 | } 285 | } 286 | 287 | impl Finalize for Resurrecter { 288 | fn finalize(&self) { 289 | RESURRECTED.with(|res| res.set(Some(self.panicking.clone()))); 290 | } 291 | } 292 | 293 | { 294 | let a = Cc::new(Resurrecter { 295 | panicking: Cc::new(Panicking { 296 | trace_counter: Cell::new(2), 297 | panic_on_trace: Cell::new(true), 298 | panic_on_finalize: Cell::new(false), 299 | panic_on_drop: Cell::new(false), 300 | cc: RefCell::new(None), 301 | }), 302 | cyclic: RefCell::new(None), 303 | }); 304 | *a.panicking.cc.borrow_mut() = Some(Cc::new(Panicking { 305 | trace_counter: Cell::new(2), 306 | panic_on_trace: Cell::new(true), 307 | panic_on_finalize: Cell::new(false), 308 | panic_on_drop: Cell::new(false), 309 | cc: RefCell::new(Some(a.panicking.clone())), 310 | })); 311 | *a.cyclic.borrow_mut() = Some(a.clone()); 312 | 313 | register_panicking(&a); 314 | register_panicking(&a.panicking); 315 | register_panicking(a.panicking.cc.borrow().as_ref().unwrap()); 316 | drop(a); 317 | } 318 | 319 | panicking_collect_cycles(assert_state_not_collecting); 320 | } 321 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tests/cc.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_const_for_thread_local)] 2 | 3 | use std::cell::{Cell, RefCell}; 4 | use std::rc::{Rc, Weak}; 5 | 6 | use rust_cc::*; 7 | 8 | #[test] 9 | fn test_complex() { 10 | struct A { 11 | b: Cc, 12 | } 13 | 14 | struct B { 15 | c: Cc, 16 | } 17 | 18 | struct C { 19 | a: RefCell>>, 20 | b: RefCell>>, 21 | } 22 | 23 | struct D { 24 | c: Cc, 25 | } 26 | 27 | unsafe impl Trace for A { 28 | fn trace(&self, ctx: &mut Context<'_>) { 29 | self.b.trace(ctx); 30 | } 31 | } 32 | 33 | impl Finalize for A {} 34 | 35 | unsafe impl Trace for B { 36 | fn trace(&self, ctx: &mut Context<'_>) { 37 | self.c.trace(ctx); 38 | } 39 | } 40 | 41 | impl Finalize for B {} 42 | 43 | unsafe impl Trace for D { 44 | fn trace(&self, ctx: &mut Context<'_>) { 45 | self.c.trace(ctx); 46 | } 47 | } 48 | 49 | impl Finalize for D {} 50 | 51 | unsafe impl Trace for C { 52 | fn trace(&self, ctx: &mut Context<'_>) { 53 | self.b.trace(ctx); 54 | if let Some(cc) = &*self.a.borrow() { 55 | cc.trace(ctx); 56 | } 57 | } 58 | } 59 | 60 | impl Finalize for C {} 61 | 62 | let a = Cc::new(A { 63 | b: Cc::new(B { 64 | c: Cc::new(C { 65 | a: RefCell::new(None), 66 | b: RefCell::new(None), 67 | }), 68 | }), 69 | }); 70 | *a.b.c.a.borrow_mut() = Some(a.clone()); 71 | *a.b.c.b.borrow_mut() = Some(a.b.clone()); 72 | let d = Cc::new(D { c: a.b.c.clone() }); 73 | drop(a); 74 | collect_cycles(); 75 | if let Some(a) = &*d.c.a.borrow() { 76 | let _count = a.strong_count(); 77 | } 78 | drop(d); 79 | collect_cycles(); 80 | } 81 | 82 | /*#[test] 83 | fn useless_cyclic() { 84 | let _cc = Cc::::new_cyclic(|_| { 85 | collect_cycles(); 86 | { 87 | let _ = Cc::new(42); 88 | } 89 | collect_cycles(); 90 | 10 91 | }); 92 | collect_cycles(); 93 | drop(_cc); 94 | collect_cycles(); 95 | }*/ 96 | 97 | #[cfg(feature = "finalization")] 98 | #[test] 99 | fn test_finalization() { 100 | thread_local! { 101 | static FINALIZED: Cell = Cell::new(false); 102 | static DROPPED: Cell = Cell::new(false); 103 | static FINALIZEDB: Cell = Cell::new(false); 104 | static DROPPEDB: Cell = Cell::new(false); 105 | } 106 | 107 | fn assert_not_dropped() { 108 | assert!(!DROPPED.with(|cell| cell.get())); 109 | assert!(!DROPPEDB.with(|cell| cell.get())); 110 | } 111 | 112 | // C doesn't impl Trace, so it cannot be put inside a Cc 113 | struct C { 114 | a: RefCell>, 115 | } 116 | 117 | struct A { 118 | dropped: Cell, 119 | c: Weak, 120 | b: RefCell>>, 121 | } 122 | 123 | struct B { 124 | dropped: Cell, 125 | a: Cc
, 126 | } 127 | 128 | unsafe impl Trace for A { 129 | fn trace(&self, ctx: &mut Context<'_>) { 130 | if let Some(b) = &*self.b.borrow() { 131 | b.trace(ctx); 132 | } 133 | } 134 | } 135 | 136 | unsafe impl Trace for B { 137 | fn trace(&self, ctx: &mut Context<'_>) { 138 | self.a.trace(ctx); 139 | } 140 | } 141 | 142 | impl Finalize for B { 143 | fn finalize(&self) { 144 | FINALIZEDB.with(|cell| cell.set(true)); 145 | } 146 | } 147 | 148 | impl Finalize for A { 149 | fn finalize(&self) { 150 | FINALIZED.with(|cell| cell.set(true)); 151 | if let Some(c) = self.c.upgrade() { 152 | *c.a.borrow_mut() = Some(A { 153 | dropped: Cell::new(false), 154 | c: self.c.clone(), 155 | b: RefCell::new(self.b.borrow_mut().take()), 156 | }); 157 | } 158 | } 159 | } 160 | 161 | impl Drop for A { 162 | fn drop(&mut self) { 163 | assert!(!self.dropped.get()); 164 | self.dropped.set(true); 165 | DROPPED.with(|cell| cell.set(true)); 166 | } 167 | } 168 | 169 | impl Drop for B { 170 | fn drop(&mut self) { 171 | assert!(!self.dropped.get()); 172 | self.dropped.set(true); 173 | DROPPEDB.with(|cell| cell.set(true)); 174 | } 175 | } 176 | 177 | { 178 | let c1 = Rc::new(C { 179 | a: RefCell::new(None), 180 | }); 181 | 182 | let a = Cc::new(A { 183 | dropped: Cell::new(false), 184 | c: Rc::downgrade(&c1), 185 | b: RefCell::new(None), 186 | }); 187 | *a.b.borrow_mut() = Some(Cc::new(B { 188 | dropped: Cell::new(false), 189 | a: a.clone(), 190 | })); 191 | 192 | assert_eq!(a.strong_count(), 2); 193 | 194 | assert!(!FINALIZED.with(|cell| cell.get())); 195 | assert!(!FINALIZEDB.with(|cell| cell.get())); 196 | assert_not_dropped(); 197 | collect_cycles(); 198 | assert!(!FINALIZED.with(|cell| cell.get())); 199 | assert!(!FINALIZEDB.with(|cell| cell.get())); 200 | assert_not_dropped(); 201 | 202 | //let _c = a.c.clone(); 203 | 204 | assert!(!FINALIZED.with(|cell| cell.get())); 205 | assert!(!FINALIZEDB.with(|cell| cell.get())); 206 | assert_not_dropped(); 207 | drop(a); 208 | assert!(!FINALIZED.with(|cell| cell.get())); 209 | assert!(!FINALIZEDB.with(|cell| cell.get())); 210 | assert_not_dropped(); 211 | collect_cycles(); 212 | // a dropped here 213 | assert!(FINALIZED.with(|cell| cell.get())); 214 | assert!(FINALIZEDB.with(|cell| cell.get())); 215 | assert_not_dropped(); 216 | 217 | // Reset DROPPED 218 | FINALIZED.with(|cell| cell.set(false)); 219 | FINALIZEDB.with(|cell| cell.set(false)); 220 | 221 | match &*c1.a.borrow() { 222 | Some(a) => { 223 | assert!(!a.dropped.get()); 224 | a.b.borrow().iter().for_each(|b| { 225 | assert!(!b.dropped.get()); 226 | let _ = b.strong_count(); 227 | let _ = b.a.strong_count(); 228 | assert!(Weak::ptr_eq(&b.a.c, &a.c)); 229 | b.a.b.borrow().iter().for_each(|b| { 230 | assert!(!b.dropped.get()); 231 | let _ = b.strong_count(); 232 | }); 233 | }); 234 | }, 235 | None => panic!("None"), 236 | }; 237 | 238 | assert_not_dropped(); 239 | } 240 | collect_cycles(); 241 | 242 | // This tests that finalizers are called only one time per object 243 | assert!(!FINALIZED.with(|cell| cell.get())); 244 | assert!(!FINALIZEDB.with(|cell| cell.get())); 245 | 246 | assert!(DROPPED.with(|cell| cell.get())); 247 | assert!(DROPPEDB.with(|cell| cell.get())); 248 | } 249 | 250 | #[test] 251 | fn test_finalize_drop() { 252 | thread_local! { 253 | static FINALIZED: Cell = Cell::new(false); 254 | static DROPPED: Cell = Cell::new(false); 255 | static FINALIZEDB: Cell = Cell::new(false); 256 | static DROPPEDB: Cell = Cell::new(false); 257 | } 258 | 259 | fn assert_not_dropped() { 260 | assert!(!DROPPED.with(|cell| cell.get())); 261 | assert!(!DROPPEDB.with(|cell| cell.get())); 262 | } 263 | 264 | // C doesn't impl Trace, so it cannot be put inside a Cc 265 | struct C { 266 | _a: RefCell>, 267 | } 268 | 269 | struct A { 270 | dropped: Cell, 271 | _c: Weak, 272 | b: RefCell>>, 273 | } 274 | 275 | struct B { 276 | dropped: Cell, 277 | a: Cc, 278 | } 279 | 280 | unsafe impl Trace for A { 281 | fn trace(&self, ctx: &mut Context<'_>) { 282 | if let Some(b) = &*self.b.borrow() { 283 | b.trace(ctx); 284 | } 285 | } 286 | } 287 | 288 | unsafe impl Trace for B { 289 | fn trace(&self, ctx: &mut Context<'_>) { 290 | self.a.trace(ctx); 291 | } 292 | } 293 | 294 | impl Finalize for A { 295 | fn finalize(&self) { 296 | FINALIZED.with(|cell| cell.set(true)); 297 | } 298 | } 299 | 300 | impl Finalize for B { 301 | fn finalize(&self) { 302 | FINALIZEDB.with(|cell| cell.set(true)); 303 | } 304 | } 305 | 306 | impl Drop for A { 307 | fn drop(&mut self) { 308 | assert!(!self.dropped.get()); 309 | self.dropped.set(true); 310 | DROPPED.with(|cell| cell.set(true)); 311 | } 312 | } 313 | 314 | impl Drop for B { 315 | fn drop(&mut self) { 316 | assert!(!self.dropped.get()); 317 | self.dropped.set(true); 318 | DROPPEDB.with(|cell| cell.set(true)); 319 | } 320 | } 321 | 322 | let cc = Rc::new_cyclic(|weak| C { 323 | _a: RefCell::new(Some(A { 324 | dropped: Cell::new(false), 325 | _c: weak.clone(), 326 | b: RefCell::new(Some(Cc::new(B { 327 | dropped: Cell::new(false), 328 | a: Cc::new(A { 329 | dropped: Cell::new(false), 330 | _c: weak.clone(), 331 | b: RefCell::new(None), 332 | }), 333 | }))), 334 | })), 335 | }); 336 | 337 | assert!(!FINALIZED.with(|cell| cell.get())); 338 | assert!(!FINALIZEDB.with(|cell| cell.get())); 339 | assert_not_dropped(); 340 | 341 | drop(cc); 342 | 343 | collect_cycles(); 344 | if cfg!(feature = "finalization") { 345 | assert!(FINALIZED.with(|cell| cell.get())); 346 | assert!(FINALIZEDB.with(|cell| cell.get())); 347 | } 348 | assert!(DROPPED.with(|cell| cell.get())); 349 | assert!(DROPPEDB.with(|cell| cell.get())); 350 | } 351 | 352 | #[cfg(feature = "finalization")] 353 | #[test] 354 | fn finalization_test() { 355 | struct Circular { 356 | next: RefCell>>, 357 | does_finalization: Cell, 358 | } 359 | 360 | unsafe impl Trace for Circular { 361 | fn trace(&self, ctx: &mut Context<'_>) { 362 | self.next.trace(ctx); 363 | } 364 | } 365 | 366 | impl Finalize for Circular { 367 | fn finalize(&self) { 368 | if self.does_finalization.get() { 369 | *self.next.borrow_mut() = Some(Cc::new(Circular { 370 | next: self.next.clone(), 371 | does_finalization: Cell::new(false), 372 | })); 373 | self.does_finalization.set(false); 374 | } 375 | } 376 | } 377 | 378 | { 379 | let cc = Cc::new(Circular { 380 | next: RefCell::new(Some(Cc::new(Circular { 381 | next: RefCell::new(None), 382 | does_finalization: Cell::new(false), 383 | }))), 384 | does_finalization: Cell::new(true), 385 | }); 386 | *cc.next.borrow().as_ref().unwrap().next.borrow_mut() = Some(cc.clone()); 387 | } 388 | collect_cycles(); 389 | } 390 | 391 | // Code which created UB when Rc had Trace implemented 392 | // Commented to avoid compilation errors since Rc doesn't implement Trace anymore 393 | /*#[test] 394 | fn rc_test() { 395 | let mut rc = None; 396 | 397 | { 398 | let _cc = Cc::>::new_cyclic(|cc| { 399 | let rc_ = Rc::new(Cc::new(cc.clone())); 400 | rc = Some(rc_.clone()); 401 | Box::new(rc_) 402 | }); 403 | } 404 | 405 | collect_cycles(); 406 | 407 | assert!(rc.expect("rc not set").is_valid()); 408 | }*/ 409 | 410 | #[test] 411 | fn box_test() { 412 | { 413 | let cc = Cc::new(RefCell::new(None)); 414 | { 415 | *cc.borrow_mut() = Some(Box::new(cc.clone()) as Box); 416 | } 417 | } 418 | 419 | collect_cycles(); 420 | } 421 | 422 | #[test] 423 | fn alignment_test() { 424 | macro_rules! define_structs { 425 | ($($i:literal),+) => { 426 | $({ 427 | #[repr(align($i))] 428 | struct A { 429 | cc: RefCell>>, 430 | } 431 | 432 | unsafe impl Trace for A { 433 | fn trace(&self, ctx: &mut Context<'_>) { 434 | self.cc.trace(ctx); 435 | } 436 | } 437 | 438 | impl Finalize for A {} 439 | 440 | fn use_struct() { 441 | let cc = Cc::new(A { 442 | cc: RefCell::new(None), 443 | }); 444 | *cc.cc.borrow_mut() = Some(cc.clone()); 445 | } 446 | use_struct(); 447 | })+ 448 | collect_cycles(); 449 | }; 450 | } 451 | 452 | define_structs!(1, 2, 4, 8, 16, 32, 64, 128); 453 | } 454 | -------------------------------------------------------------------------------- /src/tests/weak/mod.rs: -------------------------------------------------------------------------------- 1 | use std::panic::catch_unwind; 2 | use crate::*; 3 | use super::*; 4 | use crate::weak::Weak; 5 | 6 | #[test] 7 | fn weak_test() { 8 | reset_state(); 9 | 10 | let (cc, weak) = weak_test_common(); 11 | drop(cc); 12 | assert_eq!(1, weak.weak_count()); 13 | assert_eq!(0, weak.strong_count()); 14 | assert!(weak.upgrade().is_none()); 15 | assert_eq!(1, weak.weak_count()); 16 | assert_eq!(0, weak.strong_count()); 17 | let weak3 = weak.clone(); 18 | assert!(Weak::ptr_eq(&weak, &weak3)); 19 | assert_eq!(2, weak.weak_count()); 20 | assert_eq!(0, weak.strong_count()); 21 | drop(weak3); 22 | assert_eq!(1, weak.weak_count()); 23 | assert_eq!(0, weak.strong_count()); 24 | drop(weak); 25 | } 26 | 27 | #[test] 28 | fn weak_test2() { 29 | reset_state(); 30 | 31 | let (cc, weak) = weak_test_common(); 32 | drop(weak); 33 | assert_eq!(1, cc.strong_count()); 34 | assert_eq!(0, cc.weak_count()); 35 | let weak2 = cc.downgrade(); 36 | assert_eq!(1, cc.strong_count()); 37 | assert_eq!(1, weak2.weak_count()); 38 | assert_eq!(cc.strong_count(), weak2.strong_count()); 39 | assert_eq!(weak2.weak_count(), cc.weak_count()); 40 | drop(weak2); 41 | drop(cc); 42 | collect_cycles(); 43 | } 44 | 45 | fn weak_test_common() -> (Cc, Weak) { 46 | reset_state(); 47 | 48 | let cc: Cc = Cc::new(0i32); 49 | 50 | assert!(!cc.inner().counter_marker().has_allocated_for_metadata()); 51 | assert_eq!(0, cc.weak_count()); 52 | assert_eq!(1, cc.strong_count()); 53 | 54 | let cc1 = cc.clone(); 55 | 56 | assert!(!cc.inner().counter_marker().has_allocated_for_metadata()); 57 | 58 | let weak = cc.downgrade(); 59 | 60 | assert!(cc.inner().counter_marker().has_allocated_for_metadata()); 61 | 62 | assert_eq!(2, cc.strong_count()); 63 | assert_eq!(1, weak.weak_count()); 64 | assert_eq!(cc.strong_count(), weak.strong_count()); 65 | assert_eq!(weak.weak_count(), cc.weak_count()); 66 | drop(cc1); 67 | assert_eq!(1, cc.strong_count()); 68 | assert_eq!(1, weak.weak_count()); 69 | collect_cycles(); 70 | assert_eq!(1, cc.strong_count()); 71 | assert_eq!(1, weak.weak_count()); 72 | assert_eq!(cc.strong_count(), weak.strong_count()); 73 | assert_eq!(weak.weak_count(), cc.weak_count()); 74 | let weak2 = weak.clone(); 75 | assert!(Weak::ptr_eq(&weak, &weak2)); 76 | assert_eq!(1, cc.strong_count()); 77 | assert_eq!(2, weak.weak_count()); 78 | assert_eq!(cc.strong_count(), weak.strong_count()); 79 | assert_eq!(weak.weak_count(), cc.weak_count()); 80 | let upgraded = weak.upgrade().expect("Couldn't upgrade"); 81 | assert!(Cc::ptr_eq(&cc, &upgraded)); 82 | assert_eq!(2, cc.strong_count()); 83 | assert_eq!(2, weak.weak_count()); 84 | assert_eq!(cc.strong_count(), weak.strong_count()); 85 | assert_eq!(weak.weak_count(), cc.weak_count()); 86 | drop(weak2); 87 | assert_eq!(2, cc.strong_count()); 88 | assert_eq!(1, weak.weak_count()); 89 | assert_eq!(cc.strong_count(), weak.strong_count()); 90 | assert_eq!(weak.weak_count(), cc.weak_count()); 91 | drop(upgraded); 92 | assert_eq!(1, cc.strong_count()); 93 | assert_eq!(1, weak.weak_count()); 94 | assert_eq!(cc.strong_count(), weak.strong_count()); 95 | assert_eq!(weak.weak_count(), cc.weak_count()); 96 | (cc, weak) 97 | } 98 | 99 | #[cfg(feature = "nightly")] 100 | #[test] 101 | fn weak_dst() { 102 | reset_state(); 103 | 104 | let cc = Cc::new(0i32); 105 | let cc1: Cc = cc.clone(); 106 | let _weak: Weak = cc.downgrade(); 107 | let _weak1: Weak = cc1.downgrade(); 108 | } 109 | 110 | #[test] 111 | fn test_new_cyclic() { 112 | reset_state(); 113 | 114 | struct Cyclic { 115 | weak: Weak, 116 | int: i32, 117 | } 118 | 119 | unsafe impl Trace for Cyclic { 120 | fn trace(&self, ctx: &mut Context<'_>) { 121 | self.weak.trace(ctx); 122 | } 123 | } 124 | 125 | impl Finalize for Cyclic {} 126 | 127 | let cyclic = Cc::new_cyclic(|weak| { 128 | assert_eq!(1, weak.weak_count()); 129 | assert_eq!(0, weak.strong_count()); 130 | assert!(weak.upgrade().is_none()); 131 | Cyclic { 132 | weak: weak.clone(), 133 | int: 5, 134 | } 135 | }); 136 | 137 | assert!(cyclic.inner().counter_marker().has_allocated_for_metadata()); 138 | 139 | assert_eq!(1, cyclic.weak_count()); 140 | assert_eq!(1, cyclic.strong_count()); 141 | 142 | assert_eq!(5, cyclic.int); 143 | assert!(Cc::ptr_eq(&cyclic.weak.upgrade().unwrap(), &cyclic)); 144 | } 145 | 146 | #[test] 147 | #[should_panic(expected = "Expected panic during panicking_new_cyclic1!")] 148 | fn panicking_new_cyclic1() { 149 | reset_state(); 150 | 151 | #[allow(clippy::unused_unit)] // Unit type needed to fix never type fallback error 152 | let _cc = Cc::new_cyclic(|_| -> () { 153 | panic!("Expected panic during panicking_new_cyclic1!"); 154 | }); 155 | } 156 | 157 | #[test] 158 | #[should_panic(expected = "Expected panic during panicking_new_cyclic2!")] 159 | fn panicking_new_cyclic2() { 160 | reset_state(); 161 | 162 | #[allow(clippy::unused_unit)] // Unit type needed to fix never type fallback error 163 | let _cc = Cc::new_cyclic(|weak| -> () { 164 | let _weak = weak.clone(); 165 | panic!("Expected panic during panicking_new_cyclic2!"); 166 | }); 167 | } 168 | 169 | #[test] 170 | fn panicking_saving_new_cyclic() { 171 | reset_state(); 172 | 173 | thread_local! { 174 | static SAVED: RefCell>> = RefCell::new(None); 175 | } 176 | 177 | struct Cyclic { 178 | weak: Weak, 179 | } 180 | 181 | unsafe impl Trace for Cyclic { 182 | fn trace(&self, ctx: &mut Context<'_>) { 183 | self.weak.trace(ctx); 184 | } 185 | } 186 | 187 | impl Finalize for Cyclic {} 188 | 189 | assert!(catch_unwind(|| { 190 | let _cc = Cc::new_cyclic(|weak| { 191 | SAVED.with(|saved| { 192 | *saved.borrow_mut() = Some(weak.clone()); 193 | }); 194 | panic!(); 195 | }); 196 | }).is_err()); 197 | 198 | SAVED.with(|saved| { 199 | let weak = &*saved.borrow(); 200 | assert!(weak.is_some()); 201 | let weak = weak.as_ref().unwrap(); 202 | assert!(weak.upgrade().is_none()); 203 | assert_eq!(1, weak.weak_count()); 204 | assert_eq!(0, weak.strong_count()); 205 | let _weak = weak.clone(); 206 | assert_eq!(2, weak.weak_count()); 207 | assert_eq!(0, weak.strong_count()); 208 | }); 209 | } 210 | 211 | #[test] 212 | fn try_upgrade_in_finalize_and_drop() { 213 | reset_state(); 214 | 215 | thread_local! { 216 | static TRACED: Cell = Cell::new(false); 217 | static FINALIZED: Cell = Cell::new(false); 218 | static DROPPED: Cell = Cell::new(false); 219 | } 220 | 221 | struct Cyclic { 222 | weak: Weak, 223 | } 224 | 225 | unsafe impl Trace for Cyclic { 226 | fn trace(&self, ctx: &mut Context<'_>) { 227 | TRACED.with(|traced| traced.set(true)); 228 | self.weak.trace(ctx); 229 | } 230 | } 231 | 232 | impl Finalize for Cyclic { 233 | fn finalize(&self) { 234 | FINALIZED.with(|finalized| finalized.set(true)); 235 | assert!(self.weak.upgrade().is_some()); 236 | } 237 | } 238 | 239 | impl Drop for Cyclic { 240 | fn drop(&mut self) { 241 | DROPPED.with(|dropped| dropped.set(true)); 242 | assert!(self.weak.upgrade().is_none()); 243 | } 244 | } 245 | 246 | let weak = { 247 | let cc = Cc::new_cyclic(|weak| Cyclic { 248 | weak: weak.clone(), 249 | }); 250 | assert_eq!(1, cc.strong_count()); 251 | cc.downgrade() 252 | // cc is dropped and collected automatically 253 | }; 254 | 255 | assert!(weak.upgrade().is_none()); 256 | assert_eq!(0, weak.strong_count()); 257 | assert_eq!(1, weak.weak_count()); 258 | 259 | // Shouldn't have traced 260 | assert!(!TRACED.with(|traced| traced.get())); 261 | if cfg!(feature = "finalization") { 262 | assert!(FINALIZED.with(|finalized| finalized.get())); 263 | } 264 | assert!(DROPPED.with(|dropped| dropped.get())); 265 | } 266 | 267 | #[cfg(feature = "finalization")] 268 | #[test] 269 | fn try_upgrade_and_resurrect_in_finalize_and_drop() { 270 | reset_state(); 271 | 272 | thread_local! { 273 | static RESURRECTED: Cell>> = Cell::new(None); 274 | } 275 | 276 | struct Cyclic { 277 | weak: Weak, 278 | } 279 | 280 | unsafe impl Trace for Cyclic { 281 | fn trace(&self, ctx: &mut Context<'_>) { 282 | self.weak.trace(ctx); 283 | } 284 | } 285 | 286 | impl Finalize for Cyclic { 287 | fn finalize(&self) { 288 | RESURRECTED.with(|r| r.set(Some(self.weak.upgrade().unwrap()))); 289 | } 290 | } 291 | 292 | impl Drop for Cyclic { 293 | fn drop(&mut self) { 294 | assert!(self.weak.upgrade().is_none()); 295 | } 296 | } 297 | 298 | { 299 | let cc = Cc::new_cyclic(|weak| Cyclic { 300 | weak: weak.clone(), 301 | }); 302 | assert_eq!(1, cc.strong_count()); 303 | // cc is dropped and collected automatically 304 | } 305 | 306 | RESURRECTED.with(|r| { 307 | let cc = r.replace(None).unwrap(); 308 | assert_eq!(1, cc.weak_count()); 309 | assert_eq!(1, cc.strong_count()); 310 | // cc is dropped here, finally freeing the allocation 311 | }); 312 | } 313 | 314 | #[test] 315 | fn try_upgrade_in_cyclic_finalize_and_drop() { 316 | reset_state(); 317 | 318 | thread_local! { 319 | static TRACED: Cell = Cell::new(false); 320 | static FINALIZED: Cell = Cell::new(false); 321 | static DROPPED: Cell = Cell::new(false); 322 | } 323 | 324 | struct Cyclic { 325 | cyclic: RefCell>>, 326 | weak: Weak, 327 | } 328 | 329 | unsafe impl Trace for Cyclic { 330 | fn trace(&self, ctx: &mut Context<'_>) { 331 | TRACED.with(|traced| traced.set(true)); 332 | self.cyclic.trace(ctx); 333 | self.weak.trace(ctx); 334 | } 335 | } 336 | 337 | impl Finalize for Cyclic { 338 | fn finalize(&self) { 339 | FINALIZED.with(|finalized| finalized.set(true)); 340 | assert!(self.weak.upgrade().is_some()); 341 | } 342 | } 343 | 344 | impl Drop for Cyclic { 345 | fn drop(&mut self) { 346 | DROPPED.with(|dropped| dropped.set(true)); 347 | assert!(self.weak.upgrade().is_none()); 348 | } 349 | } 350 | 351 | let weak: Weak = { 352 | let cc: Cc = Cc::new_cyclic(|weak| Cyclic { 353 | cyclic: RefCell::new(None), 354 | weak: weak.clone(), 355 | }); 356 | *cc.cyclic.borrow_mut() = Some(cc.clone()); 357 | assert_eq!(2, cc.strong_count()); 358 | cc.downgrade() 359 | }; 360 | 361 | assert_eq!(1, weak.strong_count()); 362 | assert_eq!(2, weak.weak_count()); 363 | 364 | collect_cycles(); 365 | 366 | assert!(weak.upgrade().is_none()); 367 | assert_eq!(0, weak.strong_count()); 368 | assert_eq!(1, weak.weak_count()); 369 | 370 | assert!(TRACED.with(|traced| traced.get())); 371 | if cfg!(feature = "finalization") { 372 | assert!(FINALIZED.with(|finalized| finalized.get())); 373 | } 374 | assert!(DROPPED.with(|dropped| dropped.get())); 375 | } 376 | 377 | #[test] 378 | fn weak_new() { 379 | reset_state(); 380 | 381 | let new: Weak = Weak::new(); 382 | 383 | assert_eq!(0, new.weak_count()); 384 | assert_eq!(0, new.strong_count()); 385 | assert!(new.upgrade().is_none()); 386 | 387 | let other = new.clone(); 388 | 389 | assert_eq!(0, other.weak_count()); 390 | assert_eq!(0, other.strong_count()); 391 | assert!(other.upgrade().is_none()); 392 | } 393 | 394 | #[test] 395 | fn weak_new_ptr_eq() { 396 | reset_state(); 397 | 398 | let new: Weak = Weak::new(); 399 | let other_new: Weak = Weak::new(); 400 | 401 | let cc = Cc::new(5u32); 402 | let other_cc = Cc::new(6u32); 403 | 404 | assert_eq!(0, new.weak_count()); 405 | assert_eq!(0, new.strong_count()); 406 | 407 | assert!(Weak::ptr_eq(&new, &new)); 408 | assert!(Weak::ptr_eq(&new, &other_new)); 409 | assert!(Weak::ptr_eq(&new, &new.clone())); 410 | assert!(!Weak::ptr_eq(&new, &cc.downgrade())); 411 | assert!(Weak::ptr_eq(&cc.downgrade(), &cc.downgrade())); 412 | assert!(!Weak::ptr_eq(&cc.downgrade(), &other_cc.downgrade())); 413 | } 414 | 415 | #[test] 416 | fn drop_zero_weak_counter() { 417 | reset_state(); 418 | 419 | let cc = Cc::new(5u32); 420 | let _ = cc.downgrade(); 421 | drop(cc); 422 | } 423 | 424 | #[test] 425 | fn cyclic_drop_zero_weak_counter() { 426 | reset_state(); 427 | 428 | struct Cyclic { 429 | cyclic: RefCell>>, 430 | } 431 | 432 | unsafe impl Trace for Cyclic { 433 | fn trace(&self, ctx: &mut Context<'_>) { 434 | self.cyclic.trace(ctx); 435 | } 436 | } 437 | 438 | impl Finalize for Cyclic {} 439 | 440 | let cc = Cc::new(Cyclic { 441 | cyclic: RefCell::new(None), 442 | }); 443 | *cc.cyclic.borrow_mut() = Some(cc.clone()); 444 | 445 | let _ = cc.downgrade(); 446 | drop(cc); 447 | collect_cycles(); 448 | } 449 | -------------------------------------------------------------------------------- /src/lists.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use core::ptr::NonNull; 3 | use core::cell::Cell; 4 | 5 | use crate::{CcBox, Mark}; 6 | 7 | pub(crate) struct LinkedList { 8 | first: Option>>, 9 | } 10 | 11 | impl LinkedList { 12 | #[inline] 13 | pub(crate) const fn new() -> Self { 14 | Self { first: None } 15 | } 16 | 17 | #[inline] 18 | pub(crate) fn first(&self) -> Option>> { 19 | self.first 20 | } 21 | 22 | #[inline] 23 | pub(crate) fn add(&mut self, ptr: NonNull>) { 24 | debug_assert_nones(ptr); 25 | 26 | if let Some(first) = self.first { 27 | unsafe { 28 | *first.as_ref().get_prev() = Some(ptr); 29 | *ptr.as_ref().get_next() = Some(first); 30 | } 31 | } 32 | 33 | self.first = Some(ptr); 34 | } 35 | 36 | #[inline] 37 | pub(crate) fn remove(&mut self, ptr: NonNull>) { 38 | unsafe { 39 | match (*ptr.as_ref().get_next(), *ptr.as_ref().get_prev()) { 40 | (Some(next), Some(prev)) => { 41 | // ptr is in between two elements 42 | *next.as_ref().get_prev() = Some(prev); 43 | *prev.as_ref().get_next() = Some(next); 44 | 45 | // Both next and prev are != None 46 | *ptr.as_ref().get_next() = None; 47 | *ptr.as_ref().get_prev() = None; 48 | }, 49 | (Some(next), None) => { 50 | // ptr is the first element 51 | *next.as_ref().get_prev() = None; 52 | self.first = Some(next); 53 | 54 | // Only next is != None 55 | *ptr.as_ref().get_next() = None; 56 | }, 57 | (None, Some(prev)) => { 58 | // ptr is the last element 59 | *prev.as_ref().get_next() = None; 60 | 61 | // Only prev is != None 62 | *ptr.as_ref().get_prev() = None; 63 | }, 64 | (None, None) => { 65 | // ptr is the only one in the list 66 | self.first = None; 67 | }, 68 | } 69 | debug_assert_nones(ptr); 70 | } 71 | } 72 | 73 | #[inline] 74 | pub(crate) fn remove_first(&mut self) -> Option>> { 75 | match self.first { 76 | Some(first) => unsafe { 77 | self.first = *first.as_ref().get_next(); 78 | if let Some(next) = self.first { 79 | *next.as_ref().get_prev() = None; 80 | } 81 | *first.as_ref().get_next() = None; 82 | // prev is already None since it's the first element 83 | 84 | // Make sure the mark is correct 85 | first.as_ref().counter_marker().mark(Mark::NonMarked); 86 | 87 | Some(first) 88 | }, 89 | None => { 90 | None 91 | }, 92 | } 93 | } 94 | 95 | #[inline] 96 | pub(crate) fn is_empty(&self) -> bool { 97 | self.first().is_none() 98 | } 99 | 100 | #[inline] 101 | pub(crate) fn iter(&self) -> Iter { 102 | self.into_iter() 103 | } 104 | } 105 | 106 | impl Drop for LinkedList { 107 | #[inline] 108 | fn drop(&mut self) { 109 | // Remove the remaining elements from the list 110 | while self.remove_first().is_some() { 111 | // remove_first already marks every removed element NonMarked 112 | } 113 | } 114 | } 115 | 116 | impl<'a> IntoIterator for &'a LinkedList { 117 | type Item = NonNull>; 118 | type IntoIter = Iter<'a>; 119 | 120 | #[inline] 121 | fn into_iter(self) -> Self::IntoIter { 122 | Iter { 123 | next: self.first, 124 | _phantom: PhantomData, 125 | } 126 | } 127 | } 128 | 129 | impl IntoIterator for LinkedList { 130 | type Item = NonNull>; 131 | type IntoIter = ListIter; 132 | 133 | #[inline] 134 | fn into_iter(self) -> Self::IntoIter { 135 | ListIter { 136 | list: self, 137 | } 138 | } 139 | } 140 | 141 | pub(crate) struct Iter<'a> { 142 | next: Option>>, 143 | _phantom: PhantomData<&'a CcBox<()>>, 144 | } 145 | 146 | impl Iter<'_> { 147 | #[inline] 148 | #[cfg(any(feature = "pedantic-debug-assertions", all(test, feature = "std")))] // Only used in pedantic-debug-assertions or unit tests 149 | pub(crate) fn contains(mut self, ptr: NonNull>) -> bool { 150 | self.any(|elem| elem == ptr) 151 | } 152 | } 153 | 154 | impl<'a> Iterator for Iter<'a> { 155 | type Item = NonNull>; 156 | 157 | #[inline] 158 | fn next(&mut self) -> Option { 159 | match self.next { 160 | Some(ptr) => { 161 | unsafe { 162 | self.next = *ptr.as_ref().get_next(); 163 | } 164 | Some(ptr) 165 | }, 166 | None => { 167 | None 168 | }, 169 | } 170 | } 171 | } 172 | 173 | pub(crate) struct ListIter { 174 | list: LinkedList, 175 | } 176 | 177 | impl Iterator for ListIter { 178 | type Item = NonNull>; 179 | 180 | #[inline] 181 | fn next(&mut self) -> Option { 182 | self.list.remove_first() 183 | } 184 | } 185 | 186 | pub(crate) struct PossibleCycles { 187 | first: Cell>>>, 188 | size: Cell, 189 | } 190 | 191 | impl PossibleCycles { 192 | #[inline] 193 | pub(crate) const fn new() -> Self { 194 | Self { 195 | first: Cell::new(None), 196 | size: Cell::new(0), 197 | } 198 | } 199 | 200 | #[inline] 201 | #[cfg(all(test, feature = "std"))] // Only used in unit tests 202 | pub(crate) fn reset(&self) { 203 | self.first.set(None); 204 | self.size.set(0); 205 | } 206 | 207 | #[inline] 208 | pub(crate) fn size(&self) -> usize { 209 | self.size.get() 210 | } 211 | 212 | #[inline] 213 | pub(crate) fn first(&self) -> Option>> { 214 | self.first.get() 215 | } 216 | 217 | #[inline] 218 | pub(crate) fn add(&self, ptr: NonNull>) { 219 | debug_assert_nones(ptr); 220 | 221 | self.size.set(self.size.get() + 1); 222 | 223 | if let Some(first) = self.first.get() { 224 | unsafe { 225 | *first.as_ref().get_prev() = Some(ptr); 226 | *ptr.as_ref().get_next() = Some(first); 227 | } 228 | } 229 | 230 | self.first.set(Some(ptr)); 231 | } 232 | 233 | #[inline] 234 | pub(crate) fn remove(&self, ptr: NonNull>) { 235 | self.size.set(self.size.get() - 1); 236 | 237 | unsafe { 238 | match (*ptr.as_ref().get_next(), *ptr.as_ref().get_prev()) { 239 | (Some(next), Some(prev)) => { 240 | // ptr is in between two elements 241 | *next.as_ref().get_prev() = Some(prev); 242 | *prev.as_ref().get_next() = Some(next); 243 | 244 | // Both next and prev are != None 245 | *ptr.as_ref().get_next() = None; 246 | *ptr.as_ref().get_prev() = None; 247 | }, 248 | (Some(next), None) => { 249 | // ptr is the first element 250 | *next.as_ref().get_prev() = None; 251 | self.first.set(Some(next)); 252 | 253 | // Only next is != None 254 | *ptr.as_ref().get_next() = None; 255 | }, 256 | (None, Some(prev)) => { 257 | // ptr is the last element 258 | *prev.as_ref().get_next() = None; 259 | 260 | // Only prev is != None 261 | *ptr.as_ref().get_prev() = None; 262 | }, 263 | (None, None) => { 264 | // ptr is the only one in the list 265 | self.first.set(None); 266 | }, 267 | } 268 | debug_assert_nones(ptr); 269 | } 270 | } 271 | 272 | #[inline] 273 | pub(crate) fn remove_first(&self) -> Option>> { 274 | match self.first.get() { 275 | Some(first) => unsafe { 276 | self.size.set(self.size.get() - 1); 277 | let new_first = *first.as_ref().get_next(); 278 | self.first.set(new_first); 279 | if let Some(next) = new_first { 280 | *next.as_ref().get_prev() = None; 281 | } 282 | *first.as_ref().get_next() = None; 283 | // prev is already None since it's the first element 284 | 285 | // Make sure the mark is correct 286 | first.as_ref().counter_marker().mark(Mark::NonMarked); 287 | 288 | Some(first) 289 | }, 290 | None => { 291 | None 292 | }, 293 | } 294 | } 295 | 296 | #[inline] 297 | pub(crate) fn is_empty(&self) -> bool { 298 | self.first().is_none() 299 | } 300 | 301 | /// # Safety 302 | /// * The elements in `to_append` must be already marked with `mark` mark 303 | /// * `to_append_size` must be the size of `to_append` 304 | #[inline] 305 | #[cfg(feature = "finalization")] 306 | pub(crate) unsafe fn mark_self_and_append(&self, mark: Mark, to_append: LinkedList, to_append_size: usize) { 307 | if let Some(mut prev) = self.first.get() { 308 | for elem in self.iter() { 309 | unsafe { 310 | elem.as_ref().counter_marker().reset_tracing_counter(); 311 | elem.as_ref().counter_marker().mark(mark); 312 | } 313 | prev = elem; 314 | } 315 | unsafe { 316 | if let Some(ptr) = to_append.first { 317 | *prev.as_ref().get_next() = to_append.first; 318 | *ptr.as_ref().get_prev() = Some(prev); 319 | } 320 | } 321 | } else { 322 | self.first.set(to_append.first); 323 | // to_append.first.prev is already None 324 | } 325 | self.size.set(self.size.get() + to_append_size); 326 | core::mem::forget(to_append); // Don't run to_append destructor 327 | } 328 | 329 | /// # Safety 330 | /// `to_swap_size` must be the size of `to_swap`. 331 | #[inline] 332 | #[cfg(feature = "finalization")] 333 | pub(crate) unsafe fn swap_list(&self, to_swap: &mut LinkedList, to_swap_size: usize) { 334 | self.size.set(to_swap_size); 335 | to_swap.first = self.first.replace(to_swap.first); 336 | } 337 | 338 | #[inline] 339 | #[cfg(any( 340 | feature = "pedantic-debug-assertions", 341 | feature = "finalization", 342 | all(test, feature = "std") // Unit tests 343 | ))] 344 | pub(crate) fn iter(&self) -> Iter { 345 | self.into_iter() 346 | } 347 | } 348 | 349 | impl Drop for PossibleCycles { 350 | #[inline] 351 | fn drop(&mut self) { 352 | // Remove the remaining elements from the list 353 | while self.remove_first().is_some() { 354 | // remove_first already marks every removed element NonMarked 355 | } 356 | } 357 | } 358 | 359 | impl<'a> IntoIterator for &'a PossibleCycles { 360 | type Item = NonNull>; 361 | type IntoIter = Iter<'a>; 362 | 363 | #[inline] 364 | fn into_iter(self) -> Self::IntoIter { 365 | Iter { 366 | next: self.first.get(), 367 | _phantom: PhantomData, 368 | } 369 | } 370 | } 371 | 372 | pub(crate) struct LinkedQueue { 373 | first: Option>>, 374 | last: Option>>, 375 | } 376 | 377 | impl LinkedQueue { 378 | #[inline] 379 | pub(crate) const fn new() -> Self { 380 | Self { 381 | first: None, 382 | last: None, 383 | } 384 | } 385 | 386 | #[inline] 387 | pub(crate) fn add(&mut self, ptr: NonNull>) { 388 | debug_assert_nones(ptr); 389 | 390 | if let Some(last) = self.last { 391 | unsafe { 392 | *last.as_ref().get_next() = Some(ptr); 393 | } 394 | } else { 395 | self.first = Some(ptr); 396 | } 397 | 398 | self.last = Some(ptr); 399 | } 400 | 401 | #[inline] 402 | pub(crate) fn peek(&self) -> Option>> { 403 | self.first 404 | } 405 | 406 | #[inline] 407 | pub(crate) fn poll(&mut self) -> Option>> { 408 | match self.first { 409 | Some(first) => unsafe { 410 | self.first = *first.as_ref().get_next(); 411 | if self.first.is_none() { 412 | // The last element is being removed 413 | self.last = None; 414 | } 415 | *first.as_ref().get_next() = None; 416 | 417 | // Make sure the mark is correct 418 | first.as_ref().counter_marker().mark(Mark::NonMarked); 419 | 420 | Some(first) 421 | }, 422 | None => { 423 | None 424 | }, 425 | } 426 | } 427 | 428 | #[inline] 429 | pub(crate) fn is_empty(&self) -> bool { 430 | self.peek().is_none() 431 | } 432 | } 433 | 434 | impl Drop for LinkedQueue { 435 | #[inline] 436 | fn drop(&mut self) { 437 | // Remove the remaining elements from the queue 438 | while self.poll().is_some() { 439 | // poll() already marks every removed element NonMarked 440 | } 441 | } 442 | } 443 | 444 | impl<'a> IntoIterator for &'a LinkedQueue { 445 | type Item = NonNull>; 446 | type IntoIter = Iter<'a>; 447 | 448 | #[inline] 449 | fn into_iter(self) -> Self::IntoIter { 450 | Iter { 451 | next: self.first, 452 | _phantom: PhantomData, 453 | } 454 | } 455 | } 456 | 457 | #[inline(always)] // The fn is always empty in release mode 458 | fn debug_assert_nones(ptr: NonNull>) { 459 | unsafe { 460 | debug_assert!((*ptr.as_ref().get_next()).is_none()); 461 | debug_assert!((*ptr.as_ref().get_prev()).is_none()); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/trace.rs: -------------------------------------------------------------------------------- 1 | use core::cell::RefCell; 2 | use core::ffi::CStr; 3 | use core::marker::PhantomData; 4 | use core::mem::ManuallyDrop; 5 | use core::num::{ 6 | NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, 7 | NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, 8 | }; 9 | use core::panic::AssertUnwindSafe; 10 | use core::sync::atomic::{ 11 | AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, 12 | AtomicU64, AtomicU8, AtomicUsize, 13 | }; 14 | use alloc::boxed::Box; 15 | use alloc::vec::Vec; 16 | use alloc::ffi::CString; 17 | use alloc::string::String; 18 | #[cfg(feature = "std")] 19 | use std::{ 20 | path::{Path, PathBuf}, 21 | ffi::{OsStr, OsString} 22 | }; 23 | 24 | use crate::lists::{LinkedList, LinkedQueue}; 25 | 26 | /// Trait to finalize objects before freeing them. 27 | /// 28 | /// Must be always implemented for every cycle-collectable object, even when `finalization` is disabled, to avoid cross-crate incompatibilities. 29 | /// When `finalization` is disabled, the [`finalize`] method will *never* be called. 30 | /// 31 | /// # Derive macro 32 | /// 33 | /// The [`Finalize`][`macro@crate::Finalize`] derive macro can be used to implement an empty finalizer: 34 | #[cfg_attr( 35 | feature = "derive", 36 | doc = r"```rust" 37 | )] 38 | #[cfg_attr( 39 | not(feature = "derive"), 40 | doc = r"```rust,ignore" 41 | )] 42 | #[doc = r"# use rust_cc::*; 43 | # use rust_cc_derive::*; 44 | #[derive(Finalize)] 45 | struct Foo { 46 | // ... 47 | } 48 | ```"] 49 | /// 50 | /// [`finalize`]: Finalize::finalize 51 | pub trait Finalize { 52 | /// The finalizer, which is called after an object becomes garbage and before [`drop`]ing it. 53 | /// 54 | /// By default, objects are finalized only once. Use the method [`Cc::finalize_again`] to make finalization happen again for a certain object. 55 | /// Also, objects created during the execution of a finalizer are not automatically finalized. 56 | /// 57 | /// # Default implementation 58 | /// 59 | /// The default implementation is empty. 60 | /// 61 | /// [`drop`]: core::ops::Drop::drop 62 | /// [`Cc::finalize_again`]: crate::Cc::finalize_again 63 | #[inline(always)] 64 | fn finalize(&self) {} 65 | } 66 | 67 | /// Trait to trace cycle-collectable objects. 68 | /// 69 | /// This trait is unsafe to implement, but can be safely derived using the [`Trace`][`macro@crate::Trace`] derive macro, which calls the [`trace`] method on every field: 70 | #[cfg_attr( 71 | feature = "derive", 72 | doc = r"```rust" 73 | )] 74 | #[cfg_attr( 75 | not(feature = "derive"), 76 | doc = r"```rust,ignore" 77 | )] 78 | #[doc = r"# use rust_cc::*; 79 | # use rust_cc_derive::*; 80 | # #[derive(Finalize)] 81 | #[derive(Trace)] 82 | struct Foo { 83 | a_field: Cc, 84 | another_field: Cc, 85 | } 86 | ```"] 87 | /// 88 | /// This trait is already implemented for common types from the standard library. 89 | /// 90 | /// # Safety 91 | /// The implementations of this trait must uphold the following invariants: 92 | /// * The [`trace`] implementation can trace (maximum once) every [`Cc`] instance *exclusively* owned by `self`. 93 | /// No other [`Cc`] instance can be traced. 94 | /// * It's always safe to panic. 95 | /// * During the same tracing phase (see below), two different [`trace`] calls on the same value must *behave the same*, i.e. they must trace the same 96 | /// [`Cc`] instances. 97 | /// If a panic happens during the second of such [`trace`] calls but not in the first one, then the [`Cc`] instances traced during the second call 98 | /// must be a subset of the [`Cc`] instances traced in the first one. 99 | /// Tracing can be detected using the [`state::is_tracing`] function. If it never returned `false` between two [`trace`] calls 100 | /// on the same value, then they are part of the same tracing phase. 101 | /// * The [`trace`] implementation must not create, clone, move, dereference or drop any [`Cc`]. 102 | /// * If the implementing type implements [`Drop`], then the [`Drop::drop`] implementation must not create, clone, move, dereference, drop or call 103 | /// any method on any [`Cc`] instance. 104 | /// 105 | /// # Implementation tips 106 | /// It is almost always preferable to use the derive macro `#[derive(Trace)]`, but in case a manual implementation is needed the following suggestions usually apply: 107 | /// * If a field's type implements [`Trace`], then call its [`trace`] method. 108 | /// * Try to avoid panicking if not strictly necessary, since it may lead to memory leaks. 109 | /// * Avoid mixing [`Cc`]s with other shared-ownership smart pointers like [`Rc`] (a [`Cc`] contained inside an [`Rc`] cannot be traced, 110 | /// since it's not owned *exclusively*). 111 | /// * Never tracing a field is always safe. 112 | /// * If you need to perform any clean up actions, you should do them in the [`Finalize::finalize`] implementation (instead of inside [`Drop::drop`]) 113 | /// or using a [cleaner](crate::cleaners). 114 | /// 115 | /// # Derive macro compatibility 116 | /// In order to improve the `Trace` derive macro usability and error messages, it is suggested to avoid implementing this trait for references or raw pointers 117 | /// (also considering that no pointed [`Cc`] may be traced, since a reference doesn't own what it refers to). 118 | /// 119 | /// [`trace`]: crate::Trace::trace 120 | /// [`state::is_tracing`]: crate::state::is_tracing 121 | /// [`Finalize::finalize`]: crate::Finalize::finalize 122 | /// [`Cc`]: crate::Cc 123 | /// [`Drop`]: core::ops::Drop 124 | /// [`Rc`]: alloc::rc::Rc 125 | /// [`Drop::drop`]: core::ops::Drop::drop 126 | pub unsafe trait Trace: Finalize { 127 | /// Traces the contained [`Cc`]s. See [`Trace`] for more information. 128 | /// 129 | /// [`Cc`]: crate::Cc 130 | fn trace(&self, ctx: &mut Context<'_>); 131 | } 132 | 133 | /// The tracing context provided to every invocation of [`Trace::trace`]. 134 | pub struct Context<'a> { 135 | inner: ContextInner<'a>, 136 | _phantom: PhantomData<*mut ()>, // Make Context !Send and !Sync 137 | } 138 | 139 | pub(crate) enum ContextInner<'a> { 140 | Counting { 141 | root_list: &'a mut LinkedList, 142 | non_root_list: &'a mut LinkedList, 143 | queue: &'a mut LinkedQueue, 144 | }, 145 | RootTracing { 146 | non_root_list: &'a mut LinkedList, 147 | queue: &'a mut LinkedQueue, 148 | }, 149 | } 150 | 151 | impl<'b> Context<'b> { 152 | #[inline] 153 | #[must_use] 154 | pub(crate) const fn new(ctxi: ContextInner) -> Context { 155 | Context { 156 | inner: ctxi, 157 | _phantom: PhantomData, 158 | } 159 | } 160 | 161 | #[inline] 162 | pub(crate) fn inner<'a>(&'a mut self) -> &'a mut ContextInner<'b> 163 | where 164 | 'b: 'a, 165 | { 166 | &mut self.inner 167 | } 168 | } 169 | 170 | // ################################# 171 | // # Trace impls # 172 | // ################################# 173 | 174 | macro_rules! empty_trace { 175 | ($($this:ty),*,) => { 176 | $( 177 | unsafe impl $crate::trace::Trace for $this { 178 | #[inline(always)] 179 | fn trace(&self, _: &mut $crate::trace::Context<'_>) {} 180 | } 181 | 182 | impl $crate::trace::Finalize for $this { 183 | } 184 | )* 185 | }; 186 | } 187 | 188 | empty_trace! { 189 | (), 190 | bool, 191 | isize, 192 | usize, 193 | i8, 194 | u8, 195 | i16, 196 | u16, 197 | i32, 198 | u32, 199 | i64, 200 | u64, 201 | i128, 202 | u128, 203 | f32, 204 | f64, 205 | char, 206 | str, 207 | CStr, 208 | String, 209 | CString, 210 | NonZeroIsize, 211 | NonZeroUsize, 212 | NonZeroI8, 213 | NonZeroU8, 214 | NonZeroI16, 215 | NonZeroU16, 216 | NonZeroI32, 217 | NonZeroU32, 218 | NonZeroI64, 219 | NonZeroU64, 220 | NonZeroI128, 221 | NonZeroU128, 222 | AtomicBool, 223 | AtomicIsize, 224 | AtomicUsize, 225 | AtomicI8, 226 | AtomicU8, 227 | AtomicI16, 228 | AtomicU16, 229 | AtomicI32, 230 | AtomicU32, 231 | AtomicI64, 232 | AtomicU64, 233 | } 234 | 235 | #[cfg(feature = "std")] 236 | empty_trace! { 237 | Path, 238 | OsStr, 239 | PathBuf, 240 | OsString, 241 | } 242 | 243 | // Removed since these impls are error-prone. Making a Cc> and then casting it to Cc 244 | // doesn't make T traced during tracing, since the impls for MaybeUninit are empty and the vtable is saved when calling Cc::new 245 | /*unsafe impl Trace for MaybeUninit { 246 | /// This does nothing, since memory may be uninit. 247 | #[inline(always)] 248 | fn trace(&self, _: &mut Context<'_>) {} 249 | } 250 | 251 | impl Finalize for MaybeUninit { 252 | /// This does nothing, since memory may be uninit. 253 | #[inline(always)] 254 | fn finalize(&self) {} 255 | }*/ 256 | 257 | unsafe impl Trace for PhantomData { 258 | #[inline(always)] 259 | fn trace(&self, _: &mut Context<'_>) {} 260 | } 261 | 262 | impl Finalize for PhantomData {} 263 | 264 | macro_rules! deref_trace { 265 | ($generic:ident; $this:ty; $($bound:tt)*) => { 266 | unsafe impl<$generic: $($bound)* $crate::trace::Trace> $crate::trace::Trace for $this 267 | { 268 | #[inline] 269 | fn trace(&self, ctx: &mut $crate::trace::Context<'_>) { 270 | let deref: &$generic = <$this as ::core::ops::Deref>::deref(self); 271 | <$generic as $crate::trace::Trace>::trace(deref, ctx); 272 | } 273 | } 274 | 275 | impl<$generic: $($bound)* $crate::trace::Finalize> $crate::trace::Finalize for $this 276 | { 277 | #[inline] 278 | fn finalize(&self) { 279 | let deref: &$generic = <$this as ::core::ops::Deref>::deref(self); 280 | <$generic as $crate::trace::Finalize>::finalize(deref); 281 | } 282 | } 283 | } 284 | } 285 | 286 | macro_rules! deref_traces { 287 | ($($this:tt),*,) => { 288 | $( 289 | deref_trace!{T; $this; ?::core::marker::Sized +} 290 | )* 291 | } 292 | } 293 | 294 | macro_rules! deref_traces_sized { 295 | ($($this:tt),*,) => { 296 | $( 297 | deref_trace!{T; $this; } 298 | )* 299 | } 300 | } 301 | 302 | deref_traces! { 303 | Box, 304 | ManuallyDrop, 305 | } 306 | 307 | deref_traces_sized! { 308 | AssertUnwindSafe, 309 | } 310 | 311 | unsafe impl Trace for RefCell { 312 | #[inline] 313 | fn trace(&self, ctx: &mut Context<'_>) { 314 | if let Ok(borrow) = self.try_borrow_mut() { 315 | borrow.trace(ctx); 316 | } 317 | } 318 | } 319 | 320 | impl Finalize for RefCell { 321 | #[inline] 322 | fn finalize(&self) { 323 | if let Ok(borrow) = self.try_borrow() { 324 | borrow.finalize(); 325 | } 326 | } 327 | } 328 | 329 | unsafe impl Trace for Option { 330 | #[inline] 331 | fn trace(&self, ctx: &mut Context<'_>) { 332 | if let Some(inner) = self { 333 | inner.trace(ctx); 334 | } 335 | } 336 | } 337 | 338 | impl Finalize for Option { 339 | #[inline] 340 | fn finalize(&self) { 341 | if let Some(value) = self { 342 | value.finalize(); 343 | } 344 | } 345 | } 346 | 347 | unsafe impl Trace for Result { 348 | #[inline] 349 | fn trace(&self, ctx: &mut Context<'_>) { 350 | match self { 351 | Ok(ok) => ok.trace(ctx), 352 | Err(err) => err.trace(ctx), 353 | } 354 | } 355 | } 356 | 357 | impl Finalize for Result { 358 | #[inline] 359 | fn finalize(&self) { 360 | match self { 361 | Ok(value) => value.finalize(), 362 | Err(err) => err.finalize(), 363 | } 364 | } 365 | } 366 | 367 | unsafe impl Trace for [T; N] { 368 | #[inline] 369 | fn trace(&self, ctx: &mut Context<'_>) { 370 | for elem in self { 371 | elem.trace(ctx); 372 | } 373 | } 374 | } 375 | 376 | impl Finalize for [T; N] { 377 | #[inline] 378 | fn finalize(&self) { 379 | for elem in self { 380 | elem.finalize(); 381 | } 382 | } 383 | } 384 | 385 | unsafe impl Trace for [T] { 386 | #[inline] 387 | fn trace(&self, ctx: &mut Context<'_>) { 388 | for elem in self { 389 | elem.trace(ctx); 390 | } 391 | } 392 | } 393 | 394 | impl Finalize for [T] { 395 | #[inline] 396 | fn finalize(&self) { 397 | for elem in self { 398 | elem.finalize(); 399 | } 400 | } 401 | } 402 | 403 | unsafe impl Trace for Vec { 404 | #[inline] 405 | fn trace(&self, ctx: &mut Context<'_>) { 406 | for elem in self { 407 | elem.trace(ctx); 408 | } 409 | } 410 | } 411 | 412 | impl Finalize for Vec { 413 | #[inline] 414 | fn finalize(&self) { 415 | for elem in self { 416 | elem.finalize(); 417 | } 418 | } 419 | } 420 | 421 | macro_rules! tuple_finalize_trace { 422 | ($($args:ident),+) => { 423 | #[allow(non_snake_case)] 424 | unsafe impl<$($args),*> $crate::trace::Trace for ($($args,)*) 425 | where $($args: $crate::trace::Trace),* 426 | { 427 | #[inline] 428 | fn trace(&self, ctx: &mut $crate::trace::Context<'_>) { 429 | match self { 430 | ($($args,)*) => { 431 | $( 432 | <$args as $crate::trace::Trace>::trace($args, ctx); 433 | )* 434 | } 435 | } 436 | } 437 | } 438 | 439 | #[allow(non_snake_case)] 440 | impl<$($args),*> $crate::trace::Finalize for ($($args,)*) 441 | where $($args: $crate::trace::Finalize),* 442 | { 443 | #[inline] 444 | fn finalize(&self) { 445 | match self { 446 | ($($args,)*) => { 447 | $( 448 | <$args as $crate::trace::Finalize>::finalize($args); 449 | )* 450 | } 451 | } 452 | } 453 | } 454 | } 455 | } 456 | 457 | macro_rules! tuple_finalize_traces { 458 | ($(($($args:ident),+);)*) => { 459 | $( 460 | tuple_finalize_trace!($($args),*); 461 | )* 462 | } 463 | } 464 | 465 | tuple_finalize_traces! { 466 | (A); 467 | (A, B); 468 | (A, B, C); 469 | (A, B, C, D); 470 | (A, B, C, D, E); 471 | (A, B, C, D, E, F); 472 | (A, B, C, D, E, F, G); 473 | (A, B, C, D, E, F, G, H); 474 | (A, B, C, D, E, F, G, H, I); 475 | (A, B, C, D, E, F, G, H, I, J); 476 | (A, B, C, D, E, F, G, H, I, J, K); 477 | (A, B, C, D, E, F, G, H, I, J, K, L); 478 | } 479 | --------------------------------------------------------------------------------