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
15pub type ZendObjectHandlers = zend_object_handlers;
17
18impl ZendObjectHandlers {
19 #[must_use]
22 pub fn new<T: RegisteredClass>() -> ZendObjectHandlers {
23 let mut this = MaybeUninit::uninit();
24
25 unsafe { Self::init::<T>(&raw mut *this.as_mut_ptr()) };
27
28 unsafe { this.assume_init() }
31 }
32
33 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 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 #[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 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 #[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 #[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 #[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 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 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 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}