1use std::{ffi::CString, ffi::c_void, mem::MaybeUninit, os::raw::c_int, ptr};
2
3use crate::{
4 class::RegisteredClass,
5 exception::PhpResult,
6 ffi::{
7 ext_php_rs_executor_globals, instanceof_function_slow, std_object_handlers,
8 zend_class_entry, zend_is_true, zend_object_handlers, zend_object_std_dtor,
9 zend_objects_clone_members, zend_std_get_properties, zend_std_has_property,
10 zend_std_read_property, zend_std_write_property, zend_throw_error,
11 },
12 flags::{PropertyFlags, ZvalTypeFlags},
13 internal::property::PropertyDescriptor,
14 types::{ZendClassObject, ZendHashTable, ZendObject, ZendStr, Zval},
15};
16
17pub type ZendObjectHandlers = zend_object_handlers;
19
20impl ZendObjectHandlers {
21 #[must_use]
24 pub fn new<T: RegisteredClass>() -> ZendObjectHandlers {
25 let mut this = MaybeUninit::uninit();
26
27 unsafe { Self::init::<T>(&raw mut *this.as_mut_ptr()) };
29
30 unsafe { this.assume_init() }
33 }
34
35 pub unsafe fn init<T: RegisteredClass>(ptr: *mut ZendObjectHandlers) {
51 unsafe { ptr::copy_nonoverlapping(&raw const std_object_handlers, ptr, 1) };
52 let offset = ZendClassObject::<T>::std_offset();
53 unsafe { (*ptr).offset = offset.try_into().expect("Invalid offset") };
54 unsafe { (*ptr).free_obj = Some(Self::free_obj::<T>) };
55 unsafe { (*ptr).clone_obj = Some(Self::clone_obj::<T>) };
56 unsafe { (*ptr).read_property = Some(Self::read_property::<T>) };
57 unsafe { (*ptr).write_property = Some(Self::write_property::<T>) };
58 unsafe { (*ptr).get_properties = Some(Self::get_properties::<T>) };
59 unsafe { (*ptr).has_property = Some(Self::has_property::<T>) };
60 }
61
62 unsafe extern "C" fn free_obj<T: RegisteredClass>(object: *mut ZendObject) {
63 if let Some(obj) = unsafe {
67 object
68 .as_mut()
69 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
70 } {
71 unsafe { ptr::drop_in_place(&raw mut obj.obj) };
73 }
74
75 unsafe { zend_object_std_dtor(object) };
77 }
78
79 unsafe extern "C" fn clone_obj<T: RegisteredClass>(object: *mut ZendObject) -> *mut ZendObject {
80 let cloned_val = unsafe {
84 object
85 .as_ref()
86 .and_then(|obj| ZendClassObject::<T>::from_zend_obj(obj))
87 .and_then(|old| old.obj.as_ref())
88 .and_then(RegisteredClass::clone_obj)
89 };
90
91 if let Some(val) = cloned_val {
92 let mut new = ZendClassObject::<T>::new(val);
93 unsafe { zend_objects_clone_members(&raw mut new.std, object) };
94 let raw = new.into_raw();
95 &raw mut raw.std
96 } else {
97 let msg = CString::new(format!(
98 "Trying to clone an uncloneable object of class {}",
99 T::CLASS_NAME
100 ))
101 .expect("Failed to create error message");
102 unsafe { zend_throw_error(ptr::null_mut(), msg.as_ptr()) };
103 let empty = unsafe { ZendClassObject::<T>::new_uninit(None) };
106 let raw = empty.into_raw();
107 &raw mut raw.std
108 }
109 }
110
111 #[allow(clippy::items_after_statements)]
112 unsafe extern "C" fn read_property<T: RegisteredClass>(
113 object: *mut ZendObject,
114 member: *mut ZendStr,
115 type_: c_int,
116 cache_slot: *mut *mut c_void,
117 rv: *mut Zval,
118 ) -> *mut Zval {
119 let Some(obj) = (unsafe {
122 object
123 .as_mut()
124 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
125 }) else {
126 return unsafe { zend_std_read_property(object, member, type_, cache_slot, rv) };
127 };
128
129 #[allow(clippy::inline_always)]
130 #[inline(always)]
131 unsafe fn internal<T: RegisteredClass>(
132 object: *mut ZendObject,
133 obj: &mut ZendClassObject<T>,
134 member: *mut ZendStr,
135 type_: c_int,
136 cache_slot: *mut *mut c_void,
137 rv: *mut Zval,
138 ) -> PhpResult<*mut Zval> {
139 let self_ = &*obj;
140 let prop = unsafe { resolve_property::<T>(member, cache_slot)? };
141
142 let rv_mut = unsafe { rv.as_mut().ok_or("Invalid return zval given")? };
144 rv_mut.u1.type_info = ZvalTypeFlags::Null.bits();
145
146 Ok(match prop {
147 Some(prop_info) => {
148 let object_ce = unsafe { (*object).ce };
149 if !unsafe { check_property_access(prop_info.flags, object_ce) } {
150 let is_private = prop_info.flags.contains(PropertyFlags::Private);
151 let prop_name = unsafe {
152 member
153 .as_ref()
154 .ok_or("Invalid property name pointer given")?
155 };
156 unsafe {
157 throw_property_access_error(
158 T::CLASS_NAME,
159 prop_name.as_str()?,
160 is_private,
161 );
162 }
163 return Ok(rv);
164 }
165 let getter = prop_info
166 .get
167 .ok_or("No getter available for this property.")?;
168 getter(self_, rv_mut)?;
169 rv
170 }
171 None => unsafe { zend_std_read_property(object, member, type_, cache_slot, rv) },
172 })
173 }
174
175 match unsafe { internal::<T>(object, obj, member, type_, cache_slot, rv) } {
176 Ok(rv) => rv,
177 Err(e) => {
178 let _ = e.throw();
179 unsafe { (*rv).set_null() };
180 rv
181 }
182 }
183 }
184
185 #[allow(clippy::items_after_statements)]
186 unsafe extern "C" fn write_property<T: RegisteredClass>(
187 object: *mut ZendObject,
188 member: *mut ZendStr,
189 value: *mut Zval,
190 cache_slot: *mut *mut c_void,
191 ) -> *mut Zval {
192 let Some(obj) = (unsafe {
195 object
196 .as_mut()
197 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
198 }) else {
199 return unsafe { zend_std_write_property(object, member, value, cache_slot) };
200 };
201
202 #[allow(clippy::inline_always)]
203 #[inline(always)]
204 unsafe fn internal<T: RegisteredClass>(
205 object: *mut ZendObject,
206 obj: &mut ZendClassObject<T>,
207 member: *mut ZendStr,
208 value: *mut Zval,
209 cache_slot: *mut *mut c_void,
210 ) -> PhpResult<*mut Zval> {
211 let self_ = &mut *obj;
212 let prop = unsafe { resolve_property::<T>(member, cache_slot)? };
213 let value_mut = unsafe { value.as_mut().ok_or("Invalid return zval given")? };
214
215 Ok(match prop {
216 Some(prop_info) => {
217 let object_ce = unsafe { (*object).ce };
218 if !unsafe { check_property_access(prop_info.flags, object_ce) } {
219 let is_private = prop_info.flags.contains(PropertyFlags::Private);
220 let prop_name = unsafe {
221 member
222 .as_ref()
223 .ok_or("Invalid property name pointer given")?
224 };
225 unsafe {
226 throw_property_access_error(
227 T::CLASS_NAME,
228 prop_name.as_str()?,
229 is_private,
230 );
231 }
232 return Ok(value);
233 }
234 let setter = prop_info
235 .set
236 .ok_or("No setter available for this property.")?;
237 setter(self_, value_mut)?;
238 value
239 }
240 None => unsafe { zend_std_write_property(object, member, value, cache_slot) },
241 })
242 }
243
244 match unsafe { internal::<T>(object, obj, member, value, cache_slot) } {
245 Ok(rv) => rv,
246 Err(e) => {
247 let _ = e.throw();
248 value
249 }
250 }
251 }
252
253 #[allow(clippy::items_after_statements)]
254 unsafe extern "C" fn get_properties<T: RegisteredClass>(
255 object: *mut ZendObject,
256 ) -> *mut ZendHashTable {
257 let props = unsafe {
259 zend_std_get_properties(object)
260 .as_mut()
261 .or_else(|| Some(ZendHashTable::new().into_raw()))
262 .expect("Failed to get property hashtable")
263 };
264
265 let Some(obj) = (unsafe {
268 object
269 .as_mut()
270 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
271 }) else {
272 return props;
273 };
274
275 #[allow(clippy::inline_always)]
276 #[inline(always)]
277 unsafe fn internal<T: RegisteredClass>(
278 obj: &mut ZendClassObject<T>,
279 props: &mut ZendHashTable,
280 ) -> PhpResult {
281 let self_ = &*obj;
282 let metadata = T::get_metadata();
283 let method_mangled = metadata.method_mangled_names();
284
285 for desc in metadata.field_properties() {
286 let Some(getter) = desc.get else { continue };
287 let mut zv = Zval::new();
288 if getter(self_, &mut zv).is_err() {
289 continue;
290 }
291 props.insert(desc.mangled_name, zv).map_err(|e| {
292 format!("Failed to insert value into properties hashtable: {e:?}")
293 })?;
294 }
295
296 for (i, desc) in metadata.method_properties().iter().enumerate() {
297 let Some(getter) = desc.get else { continue };
298 let mut zv = Zval::new();
299 if getter(self_, &mut zv).is_err() {
300 continue;
301 }
302 props.insert(&*method_mangled[i], zv).map_err(|e| {
303 format!("Failed to insert value into properties hashtable: {e:?}")
304 })?;
305 }
306
307 Ok(())
308 }
309
310 if let Err(e) = unsafe { internal::<T>(obj, props) } {
311 let _ = e.throw();
312 }
313
314 props
315 }
316
317 #[allow(clippy::items_after_statements)]
318 unsafe extern "C" fn has_property<T: RegisteredClass>(
319 object: *mut ZendObject,
320 member: *mut ZendStr,
321 has_set_exists: c_int,
322 cache_slot: *mut *mut c_void,
323 ) -> c_int {
324 let Some(obj) = (unsafe {
327 object
328 .as_mut()
329 .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
330 }) else {
331 return unsafe { zend_std_has_property(object, member, has_set_exists, cache_slot) };
332 };
333
334 #[allow(clippy::inline_always)]
335 #[inline(always)]
336 unsafe fn internal<T: RegisteredClass>(
337 object: *mut ZendObject,
338 obj: &mut ZendClassObject<T>,
339 member: *mut ZendStr,
340 has_set_exists: c_int,
341 cache_slot: *mut *mut c_void,
342 ) -> PhpResult<c_int> {
343 let prop = unsafe { resolve_property::<T>(member, cache_slot)? };
344 let self_ = &*obj;
345
346 match has_set_exists {
347 0 => {
350 if let Some(val) = prop {
351 let getter = val.get.ok_or("No getter available for this property.")?;
352 let mut zv = Zval::new();
353 getter(self_, &mut zv)?;
354 if !zv.is_null() {
355 return Ok(1);
356 }
357 }
358 }
359 1 => {
362 if let Some(val) = prop {
363 let getter = val.get.ok_or("No getter available for this property.")?;
364 let mut zv = Zval::new();
365 getter(self_, &mut zv)?;
366
367 cfg_if::cfg_if! {
368 if #[cfg(php84)] {
369 #[allow(clippy::unnecessary_mut_passed)]
370 if unsafe { zend_is_true(&raw mut zv) } {
371 return Ok(1);
372 }
373 } else {
374 #[allow(clippy::unnecessary_mut_passed)]
375 if unsafe { zend_is_true(&raw mut zv) } == 1 {
376 return Ok(1);
377 }
378 }
379 }
380 }
381 }
382 2 => {
385 if prop.is_some() {
386 return Ok(1);
387 }
388 }
389 _ => return Err(
390 "Invalid value given for `has_set_exists` in struct `has_property` function."
391 .into(),
392 ),
393 }
394
395 Ok(unsafe { zend_std_has_property(object, member, has_set_exists, cache_slot) })
396 }
397
398 match unsafe { internal::<T>(object, obj, member, has_set_exists, cache_slot) } {
399 Ok(rv) => rv,
400 Err(e) => {
401 let _ = e.throw();
402 0
403 }
404 }
405 }
406}
407
408#[allow(clippy::inline_always)]
421#[inline(always)]
422unsafe fn resolve_property<T: RegisteredClass>(
423 member: *mut ZendStr,
424 cache_slot: *mut *mut c_void,
425) -> PhpResult<Option<&'static PropertyDescriptor<T>>> {
426 let meta = T::get_metadata();
427 let meta_ptr = ptr::from_ref(meta).cast::<c_void>().cast_mut();
428
429 if !cache_slot.is_null() {
430 let guard = unsafe { *cache_slot.add(1) };
431 if guard == meta_ptr {
432 let desc = unsafe { &*(*cache_slot).cast::<PropertyDescriptor<T>>() };
433 return Ok(Some(desc));
434 }
435 }
436
437 let prop_name = unsafe {
438 member
439 .as_ref()
440 .ok_or("Invalid property name pointer given")?
441 };
442 let Some(descriptor) = meta.find_property(prop_name.as_str()?) else {
443 return Ok(None);
444 };
445
446 if !cache_slot.is_null() {
447 unsafe {
448 *cache_slot = ptr::from_ref(descriptor).cast::<c_void>().cast_mut();
449 *cache_slot.add(1) = meta_ptr;
450 }
451 }
452
453 Ok(Some(descriptor))
454}
455
456#[inline]
462unsafe fn get_calling_scope() -> *const zend_class_entry {
463 let eg = unsafe { ext_php_rs_executor_globals().as_ref() };
464 let Some(eg) = eg else {
465 return ptr::null();
466 };
467 let execute_data = eg.current_execute_data;
468
469 if execute_data.is_null() {
470 return ptr::null();
471 }
472
473 let func = unsafe { (*execute_data).func };
474 if func.is_null() {
475 return ptr::null();
476 }
477
478 unsafe { (*func).common.scope }
480}
481
482#[inline]
491unsafe fn check_property_access(flags: PropertyFlags, object_ce: *const zend_class_entry) -> bool {
492 if !flags.contains(PropertyFlags::Private) && !flags.contains(PropertyFlags::Protected) {
494 return true;
495 }
496
497 let calling_scope = unsafe { get_calling_scope() };
498
499 if flags.contains(PropertyFlags::Private) {
500 return calling_scope == object_ce;
502 }
503
504 if flags.contains(PropertyFlags::Protected) {
505 if calling_scope.is_null() {
507 return false;
508 }
509
510 if calling_scope == object_ce {
512 return true;
513 }
514
515 unsafe {
518 instanceof_function_slow(calling_scope, object_ce)
519 || instanceof_function_slow(object_ce, calling_scope)
520 }
521 } else {
522 true
523 }
524}
525
526unsafe fn throw_property_access_error(class_name: &str, prop_name: &str, is_private: bool) {
536 let visibility = if is_private { "private" } else { "protected" };
537 let message = CString::new(format!(
538 "Cannot access {visibility} property {class_name}::${prop_name}"
539 ))
540 .expect("Failed to create error message");
541
542 unsafe {
543 zend_throw_error(ptr::null_mut(), message.as_ptr());
544 }
545}