Skip to content

Commit 1105737

Browse files
committed
Disallow assigning reference to unset readonly property
Closes GH-7942 Closes GH-8188
1 parent 26d63c7 commit 1105737

File tree

9 files changed

+250
-13
lines changed

9 files changed

+250
-13
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ PHP NEWS
3030
. Fixed bug GH-8655 (Casting an object to array does not unwrap refcount=1
3131
references). (Nicolas Grekas)
3232
. Fixed potential use after free in php_binary_init(). (Heiko Weber)
33+
. Fixed bug GH-7942 (Indirect mutation of readonly properties through
34+
references). (ilutov)
3335

3436
- CLI:
3537
. Fixed GH-8827 (Intentionally closing std handles no longer possible). (cmb)

Zend/tests/readonly_props/array_append_initialization.phpt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ function init() {
1919
var_dump($c->a);
2020
}
2121

22-
(new C)->init();
22+
try {
23+
(new C)->init();
24+
} catch (Error $e) {
25+
echo $e->getMessage(), "\n";
26+
}
2327

2428
try {
2529
init();
@@ -29,8 +33,5 @@ try {
2933

3034
?>
3135
--EXPECT--
32-
array(1) {
33-
[0]=>
34-
int(1)
35-
}
36-
Cannot initialize readonly property C::$a from global scope
36+
Cannot indirectly modify readonly property C::$a
37+
Cannot indirectly modify readonly property C::$a

Zend/tests/readonly_props/gh7942.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-7942: Disallow assigning reference to unset readonly property
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public readonly int $bar;
8+
public function __construct(int &$bar) {
9+
$this->bar = &$bar;
10+
}
11+
}
12+
13+
try {
14+
$i = 42;
15+
new Foo($i);
16+
} catch (Error $e) {
17+
echo $e->getMessage(), PHP_EOL;
18+
}
19+
20+
?>
21+
--EXPECT--
22+
Cannot indirectly modify readonly property Foo::$bar
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
--TEST--
2+
Readonly variations
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public readonly int $prop;
8+
9+
public function init() {
10+
$this->prop = 1;
11+
}
12+
13+
public function r() {
14+
echo $this->prop;
15+
}
16+
17+
public function w() {
18+
$this->prop = 1;
19+
echo 'done';
20+
}
21+
22+
public function rw() {
23+
$this->prop += 1;
24+
echo 'done';
25+
}
26+
27+
public function im() {
28+
$this->prop[] = 1;
29+
echo 'done';
30+
}
31+
32+
public function is() {
33+
echo (int) isset($this->prop);
34+
}
35+
36+
public function us() {
37+
unset($this->prop);
38+
echo 'done';
39+
}
40+
}
41+
42+
function r($test) {
43+
echo $test->prop;
44+
}
45+
46+
function w($test) {
47+
$test->prop = 0;
48+
echo 'done';
49+
}
50+
51+
function rw($test) {
52+
$test->prop += 1;
53+
echo 'done';
54+
}
55+
56+
function im($test) {
57+
$test->prop[] = 1;
58+
echo 'done';
59+
}
60+
61+
function is($test) {
62+
echo (int) isset($test->prop);
63+
}
64+
65+
function us($test) {
66+
unset($test->prop);
67+
echo 'done';
68+
}
69+
70+
foreach ([true, false] as $init) {
71+
foreach ([true, false] as $scope) {
72+
foreach (['r', 'w', 'rw', 'im', 'is', 'us'] as $op) {
73+
$test = new Test();
74+
if ($init) {
75+
$test->init();
76+
}
77+
78+
echo 'Init: ' . ((int) $init) . ', scope: ' . ((int) $scope) . ', op: ' . $op . ": ";
79+
try {
80+
if ($scope) {
81+
$test->{$op}();
82+
} else {
83+
$op($test);
84+
}
85+
} catch (Error $e) {
86+
echo $e->getMessage();
87+
}
88+
echo "\n";
89+
}
90+
}
91+
}
92+
93+
?>
94+
--EXPECT--
95+
Init: 1, scope: 1, op: r: 1
96+
Init: 1, scope: 1, op: w: Cannot modify readonly property Test::$prop
97+
Init: 1, scope: 1, op: rw: Cannot modify readonly property Test::$prop
98+
Init: 1, scope: 1, op: im: Cannot modify readonly property Test::$prop
99+
Init: 1, scope: 1, op: is: 1
100+
Init: 1, scope: 1, op: us: Cannot unset readonly property Test::$prop
101+
Init: 1, scope: 0, op: r: 1
102+
Init: 1, scope: 0, op: w: Cannot modify readonly property Test::$prop
103+
Init: 1, scope: 0, op: rw: Cannot modify readonly property Test::$prop
104+
Init: 1, scope: 0, op: im: Cannot modify readonly property Test::$prop
105+
Init: 1, scope: 0, op: is: 1
106+
Init: 1, scope: 0, op: us: Cannot unset readonly property Test::$prop
107+
Init: 0, scope: 1, op: r: Typed property Test::$prop must not be accessed before initialization
108+
Init: 0, scope: 1, op: w: done
109+
Init: 0, scope: 1, op: rw: Typed property Test::$prop must not be accessed before initialization
110+
Init: 0, scope: 1, op: im: Cannot indirectly modify readonly property Test::$prop
111+
Init: 0, scope: 1, op: is: 0
112+
Init: 0, scope: 1, op: us: done
113+
Init: 0, scope: 0, op: r: Typed property Test::$prop must not be accessed before initialization
114+
Init: 0, scope: 0, op: w: Cannot initialize readonly property Test::$prop from global scope
115+
Init: 0, scope: 0, op: rw: Typed property Test::$prop must not be accessed before initialization
116+
Init: 0, scope: 0, op: im: Cannot indirectly modify readonly property Test::$prop
117+
Init: 0, scope: 0, op: is: 0
118+
Init: 0, scope: 0, op: us: Cannot unset readonly property Test::$prop from global scope
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
--TEST--
2+
Readonly nested variations
3+
--FILE--
4+
<?php
5+
6+
class Inner {
7+
public int $prop = 1;
8+
public array $array = [];
9+
}
10+
11+
class Test {
12+
public readonly Inner $prop;
13+
14+
public function init() {
15+
$this->prop = new Inner();
16+
}
17+
}
18+
19+
function r($test) {
20+
echo $test->prop->prop;
21+
}
22+
23+
function w($test) {
24+
$test->prop->prop = 0;
25+
echo 'done';
26+
}
27+
28+
function rw($test) {
29+
$test->prop->prop += 1;
30+
echo 'done';
31+
}
32+
33+
function im($test) {
34+
$test->prop->array[] = 1;
35+
echo 'done';
36+
}
37+
38+
function is($test) {
39+
echo (int) isset($test->prop->prop);
40+
}
41+
42+
function us($test) {
43+
unset($test->prop->prop);
44+
echo 'done';
45+
}
46+
47+
foreach ([true, false] as $init) {
48+
foreach (['r', 'w', 'rw', 'im', 'is', 'us'] as $op) {
49+
$test = new Test();
50+
if ($init) {
51+
$test->init();
52+
}
53+
54+
echo 'Init: ' . ((int) $init) . ', op: ' . $op . ": ";
55+
try {
56+
$op($test);
57+
} catch (Error $e) {
58+
echo $e->getMessage();
59+
}
60+
echo "\n";
61+
}
62+
}
63+
64+
?>
65+
--EXPECT--
66+
Init: 1, op: r: 1
67+
Init: 1, op: w: done
68+
Init: 1, op: rw: done
69+
Init: 1, op: im: done
70+
Init: 1, op: is: 1
71+
Init: 1, op: us: done
72+
Init: 0, op: r: Typed property Test::$prop must not be accessed before initialization
73+
Init: 0, op: w: Cannot indirectly modify readonly property Test::$prop
74+
Init: 0, op: rw: Typed property Test::$prop must not be accessed before initialization
75+
Init: 0, op: im: Cannot indirectly modify readonly property Test::$prop
76+
Init: 0, op: is: 0
77+
Init: 0, op: us: done

Zend/zend_execute.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,12 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(
849849
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
850850
}
851851

852+
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modification_error(zend_property_info *info)
853+
{
854+
zend_throw_error(NULL, "Cannot indirectly modify readonly property %s::$%s",
855+
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
856+
}
857+
852858
static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) {
853859
if (zend_string_equals_literal_ci(name, "self")) {
854860
return self_ce;

Zend/zend_execute.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ ZEND_API ZEND_COLD zval* ZEND_FASTCALL zend_undefined_index_write(HashTable *ht,
7474
ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void);
7575

7676
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(zend_property_info *info);
77+
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modification_error(zend_property_info *info);
7778

7879
ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg);
7980
ZEND_API ZEND_COLD void zend_verify_arg_error(

Zend/zend_object_handlers.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,17 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
604604
}
605605
}
606606
goto exit;
607+
} else {
608+
if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
609+
if (type == BP_VAR_W || type == BP_VAR_RW) {
610+
zend_readonly_property_indirect_modification_error(prop_info);
611+
retval = &EG(uninitialized_zval);
612+
goto exit;
613+
} else if (type == BP_VAR_UNSET) {
614+
retval = &EG(uninitialized_zval);
615+
goto exit;
616+
}
617+
}
607618
}
608619
if (UNEXPECTED(Z_PROP_FLAG_P(retval) == IS_PROP_UNINIT)) {
609620
/* Skip __get() for uninitialized typed properties */
@@ -1024,9 +1035,9 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
10241035
ZVAL_NULL(retval);
10251036
zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name));
10261037
}
1027-
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)
1028-
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "initialize")) {
1029-
retval = &EG(error_zval);
1038+
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
1039+
/* Readonly property, delegate to read_property + write_property. */
1040+
retval = NULL;
10301041
}
10311042
} else {
10321043
/* we do have getter - fail and let it try again with usual get/set */

ext/opcache/tests/jit/fetch_obj_006.phpt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ $appendProp2 = (function() {
2121
$this->prop[] = 1;
2222
})->bindTo($test, Test::class);
2323
$appendProp2();
24-
$appendProp2();
2524
?>
2625
--EXPECTF--
27-
Fatal error: Uncaught Error: Cannot modify readonly property Test::$prop in %sfetch_obj_006.php:13
26+
Fatal error: Uncaught Error: Cannot indirectly modify readonly property Test::$prop in %s:%d
2827
Stack trace:
29-
#0 %sfetch_obj_006.php(16): Test->{closure}()
28+
#0 %sfetch_obj_006.php(15): Test->{closure}()
3029
#1 {main}
31-
thrown in %sfetch_obj_006.php on line 13
30+
thrown in %sfetch_obj_006.php on line 13

0 commit comments

Comments
 (0)