ext_php_rs/zend/
handlers.rs

1use std::{ffi::c_void, mem::MaybeUninit, os::raw::c_int, ptr};
2
3use crate::{
4    class::RegisteredClass,
5    exception::PhpResult,
6    ffi::{
7        std_object_handlers, zend_is_true, zend_object_handlers, zend_object_std_dtor,
8        zend_std_get_properties, zend_std_has_property, zend_std_read_property,
9        zend_std_write_property,
10    },
11    flags::ZvalTypeFlags,
12    types::{ZendClassObject, ZendHashTable, ZendObject, ZendStr, Zval},
13};
14
15/// A set of functions associated with a PHP class.
16pub type ZendObjectHandlers = zend_object_handlers;
17
18impl ZendObjectHandlers {
19    /// Creates a new set of object handlers based on the standard object
20    /// handlers.
21    #[must_use]
22    pub fn new<T: RegisteredClass>() -> ZendObjectHandlers {
23        let mut this = MaybeUninit::uninit();
24
25        // SAFETY: `this` is allocated on the stack and is a valid memory location.
26        unsafe { Self::init::<T>(&raw mut *this.as_mut_ptr()) };
27
28        // SAFETY: We just initialized the handlers in the previous statement, therefore
29        // we are returning a valid object.
30        unsafe { this.assume_init() }
31    }
32
33    /// Initializes a given set of object handlers by copying the standard
34    /// object handlers into the memory location, as well as setting up the
35    /// `T` type destructor.
36    ///
37    /// # Parameters
38    ///
39    /// * `ptr` - Pointer to memory location to copy the standard handlers to.
40    ///
41    /// # Safety
42    ///
43    /// Caller must guarantee that the `ptr` given is a valid memory location.
44    ///
45    /// # Panics
46    ///
47    /// * If the offset of the `T` type is not a valid `i32` value.
48    pub unsafe fn init<T: RegisteredClass>(ptr: *mut ZendObjectHandlers) {
49        unsafe { ptr::copy_nonoverlapping(&raw const std_object_handlers, ptr, 1) };
50        let offset = ZendClassObject::<T>::std_offset();
51        unsafe { (*ptr).offset = offset.try_into().expect("Invalid offset") };
52        unsafe { (*ptr).free_obj = Some(Self::free_obj::<T>) };
53        unsafe { (*ptr).read_property = Some(Self::read_property::<T>) };
54        unsafe { (*ptr).write_property = Some(Self::write_property::<T>) };
55        unsafe { (*ptr).get_properties = Some(Self::get_properties::<T>) };
56        unsafe { (*ptr).has_property = Some(Self::has_property::<T>) };
57    }
58
59    unsafe extern "C" fn free_obj<T: RegisteredClass>(object: *mut ZendObject) {
60        let obj = unsafe {
61            object
62                .as_mut()
63                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
64                .expect("Invalid object pointer given for `free_obj`")
65        };
66
67        // Manually drop the object as we don't want to free the underlying memory.
68        unsafe { ptr::drop_in_place(&raw mut obj.obj) };
69
70        unsafe { zend_object_std_dtor(object) };
71    }
72
73    unsafe extern "C" fn read_property<T: RegisteredClass>(
74        object: *mut ZendObject,
75        member: *mut ZendStr,
76        type_: c_int,
77        cache_slot: *mut *mut c_void,
78        rv: *mut Zval,
79    ) -> *mut Zval {
80        // TODO: Measure this
81        #[allow(clippy::inline_always)]
82        #[inline(always)]
83        unsafe fn internal<T: RegisteredClass>(
84            object: *mut ZendObject,
85            member: *mut ZendStr,
86            type_: c_int,
87            cache_slot: *mut *mut c_void,
88            rv: *mut Zval,
89        ) -> PhpResult<*mut Zval> {
90            let obj = unsafe {
91                object
92                    .as_mut()
93                    .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
94                    .ok_or("Invalid object pointer given")?
95            };
96            let prop_name = unsafe {
97                member
98                    .as_ref()
99                    .ok_or("Invalid property name pointer given")?
100            };
101            let self_ = &mut *obj;
102            let props = T::get_metadata().get_properties();
103            let prop = props.get(prop_name.as_str()?);
104
105            // retval needs to be treated as initialized, so we set the type to null
106            let rv_mut = unsafe { rv.as_mut().ok_or("Invalid return zval given")? };
107            rv_mut.u1.type_info = ZvalTypeFlags::Null.bits();
108
109            Ok(match prop {
110                Some(prop_info) => {
111                    prop_info.prop.get(self_, rv_mut)?;
112                    rv
113                }
114                None => unsafe { zend_std_read_property(object, member, type_, cache_slot, rv) },
115            })
116        }
117
118        match unsafe { internal::<T>(object, member, type_, cache_slot, rv) } {
119            Ok(rv) => rv,
120            Err(e) => {
121                let _ = e.throw();
122                unsafe { (*rv).set_null() };
123                rv
124            }
125        }
126    }
127
128    unsafe extern "C" fn write_property<T: RegisteredClass>(
129        object: *mut ZendObject,
130        member: *mut ZendStr,
131        value: *mut Zval,
132        cache_slot: *mut *mut c_void,
133    ) -> *mut Zval {
134        // TODO: Measure this
135        #[allow(clippy::inline_always)]
136        #[inline(always)]
137        unsafe fn internal<T: RegisteredClass>(
138            object: *mut ZendObject,
139            member: *mut ZendStr,
140            value: *mut Zval,
141            cache_slot: *mut *mut c_void,
142        ) -> PhpResult<*mut Zval> {
143            let obj = unsafe {
144                object
145                    .as_mut()
146                    .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
147                    .ok_or("Invalid object pointer given")?
148            };
149            let prop_name = unsafe {
150                member
151                    .as_ref()
152                    .ok_or("Invalid property name pointer given")?
153            };
154            let self_ = &mut *obj;
155            let props = T::get_metadata().get_properties();
156            let prop = props.get(prop_name.as_str()?);
157            let value_mut = unsafe { value.as_mut().ok_or("Invalid return zval given")? };
158
159            Ok(match prop {
160                Some(prop_info) => {
161                    prop_info.prop.set(self_, value_mut)?;
162                    value
163                }
164                None => unsafe { zend_std_write_property(object, member, value, cache_slot) },
165            })
166        }
167
168        match unsafe { internal::<T>(object, member, value, cache_slot) } {
169            Ok(rv) => rv,
170            Err(e) => {
171                let _ = e.throw();
172                value
173            }
174        }
175    }
176
177    unsafe extern "C" fn get_properties<T: RegisteredClass>(
178        object: *mut ZendObject,
179    ) -> *mut ZendHashTable {
180        // TODO: Measure this
181        #[allow(clippy::inline_always)]
182        #[inline(always)]
183        unsafe fn internal<T: RegisteredClass>(
184            object: *mut ZendObject,
185            props: &mut ZendHashTable,
186        ) -> PhpResult {
187            let obj = unsafe {
188                object
189                    .as_mut()
190                    .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
191                    .ok_or("Invalid object pointer given")?
192            };
193            let self_ = &mut *obj;
194            let struct_props = T::get_metadata().get_properties();
195
196            for (&name, val) in struct_props {
197                let mut zv = Zval::new();
198                if val.prop.get(self_, &mut zv).is_err() {
199                    continue;
200                }
201                props.insert(name, zv).map_err(|e| {
202                    format!("Failed to insert value into properties hashtable: {e:?}")
203                })?;
204            }
205
206            Ok(())
207        }
208
209        let props = unsafe {
210            zend_std_get_properties(object)
211                .as_mut()
212                .or_else(|| Some(ZendHashTable::new().into_raw()))
213                .expect("Failed to get property hashtable")
214        };
215
216        if let Err(e) = unsafe { internal::<T>(object, props) } {
217            let _ = e.throw();
218        }
219
220        props
221    }
222
223    unsafe extern "C" fn has_property<T: RegisteredClass>(
224        object: *mut ZendObject,
225        member: *mut ZendStr,
226        has_set_exists: c_int,
227        cache_slot: *mut *mut c_void,
228    ) -> c_int {
229        // TODO: Measure this
230        #[allow(clippy::inline_always)]
231        #[inline(always)]
232        unsafe fn internal<T: RegisteredClass>(
233            object: *mut ZendObject,
234            member: *mut ZendStr,
235            has_set_exists: c_int,
236            cache_slot: *mut *mut c_void,
237        ) -> PhpResult<c_int> {
238            let obj = unsafe {
239                object
240                    .as_mut()
241                    .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
242                    .ok_or("Invalid object pointer given")?
243            };
244            let prop_name = unsafe {
245                member
246                    .as_ref()
247                    .ok_or("Invalid property name pointer given")?
248            };
249            let props = T::get_metadata().get_properties();
250            let prop = props.get(prop_name.as_str()?);
251            let self_ = &mut *obj;
252
253            match has_set_exists {
254                //
255                // * 0 (has) whether property exists and is not NULL
256                0 => {
257                    if let Some(val) = prop {
258                        let mut zv = Zval::new();
259                        val.prop.get(self_, &mut zv)?;
260                        if !zv.is_null() {
261                            return Ok(1);
262                        }
263                    }
264                }
265                //
266                // * 1 (set) whether property exists and is true
267                1 => {
268                    if let Some(val) = prop {
269                        let mut zv = Zval::new();
270                        val.prop.get(self_, &mut zv)?;
271
272                        cfg_if::cfg_if! {
273                            if #[cfg(php84)] {
274                                #[allow(clippy::unnecessary_mut_passed)]
275                                if unsafe { zend_is_true(&raw mut zv) } {
276                                    return Ok(1);
277                                }
278                            } else {
279                                #[allow(clippy::unnecessary_mut_passed)]
280                                if unsafe { zend_is_true(&raw mut zv) } == 1 {
281                                    return Ok(1);
282                                }
283                            }
284                        }
285                    }
286                }
287                //
288                // * 2 (exists) whether property exists
289                2 => {
290                    if prop.is_some() {
291                        return Ok(1);
292                    }
293                }
294                _ => return Err(
295                    "Invalid value given for `has_set_exists` in struct `has_property` function."
296                        .into(),
297                ),
298            }
299
300            Ok(unsafe { zend_std_has_property(object, member, has_set_exists, cache_slot) })
301        }
302
303        match unsafe { internal::<T>(object, member, has_set_exists, cache_slot) } {
304            Ok(rv) => rv,
305            Err(e) => {
306                let _ = e.throw();
307                0
308            }
309        }
310    }
311}