1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Real instance of lazy proxy may have less magic methods

In GH-18039 we guard the underlying property before forwarding access
to the real instance of a lazy proxy. When the real instance lacks magic
methods, the assertion zobj->ce->ce_flags & ZEND_ACC_USE_GUARDS fails in
zend_get_property_guard().

Fix by checking that the real instance uses guards.

Fixes GH-20504
Closes GH-21093
This commit is contained in:
Arnaud Le Blanc
2026-01-30 15:56:34 +01:00
parent 6d6d013d79
commit de26827275
7 changed files with 150 additions and 8 deletions

2
NEWS
View File

@@ -6,6 +6,8 @@ PHP NEWS
. Fixed bug GH-21029 (zend_mm_heap corrupted on Aarch64, LTO builds). (Arnaud)
. Fixed bug GH-20657 (Assertion failure in zend_lazy_object_get_info triggered
by setRawValueWithoutLazyInitialization() and newLazyGhost()). (Arnaud)
. Fixed bug GH-20504 (Assertion failure in zend_get_property_guard when
accessing properties on Reflection LazyProxy via isset()). (Arnaud)
- Curl:
. Fixed bug GH-21023 (CURLOPT_XFERINFOFUNCTION crash with a null callback).

View File

@@ -0,0 +1,24 @@
--TEST--
GH-20504: Assertion failure in zend_get_property_guard() when lazy proxy adds magic method - isset
--CREDITS--
vi3tL0u1s
--FILE--
<?php
class RealInstance {
public $_;
}
class Proxy extends RealInstance {
public function __isset($name) {
return isset($this->$name['']);
}
}
$rc = new ReflectionClass(Proxy::class);
$obj = $rc->newLazyProxy(function () {
return new RealInstance;
});
var_dump(isset($obj->name['']));
?>
--EXPECT--
bool(false)

View File

@@ -0,0 +1,23 @@
--TEST--
GH-20504: Assertion failure in zend_get_property_guard() when lazy proxy adds magic method - get
--FILE--
<?php
class RealInstance {
public $_;
}
class Proxy extends RealInstance {
public function __get($name) {
return $this->$name;
}
}
$rc = new ReflectionClass(Proxy::class);
$obj = $rc->newLazyProxy(function () {
return new RealInstance;
});
var_dump($obj->name);
?>
--EXPECTF--
Warning: Undefined property: RealInstance::$name in %s on line %d
NULL

View File

@@ -0,0 +1,33 @@
--TEST--
GH-20504: Assertion failure in zend_get_property_guard() when lazy proxy adds magic method - set
--FILE--
<?php
#[AllowDynamicProperties]
class RealInstance {
public $_;
}
class Proxy extends RealInstance {
public function __set($name, $value) {
$this->$name = $value;
}
}
$rc = new ReflectionClass(Proxy::class);
$obj = $rc->newLazyProxy(function () {
return new RealInstance;
});
$obj->name = 0;
var_dump($obj);
?>
--EXPECTF--
lazy proxy object(Proxy)#%d (1) {
["instance"]=>
object(RealInstance)#%d (2) {
["_"]=>
NULL
["name"]=>
int(0)
}
}

View File

@@ -0,0 +1,28 @@
--TEST--
GH-20504: Assertion failure in zend_get_property_guard() when lazy proxy adds magic method - proxy defines __isset(), both have guards
--FILE--
<?php
class RealInstance {
public $_;
public function __get($name) {
printf("%s::%s\n", static::class, __FUNCTION__);
}
}
class Proxy extends RealInstance {
public function __isset($name) {
printf("%s::%s\n", static::class, __FUNCTION__);
return isset($this->$name['']);
}
}
$rc = new ReflectionClass(Proxy::class);
$obj = $rc->newLazyProxy(function () {
return new RealInstance;
});
var_dump(isset($obj->name['']));
?>
--EXPECT--
Proxy::__isset
Proxy::__get
bool(false)

View File

@@ -0,0 +1,30 @@
--TEST--
GH-20504: Assertion failure in zend_get_property_guard() when lazy proxy adds magic method - unset
--FILE--
<?php
class RealInstance {
public $_;
}
class Proxy extends RealInstance {
public function __unset($name) {
unset($this->$name);
}
}
$rc = new ReflectionClass(Proxy::class);
$obj = $rc->newLazyProxy(function () {
return new RealInstance;
});
unset($obj->name);
var_dump($obj);
?>
--EXPECTF--
lazy proxy object(Proxy)#%d (1) {
["instance"]=>
object(RealInstance)#%d (1) {
["_"]=>
NULL
}
}

View File

@@ -956,25 +956,27 @@ call_getter:
uninit_error:
if (UNEXPECTED(zend_lazy_object_must_init(zobj))) {
if (!prop_info || (Z_PROP_FLAG_P(retval) & IS_PROP_LAZY)) {
zobj = zend_lazy_object_init(zobj);
if (!zobj) {
zend_object *instance = zend_lazy_object_init(zobj);
if (!instance) {
retval = &EG(uninitialized_zval);
goto exit;
}
if (UNEXPECTED(guard)) {
if (UNEXPECTED(guard && (instance->ce->ce_flags & ZEND_ACC_USE_GUARDS))) {
/* Find which guard was used on zobj, so we can set the same
* guard on instance. */
uint32_t guard_type = (type == BP_VAR_IS) && zobj->ce->__isset
? IN_ISSET : IN_GET;
guard = zend_get_property_guard(zobj, name);
guard = zend_get_property_guard(instance, name);
if (!((*guard) & guard_type)) {
(*guard) |= guard_type;
retval = zend_std_read_property(zobj, name, type, cache_slot, rv);
retval = zend_std_read_property(instance, name, type, cache_slot, rv);
(*guard) &= ~guard_type;
return retval;
}
}
return zend_std_read_property(zobj, name, type, cache_slot, rv);
return zend_std_read_property(instance, name, type, cache_slot, rv);
}
}
if (type != BP_VAR_IS) {
@@ -1013,7 +1015,7 @@ static zval *forward_write_to_lazy_object(zend_object *zobj,
return &EG(error_zval);
}
if (UNEXPECTED(guarded)) {
if (UNEXPECTED(guarded && (instance->ce->ce_flags & ZEND_ACC_USE_GUARDS))) {
uint32_t *guard = zend_get_property_guard(instance, name);
if (!((*guard) & IN_SET)) {
(*guard) |= IN_SET;
@@ -1597,7 +1599,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
return;
}
if (UNEXPECTED(guard)) {
if (UNEXPECTED(guard && zobj->ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
guard = zend_get_property_guard(zobj, name);
if (!((*guard) & IN_UNSET)) {
(*guard) |= IN_UNSET;