@@ -886,6 +886,23 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
886886
887887 retval = & EG (uninitialized_zval );
888888
889+ /* For initialized lazy proxies: if the real instance's magic method
890+ * guard is already set for this property, we are inside a recursive
891+ * call from the real instance's __get/__isset. Forward directly to
892+ * the real instance to avoid double invocation. (GH-21478) */
893+ if (UNEXPECTED (zend_object_is_lazy_proxy (zobj )
894+ && zend_lazy_object_initialized (zobj ))) {
895+ zend_object * instance = zend_lazy_object_get_instance (zobj );
896+ if (instance -> ce -> ce_flags & ZEND_ACC_USE_GUARDS ) {
897+ uint32_t * instance_guard = zend_get_property_guard (instance , name );
898+ uint32_t guard_type = ((type == BP_VAR_IS ) && zobj -> ce -> __isset )
899+ ? IN_ISSET : IN_GET ;
900+ if ((* instance_guard ) & guard_type ) {
901+ return zend_std_read_property (instance , name , type , cache_slot , rv );
902+ }
903+ }
904+ }
905+
889906 /* magic isset */
890907 if ((type == BP_VAR_IS ) && zobj -> ce -> __isset ) {
891908 zval tmp_result ;
@@ -1203,6 +1220,20 @@ found:;
12031220 goto exit ;
12041221 }
12051222
1223+ /* For initialized lazy proxies: if the real instance's __set guard
1224+ * is already set, we are inside a recursive call from the real
1225+ * instance's __set. Forward directly to avoid double invocation. */
1226+ if (UNEXPECTED (zend_object_is_lazy_proxy (zobj )
1227+ && zend_lazy_object_initialized (zobj ))) {
1228+ zend_object * instance = zend_lazy_object_get_instance (zobj );
1229+ if (instance -> ce -> ce_flags & ZEND_ACC_USE_GUARDS ) {
1230+ uint32_t * instance_guard = zend_get_property_guard (instance , name );
1231+ if ((* instance_guard ) & IN_SET ) {
1232+ return zend_std_write_property (instance , name , value , cache_slot );
1233+ }
1234+ }
1235+ }
1236+
12061237 /* magic set */
12071238 if (zobj -> ce -> __set ) {
12081239 if (!guard ) {
@@ -1595,6 +1626,21 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
15951626 return ;
15961627 }
15971628
1629+ /* For initialized lazy proxies: if the real instance's __unset guard
1630+ * is already set, we are inside a recursive call from the real
1631+ * instance's __unset. Forward directly to avoid double invocation. */
1632+ if (UNEXPECTED (zend_object_is_lazy_proxy (zobj )
1633+ && zend_lazy_object_initialized (zobj ))) {
1634+ zend_object * instance = zend_lazy_object_get_instance (zobj );
1635+ if (instance -> ce -> ce_flags & ZEND_ACC_USE_GUARDS ) {
1636+ uint32_t * instance_guard = zend_get_property_guard (instance , name );
1637+ if ((* instance_guard ) & IN_UNSET ) {
1638+ zend_std_unset_property (instance , name , cache_slot );
1639+ return ;
1640+ }
1641+ }
1642+ }
1643+
15981644 /* magic unset */
15991645 if (zobj -> ce -> __unset ) {
16001646 if (!guard ) {
@@ -2412,6 +2458,20 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has
24122458 goto exit ;
24132459 }
24142460
2461+ /* For initialized lazy proxies: if the real instance's __isset guard
2462+ * is already set, we are inside a recursive call from the real
2463+ * instance's __isset. Forward directly to avoid double invocation. */
2464+ if (UNEXPECTED (zend_object_is_lazy_proxy (zobj )
2465+ && zend_lazy_object_initialized (zobj ))) {
2466+ zend_object * instance = zend_lazy_object_get_instance (zobj );
2467+ if (instance -> ce -> ce_flags & ZEND_ACC_USE_GUARDS ) {
2468+ uint32_t * instance_guard = zend_get_property_guard (instance , name );
2469+ if ((* instance_guard ) & IN_ISSET ) {
2470+ return zend_std_has_property (instance , name , has_set_exists , cache_slot );
2471+ }
2472+ }
2473+ }
2474+
24152475 if (!zobj -> ce -> __isset ) {
24162476 goto lazy_init ;
24172477 }
0 commit comments