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