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
//! 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`]: super::types::string::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 dereferencable.
        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 dereferencable.
        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 dereferencable.
        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) };
    }
}