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}