1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! A pointer type for heap allocation using the Zend memory manager.
//!
//! Heap memory in PHP is usually request-bound and allocated inside [memory
//! arenas], which are cleared at the start and end of a PHP request. Allocating
//! and freeing memory that is allocated on the Zend heap is done through two
//! separate functions [`efree`] and [`emalloc`].
//!
//! As such, most heap-allocated PHP types **cannot** be allocated on the stack,
//! such as [`ZendStr`], which is a dynamically-sized type, and therefore must
//! be allocated on the heap. A regular [`Box`] would not work in this case, as
//! the memory needs to be freed from a separate function `zend_string_release`.
//! The [`ZBox`] type provides a wrapper which calls the relevant release
//! functions based on the type and what is inside the implementation of
//! [`ZBoxable`].
//!
//! This type is not created directly, but rather through a function implemented
//! on the downstream type. For example, [`ZendStr`] has a function `new` which
//! returns a [`ZBox<ZendStr>`].
//!
//! [memory arenas]: https://en.wikipedia.org/wiki/Region-based_memory_management
//! [`ZendStr`]: crate::types::ZendStr
//! [`emalloc`]: super::alloc::efree

use std::{
    borrow::Borrow,
    fmt::Debug,
    mem::ManuallyDrop,
    ops::{Deref, DerefMut},
    ptr::NonNull,
};

use super::alloc::efree;

/// A pointer type for heap allocation using the Zend memory manager.
///
/// See the [module level documentation](../index.html) for more.
pub struct ZBox<T: ZBoxable>(NonNull<T>);

impl<T: ZBoxable> ZBox<T> {
    /// Creates a new box from a given pointer.
    ///
    /// # Parameters
    ///
    /// * `ptr` - A non-null, well-aligned pointer to a `T`.
    ///
    /// # Safety
    ///
    /// Caller must ensure that `ptr` is non-null, well-aligned and pointing to
    /// a `T`.
    pub unsafe fn from_raw(ptr: *mut T) -> Self {
        Self(NonNull::new_unchecked(ptr))
    }

    /// Returns the pointer contained by the box, dropping the box in the
    /// process. The data pointed to by the returned pointer is not
    /// released.
    ///
    /// # Safety
    ///
    /// The caller is responsible for managing the memory pointed to by the
    /// returned pointer, including freeing the memory.
    pub fn into_raw(self) -> &'static mut T {
        let mut this = ManuallyDrop::new(self);
        // SAFETY: All constructors ensure the contained pointer is well-aligned and
        // dereferenceable.
        unsafe { this.0.as_mut() }
    }
}

impl<T: ZBoxable> Drop for ZBox<T> {
    #[inline]
    fn drop(&mut self) {
        self.deref_mut().free()
    }
}

impl<T: ZBoxable> Deref for ZBox<T> {
    type Target = T;

    #[inline]
    fn deref(&self) -> &Self::Target {
        // SAFETY: All constructors ensure the contained pointer is well-aligned and
        // dereferenceable.
        unsafe { self.0.as_ref() }
    }
}

impl<T: ZBoxable> DerefMut for ZBox<T> {
    #[inline]
    fn deref_mut(&mut self) -> &mut Self::Target {
        // SAFETY: All constructors ensure the contained pointer is well-aligned and
        // dereferenceable.
        unsafe { self.0.as_mut() }
    }
}

impl<T: ZBoxable + Debug> Debug for ZBox<T> {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        (**self).fmt(f)
    }
}

impl<T: ZBoxable> Borrow<T> for ZBox<T> {
    #[inline]
    fn borrow(&self) -> &T {
        self
    }
}

impl<T: ZBoxable> AsRef<T> for ZBox<T> {
    #[inline]
    fn as_ref(&self) -> &T {
        self
    }
}

/// Implemented on types that can be heap allocated using the Zend memory
/// manager. These types are stored inside a [`ZBox`] when heap-allocated, and
/// the [`free`] method is called when the box is dropped.
///
/// # Safety
///
/// The default implementation of the [`free`] function uses the [`efree`]
/// function to free the memory without calling any destructors.
///
/// The implementor must ensure that any time a pointer to the implementor is
/// passed into a [`ZBox`] that the memory pointed to was allocated by the Zend
/// memory manager.
///
/// [`free`]: #method.free
pub unsafe trait ZBoxable {
    /// Frees the memory pointed to by `self`, calling any destructors required
    /// in the process.
    fn free(&mut self) {
        unsafe { efree(self as *mut _ as *mut u8) };
    }
}