• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

extphprs / ext-php-rs / 25379163335

05 May 2026 01:25PM UTC coverage: 72.479% (+6.2%) from 66.241%
25379163335

Pull #734

github

web-flow
Merge 889e3cde4 into 0912e7c24
Pull Request #734: feat!: PHP 8 union, intersection, DNF, and class-union type hints

2871 of 3074 new or added lines in 21 files covered. (93.4%)

3 existing lines in 2 files now uncovered.

11514 of 15886 relevant lines covered (72.48%)

33.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.33
/src/zend/module.rs
1
//! Builder and objects for creating modules in PHP. A module is the base of a
2
//! PHP extension.
3

4
use std::cell::UnsafeCell;
5
use std::ffi::CString;
6
use std::mem::MaybeUninit;
7
use std::os::raw::c_char;
8
use std::ptr;
9
use std::sync::Once;
10

11
use crate::ffi::zend_module_entry;
12

13
fn 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.
24
pub type ModuleEntry = zend_module_entry;
25

26
impl 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`.
41
pub struct StaticModuleEntry {
42
    init: Once,
43
    inner: UnsafeCell<MaybeUninit<ModuleEntry>>,
44
}
45

46
unsafe impl Sync for StaticModuleEntry {}
47

48
impl Default for StaticModuleEntry {
49
    fn default() -> Self {
×
50
        Self::new()
×
51
    }
×
52
}
53

54
impl 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 {
10✔
68
        self.init.call_once(|| unsafe {
10✔
69
            (*self.inner.get()).write(f());
3✔
70
        });
3✔
71
        unsafe { (*self.inner.get()).as_mut_ptr() }
10✔
72
    }
10✔
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.
86
pub 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
                }
×
NEW
125
                if !arg.type_.ptr.is_null() {
×
NEW
126
                    if (arg.type_.type_mask & crate::ffi::_ZEND_TYPE_LIST_BIT) != 0 {
×
NEW
127
                        // Zend frees the `zend_type_list` itself at MSHUTDOWN
×
NEW
128
                        // through `zend_type_release` -> `pefree(_, 1)`. See
×
NEW
129
                        // `Zend/zend_opcode.c:112-124` in php-src. Touching it
×
NEW
130
                        // here would double-free.
×
NEW
131
                    } else if zend_type_has_name(arg.type_.type_mask) {
×
NEW
132
                        unsafe { drop(CString::from_raw(arg.type_.ptr.cast::<c_char>())) };
×
NEW
133
                    }
×
UNCOV
134
                }
×
135
            }
136

137
            unsafe { drop(Box::from_raw(ptr::slice_from_raw_parts_mut(base, n + 1))) };
×
138
        }
×
139

140
        count += 1;
×
141
    }
142

143
    unsafe {
×
144
        drop(Box::from_raw(ptr::slice_from_raw_parts_mut(
×
145
            funcs,
×
146
            count + 1,
×
147
        )));
×
148
    }
×
149
    entry.functions = ptr::null();
×
150
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc