Skip to content

Commit 40908b1

Browse files
committed
Merge branch 'PHP-8.1'
* PHP-8.1: Disallow assigning reference to unset readonly property
2 parents 1a5414c + 1105737 commit 40908b1

File tree

8 files changed

+248
-13
lines changed

8 files changed

+248
-13
lines changed

Zend/tests/readonly_props/array_append_initialization.phpt

+7-6
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

+22
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
+118
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
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

+6
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,12 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(
853853
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
854854
}
855855

856+
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modification_error(zend_property_info *info)
857+
{
858+
zend_throw_error(NULL, "Cannot indirectly modify readonly property %s::$%s",
859+
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
860+
}
861+
856862
static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) {
857863
if (zend_string_equals_literal_ci(name, "self")) {
858864
return self_ce;

Zend/zend_execute.h

+1
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

+14-3
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,17 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
622622
}
623623
}
624624
goto exit;
625+
} else {
626+
if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
627+
if (type == BP_VAR_W || type == BP_VAR_RW) {
628+
zend_readonly_property_indirect_modification_error(prop_info);
629+
retval = &EG(uninitialized_zval);
630+
goto exit;
631+
} else if (type == BP_VAR_UNSET) {
632+
retval = &EG(uninitialized_zval);
633+
goto exit;
634+
}
635+
}
625636
}
626637
if (UNEXPECTED(Z_PROP_FLAG_P(retval) == IS_PROP_UNINIT)) {
627638
/* Skip __get() for uninitialized typed properties */
@@ -1051,9 +1062,9 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
10511062
ZVAL_NULL(retval);
10521063
zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name));
10531064
}
1054-
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)
1055-
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "initialize")) {
1056-
retval = &EG(error_zval);
1065+
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
1066+
/* Readonly property, delegate to read_property + write_property. */
1067+
retval = NULL;
10571068
}
10581069
} else {
10591070
/* we do have getter - fail and let it try again with usual get/set */

ext/opcache/tests/jit/fetch_obj_006.phpt

+3-4
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)