@@ -623,6 +623,44 @@ ZEND_COLD static void zend_typed_property_uninitialized_access(const zend_proper
623623 ZSTR_VAL (name ));
624624}
625625
626+ static ZEND_FUNCTION (zend_parent_hook_get_trampoline );
627+ static ZEND_FUNCTION (zend_parent_hook_set_trampoline );
628+
629+ static bool is_in_hook (const zend_property_info * prop_info )
630+ {
631+ zend_execute_data * execute_data = EG (current_execute_data );
632+ if (!execute_data || !EX (func )) {
633+ return false;
634+ }
635+
636+ zend_function * func = EX (func );
637+ zend_function * * hooks = prop_info -> hooks ;
638+ ZEND_ASSERT (hooks );
639+
640+ /* Fast path: Check if we're directly in the properties hook. */
641+ if (func == hooks [ZEND_PROPERTY_HOOK_GET ]
642+ || func == hooks [ZEND_PROPERTY_HOOK_SET ]) {
643+ return true;
644+ }
645+
646+ /* Check if we're in a parent hook. */
647+ zend_function * prototype = func -> common .prototype ? func -> common .prototype : func ;
648+ if ((hooks [ZEND_PROPERTY_HOOK_GET ] && prototype == hooks [ZEND_PROPERTY_HOOK_GET ]-> common .prototype )
649+ || (hooks [ZEND_PROPERTY_HOOK_SET ] && prototype == hooks [ZEND_PROPERTY_HOOK_SET ]-> common .prototype )) {
650+ return true;
651+ }
652+
653+ /* Check if we're in the trampoline helper. */
654+ if (EX (func ) == & EG (trampoline )
655+ && !ZEND_USER_CODE (EX (func )-> type )
656+ && (EX (func )-> internal_function .handler == ZEND_FN (zend_parent_hook_get_trampoline )
657+ || EX (func )-> internal_function .handler == ZEND_FN (zend_parent_hook_set_trampoline ))) {
658+ return true;
659+ }
660+
661+ return false;
662+ }
663+
626664ZEND_API zval * zend_std_read_property (zend_object * zobj , zend_string * name , int type , void * * cache_slot , zval * rv ) /* {{{ */
627665{
628666 zval * retval ;
@@ -738,6 +776,9 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
738776 zend_throw_error (NULL , "Must not read from virtual property %s::$%s" ,
739777 ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
740778 return & EG (uninitialized_zval );
779+ } else if (UNEXPECTED (!is_in_hook (prop_info ))) {
780+ zend_throw_error (NULL , "Must not read from property %s::$%s recursively" ,
781+ ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
741782 }
742783 property_offset = prop_info -> offset ;
743784 if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
@@ -1000,6 +1041,9 @@ found:;
10001041 ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
10011042 variable_ptr = & EG (error_zval );
10021043 goto exit ;
1044+ } else if (UNEXPECTED (!is_in_hook (prop_info ))) {
1045+ zend_throw_error (NULL , "Must not write to property %s::$%s recursively" ,
1046+ ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
10031047 }
10041048 property_offset = prop_info -> offset ;
10051049 if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
@@ -2115,6 +2159,9 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has
21152159 zend_throw_error (NULL , "Must not read from virtual property %s::$%s" ,
21162160 ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
21172161 return 0 ;
2162+ } else if (UNEXPECTED (!is_in_hook (prop_info ))) {
2163+ zend_throw_error (NULL , "Must not read from property %s::$%s recursively" ,
2164+ ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
21182165 }
21192166 property_offset = prop_info -> offset ;
21202167 if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
0 commit comments