Skip to main content

ext_php_rs/zend/
module.rs

1//! Builder and objects for creating modules in PHP. A module is the base of a
2//! PHP extension.
3
4use std::cell::UnsafeCell;
5use std::ffi::CString;
6use std::mem::MaybeUninit;
7use std::os::raw::c_char;
8use std::ptr;
9use std::sync::Once;
10
11use crate::ffi::zend_module_entry;
12
13fn zend_type_has_name(type_mask: u32) -> bool {
14    cfg_if::cfg_if! {
15        if #[cfg(php83)] {
16            (type_mask & crate::ffi::_ZEND_TYPE_LITERAL_NAME_BIT) != 0
17        } else {
18            (type_mask & crate::ffi::_ZEND_TYPE_NAME_BIT) != 0
19        }
20    }
21}
22
23/// A Zend module entry, also known as an extension.
24pub type ModuleEntry = zend_module_entry;
25
26impl ModuleEntry {
27    /// Allocates the module entry on the heap, returning a pointer to the
28    /// memory location. The caller is responsible for the memory pointed to.
29    #[deprecated(note = "use StaticModuleEntry to avoid leaking the allocation")]
30    #[must_use]
31    pub fn into_raw(self) -> *mut Self {
32        Box::into_raw(Box::new(self))
33    }
34}
35
36/// Static storage for a [`ModuleEntry`] that avoids heap allocation.
37///
38/// Mimics how C extensions declare a `static zend_module_entry`. The entry
39/// lives in the shared library's data segment and is reclaimed automatically
40/// when PHP calls `DL_UNLOAD`.
41pub struct StaticModuleEntry {
42    init: Once,
43    inner: UnsafeCell<MaybeUninit<ModuleEntry>>,
44}
45
46unsafe impl Sync for StaticModuleEntry {}
47
48impl Default for StaticModuleEntry {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl StaticModuleEntry {
55    /// Creates a new uninitialized static module entry.
56    #[must_use]
57    pub const fn new() -> Self {
58        Self {
59            init: Once::new(),
60            inner: UnsafeCell::new(MaybeUninit::uninit()),
61        }
62    }
63
64    /// Initialises the entry on first call, returning a stable `*mut` pointer.
65    ///
66    /// Subsequent calls skip `f` and return the same pointer.
67    pub fn get_or_init(&self, f: impl FnOnce() -> ModuleEntry) -> *mut ModuleEntry {
68        self.init.call_once(|| unsafe {
69            (*self.inner.get()).write(f());
70        });
71        unsafe { (*self.inner.get()).as_mut_ptr() }
72    }
73}
74
75/// Frees every heap allocation that ext-php-rs placed inside a
76/// [`ModuleEntry`]: the `name`/`version` `CString`s, the `functions` boxed
77/// slice, and all nested `fname`/`arg_info`/`default_value`/class-name
78/// pointers.
79///
80/// # Safety
81///
82/// * Must be called **exactly once**, during MSHUTDOWN, **before** PHP calls
83///   `DL_UNLOAD`.
84/// * All pointer fields must originate from ext-php-rs (`CString::into_raw` /
85///   `Box::into_raw`). Calling this on a module built by hand or by C is UB.
86pub unsafe fn cleanup_module_allocations(entry: *mut ModuleEntry) {
87    let entry = unsafe { &mut *entry };
88
89    if !entry.name.is_null() {
90        unsafe { drop(CString::from_raw(entry.name.cast_mut())) };
91        entry.name = ptr::null();
92    }
93    if !entry.version.is_null() {
94        unsafe { drop(CString::from_raw(entry.version.cast_mut())) };
95        entry.version = ptr::null();
96    }
97
98    if entry.functions.is_null() {
99        return;
100    }
101
102    let funcs = entry.functions.cast_mut();
103    let mut count: usize = 0;
104
105    while !unsafe { (*funcs.add(count)).fname }.is_null() {
106        let func = unsafe { &mut *funcs.add(count) };
107
108        unsafe { drop(CString::from_raw(func.fname.cast_mut())) };
109        func.fname = ptr::null();
110
111        // arg_info[0].name is `required_num_args` cast to a pointer, not a CString.
112        if !func.arg_info.is_null() {
113            let n = func.num_args as usize;
114            let base = func.arg_info.cast_mut();
115
116            for i in 0..=n {
117                let arg = unsafe { &mut *base.add(i) };
118
119                if i > 0 && !arg.name.is_null() {
120                    unsafe { drop(CString::from_raw(arg.name.cast_mut())) };
121                }
122                if !arg.default_value.is_null() {
123                    unsafe { drop(CString::from_raw(arg.default_value.cast_mut())) };
124                }
125                if !arg.type_.ptr.is_null() && zend_type_has_name(arg.type_.type_mask) {
126                    unsafe { drop(CString::from_raw(arg.type_.ptr.cast::<c_char>())) };
127                }
128            }
129
130            unsafe { drop(Box::from_raw(ptr::slice_from_raw_parts_mut(base, n + 1))) };
131        }
132
133        count += 1;
134    }
135
136    unsafe {
137        drop(Box::from_raw(ptr::slice_from_raw_parts_mut(
138            funcs,
139            count + 1,
140        )));
141    }
142    entry.functions = ptr::null();
143}