Skip to content

Commit b7d6e64

Browse files
committed
Better handling of magic get/set
1 parent ef95e47 commit b7d6e64

File tree

2 files changed

+86
-9
lines changed

2 files changed

+86
-9
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
--TEST--
2+
Interaction with magic get/set
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public readonly int $prop;
8+
9+
public function unsetProp() {
10+
unset($this->prop);
11+
}
12+
13+
public function __get($name) {
14+
echo __METHOD__, "($name)\n";
15+
return 1;
16+
}
17+
18+
public function __set($name, $value) {
19+
echo __METHOD__, "($name, $value)\n";
20+
}
21+
22+
public function __unset($name) {
23+
echo __METHOD__, "($name)\n";
24+
}
25+
26+
public function __isset($name) {
27+
echo __METHOD__, "($name)\n";
28+
return true;
29+
}
30+
}
31+
32+
$test = new Test;
33+
34+
// The property is in uninitialized state, no magic methods should be invoked.
35+
var_dump(isset($test->prop));
36+
try {
37+
var_dump($test->prop);
38+
} catch (Error $e) {
39+
echo $e->getMessage(), "\n";
40+
}
41+
try {
42+
$test->prop = 1;
43+
} catch (Error $e) {
44+
echo $e->getMessage(), "\n";
45+
}
46+
try {
47+
unset($test->prop);
48+
} catch (Error $e) {
49+
echo $e->getMessage(), "\n";
50+
}
51+
52+
$test->unsetProp();
53+
54+
var_dump(isset($test->prop));
55+
var_dump($test->prop);
56+
$test->prop = 2;
57+
try {
58+
unset($test->prop);
59+
} catch (Error $e) {
60+
echo $e->getMessage(), "\n";
61+
}
62+
63+
?>
64+
--EXPECT--
65+
bool(false)
66+
Typed property Test::$prop must not be accessed before initialization
67+
Cannot initialize readonly property Test::$prop from global scope
68+
Cannot unset readonly property Test::$prop from global scope
69+
Test::__isset(prop)
70+
bool(true)
71+
Test::__get(prop)
72+
int(1)
73+
Test::__set(prop, 2)
74+
Test::__unset(prop)

Zend/zend_object_handlers.c

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -800,11 +800,6 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
800800
variable_ptr, value, IS_TMP_VAR, property_uses_strict_types());
801801
goto exit;
802802
}
803-
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY)
804-
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "initialize"))) {
805-
variable_ptr = &EG(error_zval);
806-
goto exit;
807-
}
808803
if (Z_PROP_FLAG_P(variable_ptr) == IS_PROP_UNINIT) {
809804
/* Writes to uninitialized typed properties bypass __set(). */
810805
goto write_std_property;
@@ -855,6 +850,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
855850

856851
Z_TRY_ADDREF_P(value);
857852
if (UNEXPECTED(prop_info)) {
853+
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY)
854+
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "initialize"))) {
855+
Z_TRY_DELREF_P(value);
856+
variable_ptr = &EG(error_zval);
857+
goto exit;
858+
}
859+
858860
ZVAL_COPY_VALUE(&tmp, value);
859861
if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()))) {
860862
zval_ptr_dtor(value);
@@ -1093,11 +1095,12 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
10931095
}
10941096
return;
10951097
}
1096-
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY)
1097-
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "unset"))) {
1098-
return;
1099-
}
11001098
if (UNEXPECTED(Z_PROP_FLAG_P(slot) == IS_PROP_UNINIT)) {
1099+
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY)
1100+
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "unset"))) {
1101+
return;
1102+
}
1103+
11011104
/* Reset the IS_PROP_UNINIT flag, if it exists and bypass __unset(). */
11021105
Z_PROP_FLAG_P(slot) = 0;
11031106
return;

0 commit comments

Comments
 (0)