Skip to content

Commit d68fcd8

Browse files
committed
Fix GH-21478: Forward read_property to real instance for initialized lazy proxies
For initialized lazy proxies, the proxy and real instance have separate magic method guard slots. When the real instance's __get/__isset is running and code inside it accesses the proxy, the proxy's guard is clear, causing __get/__isset to fire on the proxy too (double invocation). Check whether the real instance's guard is already set before invoking magic methods on the proxy. If it is, forward to the real instance (we're inside a recursive call). If not, the proxy was accessed directly and its own magic methods should run as normal.
1 parent d34c840 commit d68fcd8

File tree

6 files changed

+79
-10
lines changed

6 files changed

+79
-10
lines changed

Zend/tests/lazy_objects/gh18038-004.phpt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ var_dump($real->prop);
3636
--EXPECTF--
3737
init
3838
string(19) "RealInstance::__get"
39-
string(12) "Proxy::__get"
4039

4140
Warning: Undefined property: RealInstance::$prop in %s on line %d
4241
NULL

Zend/tests/lazy_objects/gh18038-007.phpt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,5 @@ var_dump(isset($real->prop['']));
3636
--EXPECT--
3737
init
3838
string(21) "RealInstance::__isset"
39-
string(14) "Proxy::__isset"
4039
bool(false)
4140
bool(false)

Zend/tests/lazy_objects/gh20875.phpt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ Warning: Undefined variable $a in %s on line %d
3131

3232
Warning: Undefined variable $v in %s on line %d
3333

34-
Notice: Indirect modification of overloaded property A::$b has no effect in %s on line %d
35-
36-
Warning: Undefined variable $x in %s on line %d
37-
38-
Notice: Object of class stdClass could not be converted to int in %s on line %d
39-
40-
Warning: Undefined variable $v in %s on line %d
41-
4234
Notice: Indirect modification of overloaded property A::$f has no effect in %s on line %d
4335

4436
Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
GH-21478: Proxy's own __get runs when accessed directly (not from real instance)
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
private $_;
8+
9+
public function __get($name) {
10+
echo __CLASS__, " ", $name, "\n";
11+
}
12+
}
13+
14+
class Bar extends Foo {
15+
public function __get($name) {
16+
echo __CLASS__, " ", $name, "\n";
17+
}
18+
}
19+
20+
$rc = new ReflectionClass(Bar::class);
21+
$proxy = $rc->newLazyProxy(function () {
22+
return new Foo();
23+
});
24+
$rc->initializeLazyObject($proxy);
25+
26+
$proxy->x;
27+
28+
?>
29+
--EXPECT--
30+
Bar x
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
GH-21478 (Property access on lazy proxy may invoke magic method despite real instance guards)
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public $_;
8+
9+
public function __get($name) {
10+
global $proxy;
11+
printf("__get(\$%s) on %s\n", $name, $this::class);
12+
return $proxy->{$name};
13+
}
14+
}
15+
16+
class Bar extends Foo {}
17+
18+
$rc = new ReflectionClass(Bar::class);
19+
$proxy = $rc->newLazyProxy(function () {
20+
echo "Init\n";
21+
return new Foo();
22+
});
23+
24+
$real = $rc->initializeLazyObject($proxy);
25+
$real->x;
26+
27+
?>
28+
--EXPECTF--
29+
Init
30+
__get($x) on Foo
31+
32+
Warning: Undefined property: Foo::$x in %s on line %d

Zend/zend_object_handlers.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,23 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
893893

894894
retval = &EG(uninitialized_zval);
895895

896+
/* For initialized lazy proxies: if the real instance's magic method
897+
* guard is already set for this property, we are inside a recursive
898+
* call from the real instance's __get/__isset. Forward directly to
899+
* the real instance to avoid double invocation. (GH-21478) */
900+
if (UNEXPECTED(zend_object_is_lazy_proxy(zobj)
901+
&& zend_lazy_object_initialized(zobj))) {
902+
zend_object *instance = zend_lazy_object_get_instance(zobj);
903+
if (instance->ce->ce_flags & ZEND_ACC_USE_GUARDS) {
904+
uint32_t *instance_guard = zend_get_property_guard(instance, name);
905+
uint32_t guard_type = ((type == BP_VAR_IS) && zobj->ce->__isset)
906+
? IN_ISSET : IN_GET;
907+
if ((*instance_guard) & guard_type) {
908+
return zend_std_read_property(instance, name, type, cache_slot, rv);
909+
}
910+
}
911+
}
912+
896913
/* magic isset */
897914
if ((type == BP_VAR_IS) && zobj->ce->__isset) {
898915
zval tmp_result;

0 commit comments

Comments
 (0)