Skip to main content

ext_php_rs/
alloc.rs

1//! Functions relating to the Zend Memory Manager, used to allocate
2//! request-bound memory.
3
4use cfg_if::cfg_if;
5
6use crate::ffi::{_efree, _emalloc, _estrdup, ZEND_MM_ALIGNMENT};
7use std::{
8    alloc::Layout,
9    ffi::{CString, c_char, c_void},
10};
11
12/// Uses the PHP memory allocator to allocate request-bound memory.
13///
14/// The Zend allocator only guarantees `ZEND_MM_ALIGNMENT` (8 bytes) and has
15/// no aligned-allocation entry point, so a `layout` requesting a stricter
16/// alignment cannot be honoured and yields a null pointer.
17///
18/// # Parameters
19///
20/// * `layout` - The layout of the requested memory.
21///
22/// # Returns
23///
24/// A pointer to the memory allocated, or null if `layout.align()` exceeds
25/// `ZEND_MM_ALIGNMENT`.
26#[must_use]
27pub fn emalloc(layout: Layout) -> *mut u8 {
28    if layout.align() > ZEND_MM_ALIGNMENT.unsigned_abs() {
29        return std::ptr::null_mut();
30    }
31    let size = layout.size();
32
33    (unsafe {
34        cfg_if! {
35            if #[cfg(php_debug)] {
36                #[allow(clippy::used_underscore_items)]
37                _emalloc(size as _, std::ptr::null_mut(), 0, std::ptr::null_mut(), 0)
38            } else {
39                #[allow(clippy::used_underscore_items)]
40                _emalloc(size as _)
41            }
42        }
43    })
44    .cast::<u8>()
45}
46
47/// Frees a given memory pointer which was allocated through the PHP memory
48/// manager.
49///
50/// # Parameters
51///
52/// * `ptr` - The pointer to the memory to free.
53///
54/// # Safety
55///
56/// Caller must guarantee that the given pointer is valid (aligned and non-null)
57/// and was originally allocated through the Zend memory manager.
58pub unsafe fn efree(ptr: *mut u8) {
59    cfg_if! {
60        if #[cfg(php_debug)] {
61            #[allow(clippy::used_underscore_items)]
62            unsafe {
63                _efree(
64                    ptr.cast::<c_void>(),
65                    std::ptr::null_mut(),
66                    0,
67                    std::ptr::null_mut(),
68                    0,
69                );
70            }
71        } else {
72            #[allow(clippy::used_underscore_items)]
73            unsafe {
74                _efree(ptr.cast::<c_void>());
75            }
76        }
77    }
78}
79
80/// Duplicates a string using the PHP memory manager.
81///
82/// # Parameters
83///
84/// * `string` - The string to duplicate, which can be any type that can be
85///   converted into a `Vec<u8>`.
86///
87/// # Returns
88///
89/// A pointer to the duplicated string in the PHP memory manager.
90pub fn estrdup(string: impl Into<Vec<u8>>) -> *mut c_char {
91    let string = unsafe { CString::from_vec_unchecked(string.into()) }.into_raw();
92
93    let result = unsafe {
94        cfg_if! {
95            if #[cfg(php_debug)] {
96                #[allow(clippy::used_underscore_items)]
97                _estrdup(string, std::ptr::null_mut(), 0, std::ptr::null_mut(), 0)
98            } else {
99                #[allow(clippy::used_underscore_items)]
100                _estrdup(string)
101            }
102        }
103    };
104
105    drop(unsafe { CString::from_raw(string) });
106    result
107}
108
109#[cfg(test)]
110#[cfg(feature = "embed")]
111mod test {
112    use super::*;
113    use crate::embed::Embed;
114    use std::ffi::CStr;
115
116    #[test]
117    fn test_emalloc() {
118        Embed::run(|| {
119            let layout = Layout::from_size_align(16, 8).expect("should create layout");
120            let ptr = emalloc(layout);
121            assert!(!ptr.is_null());
122            unsafe { efree(ptr) };
123        });
124    }
125
126    #[test]
127    fn test_estrdup() {
128        Embed::run(|| {
129            let original = "Hello, world!";
130            let duplicated = estrdup(original);
131            assert!(!duplicated.is_null());
132
133            let duplicated_str = unsafe { CStr::from_ptr(duplicated) };
134            assert_eq!(
135                duplicated_str.to_str().expect("should convert to str"),
136                original
137            );
138
139            unsafe { efree(duplicated.cast::<u8>()) }
140        });
141    }
142}