Skip to main content

ext_php_rs/
boxed.rs

1//! A pointer type for heap allocation using the Zend memory manager.
2//!
3//! Heap memory in PHP is usually request-bound and allocated inside [memory
4//! arenas], which are cleared at the start and end of a PHP request. Allocating
5//! and freeing memory that is allocated on the Zend heap is done through two
6//! separate functions [`efree`] and [`emalloc`].
7//!
8//! As such, most heap-allocated PHP types **cannot** be allocated on the stack,
9//! such as [`ZendStr`], which is a dynamically-sized type, and therefore must
10//! be allocated on the heap. A regular [`Box`] would not work in this case, as
11//! the memory needs to be freed from a separate function `zend_string_release`.
12//! The [`ZBox`] type provides a wrapper which calls the relevant release
13//! functions based on the type and what is inside the implementation of
14//! [`ZBoxable`].
15//!
16//! This type is not created directly, but rather through a function implemented
17//! on the downstream type. For example, [`ZendStr`] has a function `new` which
18//! returns a [`ZBox<ZendStr>`].
19//!
20//! [memory arenas]: https://en.wikipedia.org/wiki/Region-based_memory_management
21//! [`ZendStr`]: crate::types::ZendStr
22//! [`emalloc`]: super::alloc::emalloc
23
24use std::{
25    borrow::Borrow,
26    fmt::Debug,
27    mem::ManuallyDrop,
28    ops::{Deref, DerefMut},
29    ptr::{self, NonNull},
30};
31
32use super::alloc::efree;
33
34/// A pointer type for heap allocation using the Zend memory manager.
35///
36/// See the [module level documentation](../index.html) for more.
37pub struct ZBox<T: ZBoxable>(NonNull<T>);
38
39impl<T: ZBoxable> ZBox<T> {
40    /// Creates a new box from a given pointer.
41    ///
42    /// # Parameters
43    ///
44    /// * `ptr` - A non-null, well-aligned pointer to a `T`.
45    ///
46    /// # Safety
47    ///
48    /// Caller must ensure that `ptr` is non-null, well-aligned and pointing to
49    /// a `T`.
50    pub unsafe fn from_raw(ptr: *mut T) -> Self {
51        Self(unsafe { NonNull::new_unchecked(ptr) })
52    }
53
54    /// Creates a new box from a pointer returned by a Zend allocation.
55    ///
56    /// # Parameters
57    ///
58    /// * `ptr` - A well-aligned pointer to a `T`, freshly allocated by the
59    ///   engine.
60    ///
61    /// # Safety
62    ///
63    /// Caller must ensure that `ptr` is well-aligned and pointing to a `T`.
64    ///
65    /// # Panics
66    ///
67    /// Panics if the engine returned a null pointer.
68    #[expect(
69        clippy::expect_used,
70        reason = "the Zend allocator aborts the process on OOM, so the pointer is never null"
71    )]
72    pub(crate) unsafe fn from_zend_alloc(ptr: *mut T) -> Self {
73        Self(NonNull::new(ptr).expect("the Zend allocator returned a null pointer"))
74    }
75
76    /// Returns the pointer contained by the box, dropping the box in the
77    /// process. The data pointed to by the returned pointer is not
78    /// released.
79    ///
80    /// Mirrors [`Box::into_raw`]: the pointer is non-null and well-aligned, and
81    /// the caller becomes responsible for releasing it, typically by passing it
82    /// back to [`ZBox::from_raw`] or to the engine.
83    #[must_use]
84    pub fn into_raw(self) -> *mut T {
85        ManuallyDrop::new(self).0.as_ptr()
86    }
87}
88
89impl<T: ZBoxable> Drop for ZBox<T> {
90    #[inline]
91    fn drop(&mut self) {
92        self.deref_mut().free();
93    }
94}
95
96impl<T: ZBoxable> Deref for ZBox<T> {
97    type Target = T;
98
99    #[inline]
100    fn deref(&self) -> &Self::Target {
101        // SAFETY: All constructors ensure the contained pointer is well-aligned and
102        // dereferenceable.
103        unsafe { self.0.as_ref() }
104    }
105}
106
107impl<T: ZBoxable> DerefMut for ZBox<T> {
108    #[inline]
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        // SAFETY: All constructors ensure the contained pointer is well-aligned and
111        // dereferenceable.
112        unsafe { self.0.as_mut() }
113    }
114}
115
116impl<T: ZBoxable + Debug> Debug for ZBox<T> {
117    #[inline]
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        (**self).fmt(f)
120    }
121}
122
123impl<T: ZBoxable> Borrow<T> for ZBox<T> {
124    #[inline]
125    fn borrow(&self) -> &T {
126        self
127    }
128}
129
130impl<T: ZBoxable> AsRef<T> for ZBox<T> {
131    #[inline]
132    fn as_ref(&self) -> &T {
133        self
134    }
135}
136
137/// Implemented on types that can be heap allocated using the Zend memory
138/// manager. These types are stored inside a [`ZBox`] when heap-allocated, and
139/// the [`free`] method is called when the box is dropped.
140///
141/// # Safety
142///
143/// The default implementation of the [`free`] function uses the [`efree`]
144/// function to free the memory without calling any destructors.
145///
146/// The implementor must ensure that any time a pointer to the implementor is
147/// passed into a [`ZBox`] that the memory pointed to was allocated by the Zend
148/// memory manager.
149///
150/// [`free`]: #method.free
151pub unsafe trait ZBoxable {
152    /// Frees the memory pointed to by `self`, calling any destructors required
153    /// in the process.
154    fn free(&mut self) {
155        unsafe { efree(ptr::from_mut(self).cast::<u8>()) };
156    }
157}