Skip to content

Commit 8808371

Browse files
committed
Dirty solution for ref mod to typed and readonly props through ArrayIterator
Fixes GH-10844
1 parent 90f5b2b commit 8808371

File tree

4 files changed

+132
-5
lines changed

4 files changed

+132
-5
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Typed property type coercion through ArrayIterator
3+
--FILE--
4+
<?php
5+
class A implements IteratorAggregate {
6+
function __construct(
7+
public string $foo = 'bar'
8+
) {}
9+
10+
function getIterator(): Traversable {
11+
return new ArrayIterator($this);
12+
}
13+
}
14+
15+
$obj = new A;
16+
foreach ($obj as $k => &$v) {
17+
$v = 42;
18+
}
19+
20+
var_dump($obj);
21+
?>
22+
--EXPECT--
23+
object(A)#1 (1) {
24+
["foo"]=>
25+
&string(2) "42"
26+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
Typed property type error through ArrayIterator
3+
--FILE--
4+
<?php
5+
class A implements IteratorAggregate {
6+
function __construct(
7+
public string $foo = 'bar'
8+
) {}
9+
10+
function getIterator(): Traversable {
11+
return new ArrayIterator($this);
12+
}
13+
}
14+
15+
$obj = new A;
16+
foreach ($obj as $k => &$v) {
17+
try {
18+
$v = [];
19+
} catch (Throwable $e) {
20+
echo $e->getMessage(), "\n";
21+
}
22+
}
23+
foreach ($obj as $k => &$v) {
24+
try {
25+
$v = [];
26+
} catch (Throwable $e) {
27+
echo $e->getMessage(), "\n";
28+
}
29+
}
30+
31+
var_dump($obj);
32+
?>
33+
--EXPECT--
34+
Cannot assign array to reference held by property A::$foo of type string
35+
Cannot assign array to reference held by property A::$foo of type string
36+
object(A)#1 (1) {
37+
["foo"]=>
38+
&string(3) "bar"
39+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Readonly property modification error through ArrayIterator
3+
--FILE--
4+
<?php
5+
class A implements IteratorAggregate {
6+
function __construct(
7+
public readonly string $foo = 'bar'
8+
) {}
9+
10+
function getIterator(): Traversable {
11+
return new ArrayIterator($this);
12+
}
13+
}
14+
15+
$obj = new A;
16+
17+
try {
18+
foreach ($obj as $k => &$v) {}
19+
} catch (Throwable $e) {
20+
echo $e->getMessage(), "\n";
21+
}
22+
23+
var_dump($obj);
24+
?>
25+
--EXPECT--
26+
Cannot acquire reference to readonly property A::$foo
27+
object(A)#1 (1) {
28+
["foo"]=>
29+
string(3) "bar"
30+
}

ext/spl/spl_array.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ typedef struct _spl_array_object {
7373
zend_object std;
7474
} spl_array_object;
7575

76+
typedef struct _spl_array_iterator {
77+
zend_object_iterator it;
78+
zend_class_entry *ce;
79+
zval value;
80+
bool by_ref;
81+
} spl_array_iterator;
82+
7683
static inline spl_array_object *spl_array_from_obj(zend_object *obj) /* {{{ */ {
7784
return (spl_array_object*)((char*)(obj) - XtOffsetOf(spl_array_object, std));
7885
}
@@ -1007,18 +1014,42 @@ static int spl_array_it_valid(zend_object_iterator *iter) /* {{{ */
10071014

10081015
static zval *spl_array_it_get_current_data(zend_object_iterator *iter) /* {{{ */
10091016
{
1017+
spl_array_iterator *array_iter = (spl_array_iterator*)iter;
10101018
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
10111019
HashTable *aht = spl_array_get_hash_table(object);
10121020

1021+
zval *data;
10131022
if (object->ar_flags & SPL_ARRAY_OVERLOADED_CURRENT) {
1014-
return zend_user_it_get_current_data(iter);
1023+
data = zend_user_it_get_current_data(iter);
10151024
} else {
1016-
zval *data = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, object));
1025+
data = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, object));
10171026
if (data && Z_TYPE_P(data) == IS_INDIRECT) {
10181027
data = Z_INDIRECT_P(data);
10191028
}
1020-
return data;
10211029
}
1030+
// ZEND_FE_FETCH_RW converts the value to a reference but doesn't know the source is a property.
1031+
// Typed properties must add a type source to the reference, and readonly properties must fail.
1032+
if (array_iter->by_ref
1033+
&& Z_TYPE_P(data) != IS_REFERENCE
1034+
&& !(object->ar_flags & SPL_ARRAY_IS_SELF)
1035+
&& !(object->ar_flags & SPL_ARRAY_USE_OTHER)
1036+
&& Z_TYPE(object->array) == IS_OBJECT) {
1037+
zend_string *key;
1038+
zend_hash_get_current_key_ex(aht, &key, NULL, spl_array_get_pos_ptr(aht, object));
1039+
zend_class_entry *ce = Z_OBJCE(object->array);
1040+
zend_property_info *prop_info = zend_get_property_info(ce, key, true);
1041+
if (ZEND_TYPE_IS_SET(prop_info->type)) {
1042+
if (prop_info->flags & ZEND_ACC_READONLY) {
1043+
zend_throw_error(NULL,
1044+
"Cannot acquire reference to readonly property %s::$%s",
1045+
ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(key));
1046+
return NULL;
1047+
}
1048+
ZVAL_NEW_REF(data, data);
1049+
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), prop_info);
1050+
}
1051+
}
1052+
return data;
10221053
}
10231054
/* }}} */
10241055

@@ -1156,22 +1187,23 @@ static const zend_object_iterator_funcs spl_array_it_funcs = {
11561187

11571188
zend_object_iterator *spl_array_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
11581189
{
1159-
zend_user_iterator *iterator;
1190+
spl_array_iterator *iterator;
11601191
spl_array_object *array_object = Z_SPLARRAY_P(object);
11611192

11621193
if (by_ref && (array_object->ar_flags & SPL_ARRAY_OVERLOADED_CURRENT)) {
11631194
zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
11641195
return NULL;
11651196
}
11661197

1167-
iterator = emalloc(sizeof(zend_user_iterator));
1198+
iterator = emalloc(sizeof(spl_array_iterator));
11681199

11691200
zend_iterator_init(&iterator->it);
11701201

11711202
ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object));
11721203
iterator->it.funcs = &spl_array_it_funcs;
11731204
iterator->ce = ce;
11741205
ZVAL_UNDEF(&iterator->value);
1206+
iterator->by_ref = by_ref;
11751207

11761208
return &iterator->it;
11771209
}

0 commit comments

Comments
 (0)