diff --git a/Zend/tests/lazy_objects/gh18038-004.phpt b/Zend/tests/lazy_objects/gh18038-004.phpt index 8810efb6bec2e..c1495c5a6d8d6 100644 --- a/Zend/tests/lazy_objects/gh18038-004.phpt +++ b/Zend/tests/lazy_objects/gh18038-004.phpt @@ -36,7 +36,6 @@ var_dump($real->prop); --EXPECTF-- init string(19) "RealInstance::__get" -string(12) "Proxy::__get" Warning: Undefined property: RealInstance::$prop in %s on line %d NULL diff --git a/Zend/tests/lazy_objects/gh18038-007.phpt b/Zend/tests/lazy_objects/gh18038-007.phpt index 9925190a19801..4c7c0d0b4b0a6 100644 --- a/Zend/tests/lazy_objects/gh18038-007.phpt +++ b/Zend/tests/lazy_objects/gh18038-007.phpt @@ -36,6 +36,5 @@ var_dump(isset($real->prop[''])); --EXPECT-- init string(21) "RealInstance::__isset" -string(14) "Proxy::__isset" bool(false) bool(false) diff --git a/Zend/tests/lazy_objects/gh20875.phpt b/Zend/tests/lazy_objects/gh20875.phpt index 72e16011320c3..ff036edabd596 100644 --- a/Zend/tests/lazy_objects/gh20875.phpt +++ b/Zend/tests/lazy_objects/gh20875.phpt @@ -31,14 +31,6 @@ Warning: Undefined variable $a in %s on line %d Warning: Undefined variable $v in %s on line %d -Notice: Indirect modification of overloaded property A::$b has no effect in %s on line %d - -Warning: Undefined variable $x in %s on line %d - -Notice: Object of class stdClass could not be converted to int in %s on line %d - -Warning: Undefined variable $v in %s on line %d - Notice: Indirect modification of overloaded property A::$f has no effect in %s on line %d Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d diff --git a/Zend/tests/lazy_objects/gh21478-proxy-get-override.phpt b/Zend/tests/lazy_objects/gh21478-proxy-get-override.phpt new file mode 100644 index 0000000000000..520c8f6623531 --- /dev/null +++ b/Zend/tests/lazy_objects/gh21478-proxy-get-override.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-21478: Proxy's own __get runs when accessed directly (not from real instance) +--FILE-- +newLazyProxy(function () { + return new Foo(); +}); +$rc->initializeLazyObject($proxy); + +$proxy->x; + +?> +--EXPECT-- +Bar x diff --git a/Zend/tests/lazy_objects/gh21478.phpt b/Zend/tests/lazy_objects/gh21478.phpt new file mode 100644 index 0000000000000..aaa226a9a09a7 --- /dev/null +++ b/Zend/tests/lazy_objects/gh21478.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-21478 (Property access on lazy proxy may invoke magic method despite real instance guards) +--FILE-- +{$name}; + } +} + +class Bar extends Foo {} + +$rc = new ReflectionClass(Bar::class); +$proxy = $rc->newLazyProxy(function () { + echo "Init\n"; + return new Foo(); +}); + +$real = $rc->initializeLazyObject($proxy); +$real->x; + +?> +--EXPECTF-- +Init +__get($x) on Foo + +Warning: Undefined property: Foo::$x in %s on line %d diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 7e03139dc426b..c41d76bf453a8 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -893,6 +893,23 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int retval = &EG(uninitialized_zval); + /* For initialized lazy proxies: if the real instance's magic method + * guard is already set for this property, we are inside a recursive + * call from the real instance's __get/__isset. Forward directly to + * the real instance to avoid double invocation. (GH-21478) */ + if (UNEXPECTED(zend_object_is_lazy_proxy(zobj) + && zend_lazy_object_initialized(zobj))) { + zend_object *instance = zend_lazy_object_get_instance(zobj); + if (instance->ce->ce_flags & ZEND_ACC_USE_GUARDS) { + uint32_t *instance_guard = zend_get_property_guard(instance, name); + uint32_t guard_type = ((type == BP_VAR_IS) && zobj->ce->__isset) + ? IN_ISSET : IN_GET; + if ((*instance_guard) & guard_type) { + return zend_std_read_property(instance, name, type, cache_slot, rv); + } + } + } + /* magic isset */ if ((type == BP_VAR_IS) && zobj->ce->__isset) { zval tmp_result;