erased_type_arena/
lib.rs

1//! A type-erased allocation arena with proper dropping. It is just like [`typed-arena`], but the
2//! generic type parameter is erased from the arena and an arena is capable of allocating values of
3//! different types. Furthermore, potential use-after-free vulnerabilities due to the improper
4//! implementation of the `drop` function is prevented by dynamic checks.
5//!
6//! # Motivation
7//!
8//! Implementing a graph-like data structure in 100% safe Rust is not easy since a graph node may
9//! be shared by multiple nodes, which inherently violates the ownership rule of Rust. A typical
10//! approach to overcome this is to allocate the graph nodes in an **allocation arena**, and each
11//! node is shared by multiple other nodes via immutable references to interior-mutable containers
12//! such as [`RefCell`]. We can illustrate the approach by the following code definitions:
13//!
14//! ```ignore
15//! struct GraphContext {
16//!     node_arena: Arena,
17//! }
18//!
19//! impl GraphContext {
20//!     fn alloc_node<'ctx>(&'ctx self) -> &'ctx RefCell<GraphNode<'ctx>> {
21//!         self.node_arena.alloc(RefCell::new(GraphNode {
22//!             other_nodes: Vec::new(),
23//!         }))
24//!     }
25//! }
26//!
27//! struct GraphNode<'ctx> {
28//!     other_nodes: Vec<&'ctx RefCell<GraphNode<'ctx>>>,
29//! }
30//! ```
31//!
32//! We can choose the arena allocator provided by the [`bumpalo`] crate as our node allocation
33//! arena. In most cases this works just fine. However, if the graph node implements the [`Drop`]
34//! trait, [`bumpalo`] is out of option since its provided arena allocator does not support
35//! executing the drop function when the arena itself is being dropped.
36//!
37//! [`typed-arena`] is another crate providing an allocation arena that performs proper dropping
38//! of the allocated value when the arena itself is being dropped. However, the type of the arena
39//! provided by [`typed-arena`] requires a generic type parameter indicating the type of the values
40//! that can be allocated by the arena. This minor difference made it infeasible in our graph
41//! structure example since the lifetime annotation of `GraphContext` will now be propagated to
42//! itself:
43//!
44//! ```ignore
45//! struct GraphContext<'ctx> {  // The `'ctx` lifetime notation here is clearly inappropriate
46//!     node_arena: Arena<RefCell<GraphContext<'ctx>>>,
47//! }
48//!
49//! impl GraphContext {
50//!     fn alloc_node<'ctx>(&'ctx self) -> &'ctx RefCell<GraphNode<'ctx>> {
51//!         self.node_arena.alloc(RefCell::new(GraphNode {
52//!             other_nodes: Vec::new(),
53//!         }))
54//!     }
55//! }
56//!
57//! struct GraphNode<'ctx> {
58//!     other_nodes: Vec<&'ctx RefCell<GraphNode<'ctx>>>,
59//! }
60//! ```
61//!
62//! To overcome the limitations of the allocation arenas above, this crate provides an allocation
63//! arena that:
64//! * Properly drops the allocated value when the arena itself is being dropped, just like what
65//!   [`typed-arena`] does;
66//! * The arena can allocate values of different types and the generic type parameter is erased from
67//!   the arena's type. Instead, the generic type parameter is moved to the `alloc` function.
68//!
69//! # Drop Safety
70//!
71//! The `drop` function of the allocated values, if not properly implemented, can lead to
72//! use-after-free vulnerabilities. More specifically, references to values allocated in an arena
73//! can be dangling when the arena itself is being dropped. The following example proves this:
74//!
75//! ```ignore
76//! struct GraphNode<'ctx> {
77//!     data: i32,
78//!     other_nodes: Vec<&'ctx GraphNode<'ctx>>,
79//! }
80//!
81//! impl<'ctx> Drop for GraphNode<'ctx> {
82//!     fn drop(&mut self) {
83//!         let mut s = 0;
84//!         for node in &self.other_nodes {
85//!             // The reference `node` which points to other nodes allocated in the same arena may
86//!             // dangle here.
87//!             s += node.data;
88//!         }
89//!         println!("{}", s);
90//!     }
91//! }
92//! ```
93//!
94//! To solve this problem, this crate provides two safe wrappers [`ArenaMut`] and [`ArenaRef`]
95//! around mutable and immutable references to allocated values. Each time the safe wrapper is
96//! [`Deref`]-ed, it checks whether the referenced value has been dropped. If, unfortunately, the
97//! referenced value has been dropped, it panics the program and thus prevents undefined behaviors
98//! from happening.
99//!
100//! # Usage
101//!
102//! The [`Arena`] struct represents an allocation arena.
103//!
104//! [`bumpalo`]: https://crates.io/crates/bumpalo
105//! [`typed-arena`]: https://crates.io/crates/typed-arena
106//!
107
108#![no_std]
109
110extern crate alloc;
111extern crate core;
112#[cfg(test)]
113extern crate std;
114
115mod utils;
116
117use alloc::alloc::{alloc, dealloc, Layout};
118use alloc::boxed::Box;
119use alloc::sync::Arc;
120use core::borrow::{Borrow, BorrowMut};
121use core::cell::Cell;
122use core::fmt::{Debug, Display, Formatter};
123use core::ops::{Deref, DerefMut};
124use core::ptr::{drop_in_place, NonNull};
125
126use crate::utils::linked_list::ConcurrentLinkedList;
127
128/// A type-erased allocation arena with proper dropping.
129pub struct Arena {
130    objects: ConcurrentLinkedList<ArenaBox>,
131}
132
133impl Arena {
134    /// Create a new arena.
135    pub fn new() -> Self {
136        Self {
137            objects: ConcurrentLinkedList::new(),
138        }
139    }
140
141    /// Allocate and initialize a new value in the arena.
142    ///
143    /// This function returns a safe wrapper around a mutable reference to the allocated value. When
144    /// being `Deref`-ed, it performs safety checks to ensure that the referenced value has not been
145    /// dropped.
146    pub fn alloc<'s, T: 's>(&'s self, value: T) -> AllocMut<'s, T> {
147        let arena_box = ArenaBox::new(value);
148        let object_ptr = arena_box.object;
149        let dropped_flag = arena_box.dropped.clone();
150        self.objects.push_front(arena_box);
151
152        AllocMut {
153            value: unsafe { object_ptr.cast().as_mut() },
154            dropped: dropped_flag,
155        }
156    }
157
158    /// Allocate and initialize a new value in the arena.
159    ///
160    /// This function is unsafe in the manner that a raw reference is returned rather than a safe
161    /// wrapper that checks the value has not been dropped when `Deref`-ed. This may lead to
162    /// potential use-after-free vulnerabilities as described in the crate-level documentation.
163    pub unsafe fn alloc_unchecked<'s, T: 's>(&'s self, value: T) -> &'s mut T {
164        let arena_box = ArenaBox::new(value);
165        let object_ptr = arena_box.object;
166        self.objects.push_front(arena_box);
167
168        object_ptr.cast().as_mut()
169    }
170}
171
172/// A safe wrapper around a mutable reference to a value allocated in an arena. It's the mutable
173/// counterpart of [`AllocRef`].
174///
175/// This wrapper type can be `Deref`-ed to the allocated type. When being `Deref`-ed, this wrapper
176/// checks that the referenced value has not been dropped due to the dropping of the arena. For more
177/// explanation about why this could happen, you can refer to the crate-level documentation.
178pub struct AllocMut<'a, T: ?Sized> {
179    value: &'a mut T,
180    dropped: Arc<Cell<bool>>,
181}
182
183impl<'a, T: ?Sized> AllocMut<'a, T> {
184    /// Get an immutable reference to the allocated value.
185    ///
186    /// This function panics if the referenced value has been dropped.
187    pub fn get(&self) -> &T {
188        self.ensure_not_dropped();
189        self.value
190    }
191
192    /// Get a mutable reference to the allocated value.
193    ///
194    /// This function panics if the referenced value has been dropped.
195    pub fn get_mut(&mut self) -> &mut T {
196        self.ensure_not_dropped();
197        self.value
198    }
199
200    /// Get an immutable reference to the allocated value, without safety checks.
201    pub unsafe fn get_unchecked(&self) -> &T {
202        self.value
203    }
204
205    /// Get a mutable reference to the allocated value, without safety checks.
206    //noinspection RsSelfConvention
207    pub unsafe fn get_mut_unchecked(&mut self) -> &mut T {
208        self.value
209    }
210
211    /// Determine whether the referenced value has been dropped.
212    pub fn dropped(&self) -> bool {
213        self.dropped.get()
214    }
215
216    /// Consume this safety wrapper and leak the mutable reference to the allocated value.
217    ///
218    /// This function panics if the referenced value has been dropped.
219    pub unsafe fn leak(self) -> &'a mut T {
220        self.ensure_not_dropped();
221        self.value
222    }
223
224    /// Consume this safety wrapper and leak the mutable reference to the allocated value, without
225    /// safety checks.
226    pub unsafe fn leak_unchecked(self) -> &'a mut T {
227        self.value
228    }
229
230    /// Ensure that the referenced value has not been dropped.
231    ///
232    /// This function panics if the referenced value has been dropped.
233    fn ensure_not_dropped(&self) {
234        assert!(
235            !self.dropped(),
236            "The allocated object requesting for use has been dropped"
237        );
238    }
239}
240
241impl<'a, T: ?Sized> AsRef<T> for AllocMut<'a, T> {
242    fn as_ref(&self) -> &T {
243        self.get()
244    }
245}
246
247impl<'a, T: ?Sized> AsMut<T> for AllocMut<'a, T> {
248    fn as_mut(&mut self) -> &mut T {
249        self.get_mut()
250    }
251}
252
253impl<'a, T: ?Sized> Borrow<T> for AllocMut<'a, T> {
254    fn borrow(&self) -> &T {
255        self.get()
256    }
257}
258
259impl<'a, T: ?Sized> BorrowMut<T> for AllocMut<'a, T> {
260    fn borrow_mut(&mut self) -> &mut T {
261        self.get_mut()
262    }
263}
264
265impl<'a, T> Debug for AllocMut<'a, T>
266where
267    T: ?Sized + Debug,
268{
269    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
270        f.write_fmt(format_args!("{:?}", self.get()))
271    }
272}
273
274impl<'a, T: ?Sized> Deref for AllocMut<'a, T> {
275    type Target = T;
276
277    fn deref(&self) -> &Self::Target {
278        self.get()
279    }
280}
281
282impl<'a, T: ?Sized> DerefMut for AllocMut<'a, T> {
283    fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
284        self.get_mut()
285    }
286}
287
288impl<'a, T> Display for AllocMut<'a, T>
289where
290    T: ?Sized + Display,
291{
292    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
293        f.write_fmt(format_args!("{}", self.get()))
294    }
295}
296
297/// A safe wrapper around an immutable reference to a value allocated in an arena. It's the
298/// immutable counterpart of [`AllocMut`].
299///
300/// This wrapper type can be `Deref`-ed to the allocated type. When being `Deref`-ed, this wrapper
301/// checks that the referenced value has not been dropped due to the dropping of the arena. For more
302/// explanation about why this could happen, you can refer to the crate-level documentation.
303#[derive(Clone)]
304pub struct AllocRef<'a, T: ?Sized> {
305    value: &'a T,
306    dropped: Arc<Cell<bool>>,
307}
308
309impl<'a, T> AllocRef<'a, T>
310where
311    T: ?Sized,
312{
313    /// Get an immutable reference to the allocated value.
314    ///
315    /// This function panics if the allocated value has been dropped.
316    pub fn get(&self) -> &T {
317        self.ensure_not_dropped();
318        self.value
319    }
320
321    /// Get an immutable reference to the allocated value, without safety checks.
322    pub unsafe fn get_unchecked(&self) -> &T {
323        self.value
324    }
325
326    /// Determine whether the allocated value is dropped.
327    pub fn dropped(&self) -> bool {
328        self.dropped.get()
329    }
330
331    /// Consume this `AllocRef` and get the immutable reference to the allocated value.
332    ///
333    /// This function panics if the allocated value has been dropped.
334    pub unsafe fn leak(self) -> &'a T {
335        self.ensure_not_dropped();
336        self.value
337    }
338
339    /// Consume this `AllocRef` and get the immutable reference to the allocated value, without
340    /// safety checks.
341    pub unsafe fn leak_unchecked(self) -> &'a T {
342        self.value
343    }
344
345    /// Ensures that the allocated value is not dropped.
346    ///
347    /// This function panics if the allocated value has been dropped.
348    fn ensure_not_dropped(&self) {
349        assert!(
350            !self.dropped(),
351            "The allocated object requesting for use has been dropped"
352        );
353    }
354}
355
356impl<'a, T> AsRef<T> for AllocRef<'a, T>
357where
358    T: ?Sized,
359{
360    fn as_ref(&self) -> &T {
361        self.get()
362    }
363}
364
365impl<'a, T> Borrow<T> for AllocRef<'a, T>
366where
367    T: ?Sized,
368{
369    fn borrow(&self) -> &T {
370        self.get()
371    }
372}
373
374impl<'a, T> Debug for AllocRef<'a, T>
375where
376    T: ?Sized + Debug,
377{
378    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
379        f.write_fmt(format_args!("{:?}", self.value))
380    }
381}
382
383impl<'a, T> Deref for AllocRef<'a, T> {
384    type Target = T;
385
386    fn deref(&self) -> &Self::Target {
387        self.get()
388    }
389}
390
391impl<'a, T> Display for AllocRef<'a, T>
392where
393    T: ?Sized + Display,
394{
395    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
396        f.write_fmt(format_args!("{}", self.value))
397    }
398}
399
400impl<'a, T> From<AllocMut<'a, T>> for AllocRef<'a, T> {
401    fn from(ptr: AllocMut<'a, T>) -> Self {
402        Self {
403            value: ptr.value,
404            dropped: ptr.dropped,
405        }
406    }
407}
408
409/// A type-erased smart pointer to an arena-allocated value.
410///
411/// The smart pointer will properly drop the allocated value upon the dropping of the arena.
412///
413/// The smart pointer also maintains a boolean flag indicating whether the allocated value has been
414/// dropped, which [`AllocMut`] wrappers rely on to perform safety checks.
415///
416/// [`AllocMut`]: ../struct.AllocMut.html
417struct ArenaBox {
418    /// Pointer to the allocated value.
419    object: NonNull<u8>,
420
421    /// The function used for dropping the allocated value.
422    dropper: Box<dyn FnMut()>,
423
424    /// A boolean flag indicating whether the allocated value has been dropped.
425    dropped: Arc<Cell<bool>>,
426}
427
428impl ArenaBox {
429    /// Allocate and initialize a value of type `T` and create an `ArenaBox` value referencing to
430    /// the allocated value.
431    fn new<T>(value: T) -> Self {
432        // Allocate memory suitable for holding a value of type `T`.
433        let layout = Layout::new::<T>();
434        let object = unsafe { NonNull::new(alloc(layout)).expect("alloc returns null pointer") };
435
436        // Initialize a value in the allocated memory.
437        unsafe {
438            core::ptr::write(object.cast::<T>().as_ptr(), value);
439        }
440
441        // Create a dropper function that can be used for dropping the initialized value.
442        let dropper = Box::new(move || unsafe {
443            drop_in_place(object.as_ptr() as *mut T);
444            dealloc(object.as_ptr(), layout);
445        });
446
447        Self {
448            object,
449            dropper,
450            dropped: Arc::new(Cell::new(false)),
451        }
452    }
453
454    /// Set the internal dropped flag.
455    fn mark_as_dropped(&self) {
456        self.dropped.set(true);
457    }
458}
459
460impl Drop for ArenaBox {
461    fn drop(&mut self) {
462        self.mark_as_dropped();
463        (self.dropper)();
464    }
465}
466
467#[cfg(test)]
468mod tests {
469    use alloc::vec::Vec;
470    use core::cell::RefCell;
471
472    use super::*;
473
474    mod arena_tests {
475        use super::*;
476
477        #[test]
478        fn test_alloc() {
479            let arena = Arena::new();
480            let value = arena.alloc(10);
481            assert_eq!(*value.get(), 10);
482
483            let value = arena.alloc(20);
484            assert_eq!(*value.get(), 20);
485        }
486
487        #[test]
488        fn test_alloc_unsafe() {
489            let arena = Arena::new();
490            let value = unsafe { arena.alloc_unchecked(10) };
491            assert_eq!(*value, 10);
492
493            let value = unsafe { arena.alloc_unchecked(20) };
494            assert_eq!(*value, 20);
495        }
496
497        #[test]
498        fn test_drop_empty_arena() {
499            let _arena = Arena::new();
500        }
501
502        #[test]
503        fn test_drop() {
504            struct Mock<'a> {
505                data: i32,
506                output: &'a RefCell<Vec<i32>>,
507            }
508
509            impl<'a> Drop for Mock<'a> {
510                fn drop(&mut self) {
511                    self.output.borrow_mut().push(self.data);
512                }
513            }
514
515            let output = RefCell::new(Vec::new());
516            let arena = Arena::new();
517            arena.alloc(Mock {
518                data: 10,
519                output: &output,
520            });
521            arena.alloc(Mock {
522                data: 20,
523                output: &output,
524            });
525
526            drop(arena);
527
528            let output = output.borrow().clone();
529            assert_eq!(output, alloc::vec![20, 10]);
530        }
531    }
532
533    mod alloc_mut_tests {
534        use super::*;
535
536        #[test]
537        #[should_panic]
538        fn test_use_dropped_value() {
539            struct Mock<'a> {
540                data: i32,
541                another: Option<AllocMut<'a, Mock<'a>>>,
542            }
543
544            impl<'a> Drop for Mock<'a> {
545                fn drop(&mut self) {
546                    if let Some(another) = &mut self.another {
547                        another.data = 0;
548                    }
549                }
550            }
551
552            let arena = Arena::new();
553            let mut first = arena.alloc(Mock {
554                data: 10,
555                another: None,
556            });
557            let second = arena.alloc(Mock {
558                data: 20,
559                another: None,
560            });
561
562            first.another = Some(second);
563
564            // The following statement should panic.
565            drop(arena);
566        }
567    }
568
569    mod alloc_ref_tests {
570        use super::*;
571
572        #[test]
573        #[should_panic]
574        fn test_use_dropped_value() {
575            struct Mock<'a, 'b> {
576                output: Option<&'b mut i32>,
577                data: i32,
578                another: Option<AllocRef<'a, Mock<'a, 'b>>>,
579            }
580
581            impl<'a, 'b> Drop for Mock<'a, 'b> {
582                fn drop(&mut self) {
583                    let output = self.output.take();
584                    if let Some(another) = &self.another {
585                        if let Some(output) = output {
586                            *output = another.data;
587                        }
588                    }
589                }
590            }
591
592            let arena = Arena::new();
593            let mut output = 0;
594            let mut first = arena.alloc(Mock {
595                output: Some(&mut output),
596                data: 20,
597                another: None,
598            });
599            let second = arena.alloc(Mock {
600                output: None,
601                data: 10,
602                another: None,
603            });
604
605            first.another = Some(second.into());
606
607            // The following statement should panic.
608            drop(arena);
609        }
610    }
611}