@@ -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 ;
@@ -739,6 +777,9 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
739
777
zend_throw_error (NULL , "Must not read from virtual property %s::$%s" ,
740
778
ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
741
779
return & EG (uninitialized_zval );
780
+ } else if (UNEXPECTED (!is_in_hook (prop_info ))) {
781
+ zend_throw_error (NULL , "Must not read from property %s::$%s recursively" ,
782
+ ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
742
783
}
743
784
property_offset = prop_info -> offset ;
744
785
if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
@@ -1006,6 +1047,9 @@ found:;
1006
1047
ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
1007
1048
variable_ptr = & EG (error_zval );
1008
1049
goto exit ;
1050
+ } else if (UNEXPECTED (!is_in_hook (prop_info ))) {
1051
+ zend_throw_error (NULL , "Must not write to property %s::$%s recursively" ,
1052
+ ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
1009
1053
}
1010
1054
property_offset = prop_info -> offset ;
1011
1055
if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
@@ -2122,6 +2166,9 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has
2122
2166
zend_throw_error (NULL , "Must not read from virtual property %s::$%s" ,
2123
2167
ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
2124
2168
return 0 ;
2169
+ } else if (UNEXPECTED (!is_in_hook (prop_info ))) {
2170
+ zend_throw_error (NULL , "Must not read from property %s::$%s recursively" ,
2171
+ ZSTR_VAL (zobj -> ce -> name ), ZSTR_VAL (name ));
2125
2172
}
2126
2173
property_offset = prop_info -> offset ;
2127
2174
if (!ZEND_TYPE_IS_SET (prop_info -> type )) {
0 commit comments