Fix ReflectionProperty::isInitialized() for hooked props

In zend_std_has_property with ZEND_PROPERTY_EXISTS, we'd just return true when
no get hook was present. However, this function is supposed to return false for
uninitialized properties. PROPERTY_EXISTS is somewhat of a misnomer. Virtual
properties continue to always return true, given there's no backing value to
check.

Fixes GH-15694
Closes GH-15822
This commit is contained in:
Ilija Tovilo 2024-09-10 12:27:19 +02:00
parent bdcb2185aa
commit 025ed70ce3
No known key found for this signature in database
GPG Key ID: 5050C66BFCD1015A
3 changed files with 67 additions and 7 deletions

2
NEWS
View File

@ -49,6 +49,8 @@ PHP NEWS
- Reflection:
. Fixed bug GH-15718 (Segfault on ReflectionProperty::get{Hook,Hooks}() on
dynamic properties). (DanielEScherzer)
. Fixed bug GH-15694 (ReflectionProperty::isInitialized() is incorrect for
hooked properties). (ilutov)
- SOAP:
. Fixed bug #61525 (SOAP functions require at least one space after HTTP

View File

@ -2208,6 +2208,15 @@ found:
}
} else if (IS_HOOKED_PROPERTY_OFFSET(property_offset)) {
zend_function *get = prop_info->hooks[ZEND_PROPERTY_HOOK_GET];
if (has_set_exists == ZEND_PROPERTY_EXISTS) {
if (prop_info->flags & ZEND_ACC_VIRTUAL) {
return true;
}
property_offset = prop_info->offset;
goto try_again;
}
if (!get) {
if (prop_info->flags & ZEND_ACC_VIRTUAL) {
zend_throw_error(NULL, "Property %s::$%s is write-only",
@ -2219,19 +2228,12 @@ found:
}
}
if (has_set_exists == ZEND_PROPERTY_EXISTS) {
return 1;
}
zval rv;
if (!zend_call_get_hook(prop_info, name, get, zobj, &rv)) {
if (EG(exception)) {
return 0;
}
property_offset = prop_info->offset;
if (!ZEND_TYPE_IS_SET(prop_info->type)) {
prop_info = NULL;
}
goto try_again;
}

View File

@ -0,0 +1,56 @@
--TEST--
ReflectionProperty::isInitialized() on hooked properties
--FILE--
<?php
class Test {
// Plain
public $p1;
public string $p2;
// Virtual
public $v1 { get => throw new Exception(); }
public $v2 { set { throw new Exception(); } }
// Backed
public $b1 { get => throw new Exception($this->b1); }
public string $b2 { get => throw new Exception($this->b2); }
public $b3 { set => throw new Exception(); }
public string $b4 { set => throw new Exception(); }
}
$test = new Test();
$rc = new ReflectionClass(Test::class);
foreach ($rc->getProperties() as $rp) {
echo $rp->getName(), "\n";
var_dump($rp->isInitialized($test));
try {
$rp->setRawValue($test, 42);
} catch (Error $e) {}
var_dump($rp->isInitialized($test));
}
?>
--EXPECT--
p1
bool(true)
bool(true)
p2
bool(false)
bool(true)
v1
bool(true)
bool(true)
v2
bool(true)
bool(true)
b1
bool(true)
bool(true)
b2
bool(false)
bool(true)
b3
bool(true)
bool(true)
b4
bool(false)
bool(true)