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        ptr::copy_nonoverlapping(&raw const std_object_handlers, ptr, 1);
50        let offset = ZendClassObject::<T>::std_offset();
51        (*ptr).offset = offset.try_into().expect("Invalid offset");
52        (*ptr).free_obj = Some(Self::free_obj::<T>);
53        (*ptr).read_property = Some(Self::read_property::<T>);
54        (*ptr).write_property = Some(Self::write_property::<T>);
55        (*ptr).get_properties = Some(Self::get_properties::<T>);
56        (*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 = object
61            .as_mut()
62            .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
63            .expect("Invalid object pointer given for `free_obj`");
64
65        // Manually drop the object as we don't want to free the underlying memory.
66        ptr::drop_in_place(&raw mut obj.obj);
67
68        zend_object_std_dtor(object);
69    }
70
71    unsafe extern "C" fn read_property<T: RegisteredClass>(
72        object: *mut ZendObject,
73        member: *mut ZendStr,
74        type_: c_int,
75        cache_slot: *mut *mut c_void,
76        rv: *mut Zval,
77    ) -> *mut Zval {
78        // TODO: Measure this
79        #[allow(clippy::inline_always)]
80        #[inline(always)]
81        unsafe fn internal<T: RegisteredClass>(
82            object: *mut ZendObject,
83            member: *mut ZendStr,
84            type_: c_int,
85            cache_slot: *mut *mut c_void,
86            rv: *mut Zval,
87        ) -> PhpResult<*mut Zval> {
88            let obj = object
89                .as_mut()
90                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
91                .ok_or("Invalid object pointer given")?;
92            let prop_name = member
93                .as_ref()
94                .ok_or("Invalid property name pointer given")?;
95            let self_ = &mut *obj;
96            let props = T::get_metadata().get_properties();
97            let prop = props.get(prop_name.as_str()?);
98
99            // retval needs to be treated as initialized, so we set the type to null
100            let rv_mut = rv.as_mut().ok_or("Invalid return zval given")?;
101            rv_mut.u1.type_info = ZvalTypeFlags::Null.bits();
102
103            Ok(match prop {
104                Some(prop_info) => {
105                    prop_info.prop.get(self_, rv_mut)?;
106                    rv
107                }
108                None => zend_std_read_property(object, member, type_, cache_slot, rv),
109            })
110        }
111
112        match internal::<T>(object, member, type_, cache_slot, rv) {
113            Ok(rv) => rv,
114            Err(e) => {
115                let _ = e.throw();
116                (*rv).set_null();
117                rv
118            }
119        }
120    }
121
122    unsafe extern "C" fn write_property<T: RegisteredClass>(
123        object: *mut ZendObject,
124        member: *mut ZendStr,
125        value: *mut Zval,
126        cache_slot: *mut *mut c_void,
127    ) -> *mut Zval {
128        // TODO: Measure this
129        #[allow(clippy::inline_always)]
130        #[inline(always)]
131        unsafe fn internal<T: RegisteredClass>(
132            object: *mut ZendObject,
133            member: *mut ZendStr,
134            value: *mut Zval,
135            cache_slot: *mut *mut c_void,
136        ) -> PhpResult<*mut Zval> {
137            let obj = object
138                .as_mut()
139                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
140                .ok_or("Invalid object pointer given")?;
141            let prop_name = member
142                .as_ref()
143                .ok_or("Invalid property name pointer given")?;
144            let self_ = &mut *obj;
145            let props = T::get_metadata().get_properties();
146            let prop = props.get(prop_name.as_str()?);
147            let value_mut = value.as_mut().ok_or("Invalid return zval given")?;
148
149            Ok(match prop {
150                Some(prop_info) => {
151                    prop_info.prop.set(self_, value_mut)?;
152                    value
153                }
154                None => zend_std_write_property(object, member, value, cache_slot),
155            })
156        }
157
158        match internal::<T>(object, member, value, cache_slot) {
159            Ok(rv) => rv,
160            Err(e) => {
161                let _ = e.throw();
162                value
163            }
164        }
165    }
166
167    unsafe extern "C" fn get_properties<T: RegisteredClass>(
168        object: *mut ZendObject,
169    ) -> *mut ZendHashTable {
170        // TODO: Measure this
171        #[allow(clippy::inline_always)]
172        #[inline(always)]
173        unsafe fn internal<T: RegisteredClass>(
174            object: *mut ZendObject,
175            props: &mut ZendHashTable,
176        ) -> PhpResult {
177            let obj = object
178                .as_mut()
179                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
180                .ok_or("Invalid object pointer given")?;
181            let self_ = &mut *obj;
182            let struct_props = T::get_metadata().get_properties();
183
184            for (&name, val) in struct_props {
185                let mut zv = Zval::new();
186                if val.prop.get(self_, &mut zv).is_err() {
187                    continue;
188                }
189                props.insert(name, zv).map_err(|e| {
190                    format!("Failed to insert value into properties hashtable: {e:?}")
191                })?;
192            }
193
194            Ok(())
195        }
196
197        let props = zend_std_get_properties(object)
198            .as_mut()
199            .or_else(|| Some(ZendHashTable::new().into_raw()))
200            .expect("Failed to get property hashtable");
201
202        if let Err(e) = internal::<T>(object, props) {
203            let _ = e.throw();
204        }
205
206        props
207    }
208
209    unsafe extern "C" fn has_property<T: RegisteredClass>(
210        object: *mut ZendObject,
211        member: *mut ZendStr,
212        has_set_exists: c_int,
213        cache_slot: *mut *mut c_void,
214    ) -> c_int {
215        // TODO: Measure this
216        #[allow(clippy::inline_always)]
217        #[inline(always)]
218        unsafe fn internal<T: RegisteredClass>(
219            object: *mut ZendObject,
220            member: *mut ZendStr,
221            has_set_exists: c_int,
222            cache_slot: *mut *mut c_void,
223        ) -> PhpResult<c_int> {
224            let obj = object
225                .as_mut()
226                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
227                .ok_or("Invalid object pointer given")?;
228            let prop_name = member
229                .as_ref()
230                .ok_or("Invalid property name pointer given")?;
231            let props = T::get_metadata().get_properties();
232            let prop = props.get(prop_name.as_str()?);
233            let self_ = &mut *obj;
234
235            match has_set_exists {
236                //
237                // * 0 (has) whether property exists and is not NULL
238                0 => {
239                    if let Some(val) = prop {
240                        let mut zv = Zval::new();
241                        val.prop.get(self_, &mut zv)?;
242                        if !zv.is_null() {
243                            return Ok(1);
244                        }
245                    }
246                }
247                //
248                // * 1 (set) whether property exists and is true
249                1 => {
250                    if let Some(val) = prop {
251                        let mut zv = Zval::new();
252                        val.prop.get(self_, &mut zv)?;
253
254                        cfg_if::cfg_if! {
255                            if #[cfg(php84)] {
256                                #[allow(clippy::unnecessary_mut_passed)]
257                                if zend_is_true(&raw mut zv) {
258                                    return Ok(1);
259                                }
260                            } else {
261                                #[allow(clippy::unnecessary_mut_passed)]
262                                if zend_is_true(&raw mut zv) == 1 {
263                                    return Ok(1);
264                                }
265                            }
266                        }
267                    }
268                }
269                //
270                // * 2 (exists) whether property exists
271                2 => {
272                    if prop.is_some() {
273                        return Ok(1);
274                    }
275                }
276                _ => return Err(
277                    "Invalid value given for `has_set_exists` in struct `has_property` function."
278                        .into(),
279                ),
280            }
281
282            Ok(zend_std_has_property(
283                object,
284                member,
285                has_set_exists,
286                cache_slot,
287            ))
288        }
289
290        match internal::<T>(object, member, has_set_exists, cache_slot) {
291            Ok(rv) => rv,
292            Err(e) => {
293                let _ = e.throw();
294                0
295            }
296        }
297    }
298}