Skip to content

Commit c2c6730

Browse files
committed
Fix GH-21687: array_walk creates references to readonly properties
By-ref callbacks throw the same "Cannot acquire reference to readonly property" error foreach uses. By-val callbacks copy the value into a temp so MAKE_REF does not corrupt the slot. Closes GH-21687
1 parent e228395 commit c2c6730

2 files changed

Lines changed: 81 additions & 3 deletions

File tree

Zend/tests/gh21687.phpt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
GH-21687 (array_walk corrupts readonly and enum properties by wrapping them in IS_REFERENCE)
3+
--FILE--
4+
<?php
5+
enum Foo: int {
6+
case Bar = 0;
7+
}
8+
9+
$bar = Foo::Bar;
10+
array_walk($bar, function($v) {});
11+
var_dump($bar);
12+
13+
try {
14+
array_walk($bar, function(&$v) { $v = 1; });
15+
} catch (\Error $e) {
16+
echo $e->getMessage() . "\n";
17+
}
18+
var_dump($bar);
19+
20+
class MyObj {
21+
public function __construct(public readonly string $name = "test") {}
22+
}
23+
24+
$obj = new MyObj();
25+
array_walk($obj, function($v) {});
26+
echo $obj->name . "\n";
27+
28+
try {
29+
array_walk($obj, function(&$v) { $v = "modified"; });
30+
} catch (\Error $e) {
31+
echo $e->getMessage() . "\n";
32+
}
33+
echo $obj->name . "\n";
34+
35+
class Container {
36+
public function __construct(public readonly array $items = ["a", "b"]) {}
37+
}
38+
$c = new Container();
39+
array_walk_recursive($c, function($v) {});
40+
var_dump($c->items);
41+
?>
42+
--EXPECT--
43+
enum(Foo::Bar)
44+
Cannot acquire reference to readonly property Foo::$name
45+
enum(Foo::Bar)
46+
test
47+
Cannot acquire reference to readonly property MyObj::$name
48+
test
49+
array(2) {
50+
[0]=>
51+
string(1) "a"
52+
[1]=>
53+
string(1) "b"
54+
}

ext/standard/array.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,7 @@ static zend_result php_array_walk(
13611361
{
13621362
zval args[3], /* Arguments to userland function */
13631363
retval, /* Return value - unused */
1364+
temp,
13641365
*zv;
13651366
HashTable *target_hash = HASH_OF(array);
13661367
HashPosition pos;
@@ -1375,6 +1376,10 @@ static zend_result php_array_walk(
13751376
return result;
13761377
}
13771378

1379+
zend_function *callback = context->fci_cache.function_handler;
1380+
bool callback_by_ref = callback != NULL
1381+
&& ARG_SHOULD_BE_SENT_BY_REF(callback, 1);
1382+
13781383
/* Set up known arguments */
13791384
ZVAL_UNDEF(&args[1]);
13801385
if (userdata) {
@@ -1390,6 +1395,8 @@ static zend_result php_array_walk(
13901395

13911396
/* Iterate through hash */
13921397
do {
1398+
bool used_temp = false;
1399+
13931400
/* Retrieve value */
13941401
zv = zend_hash_get_current_data_ex(target_hash, &pos);
13951402
if (zv == NULL) {
@@ -1407,10 +1414,23 @@ static zend_result php_array_walk(
14071414
/* Add type source for property references. */
14081415
if (Z_TYPE_P(zv) != IS_REFERENCE && Z_TYPE_P(array) == IS_OBJECT) {
14091416
zend_property_info *prop_info =
1410-
zend_get_typed_property_info_for_slot(Z_OBJ_P(array), zv);
1417+
zend_get_property_info_for_slot(Z_OBJ_P(array), zv);
14111418
if (prop_info) {
1412-
ZVAL_NEW_REF(zv, zv);
1413-
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(zv), prop_info);
1419+
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
1420+
if (callback_by_ref) {
1421+
zend_throw_error(NULL,
1422+
"Cannot acquire reference to readonly property %s::$%s",
1423+
ZSTR_VAL(prop_info->ce->name),
1424+
zend_get_unmangled_property_name(prop_info->name));
1425+
break;
1426+
}
1427+
ZVAL_COPY(&temp, zv);
1428+
zv = &temp;
1429+
used_temp = true;
1430+
} else if (ZEND_TYPE_IS_SET(prop_info->type)) {
1431+
ZVAL_NEW_REF(zv, zv);
1432+
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(zv), prop_info);
1433+
}
14141434
}
14151435
}
14161436
}
@@ -1461,6 +1481,10 @@ static zend_result php_array_walk(
14611481

14621482
zval_ptr_dtor_str(&args[1]);
14631483

1484+
if (used_temp) {
1485+
zval_ptr_dtor(&temp);
1486+
}
1487+
14641488
if (result == FAILURE) {
14651489
break;
14661490
}

0 commit comments

Comments
 (0)