@@ -623,6 +623,44 @@ ZEND_COLD static void zend_typed_property_uninitialized_access(const zend_proper
623
623
ZSTR_VAL (name ));
624
624
}
625
625
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
+
626
664
ZEND_API zval * zend_std_read_property (zend_object * zobj , zend_string * name , int type , void * * cache_slot , zval * rv ) /* {{{ */
627
665
{
628
666
zval * retval ;
@@ -738,6 +776,9 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
738
776
zend_throw_error (NULL , "Must not read from virtual property %s::$%s" ,
739
777
ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
740
778
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 ));
741
782
}
742
783
property_offset = prop_info -> offset ;
743
784
if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
@@ -1000,6 +1041,9 @@ found:;
1000
1041
ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
1001
1042
variable_ptr = & EG (error_zval );
1002
1043
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 ));
1003
1047
}
1004
1048
property_offset = prop_info -> offset ;
1005
1049
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
2115
2159
zend_throw_error (NULL , "Must not read from virtual property %s::$%s" ,
2116
2160
ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
2117
2161
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 ));
2118
2165
}
2119
2166
property_offset = prop_info -> offset ;
2120
2167
if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
0 commit comments