Skip to content

Commit a86796e

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: By-ref modification of typed and readonly props through ArrayIterator
2 parents 8792241 + 3d8107f commit a86796e

File tree

4 files changed

+128
-5
lines changed

4 files changed

+128
-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: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ typedef struct _spl_array_object {
5656
zend_object std;
5757
} spl_array_object;
5858

59+
typedef struct _spl_array_iterator {
60+
zend_object_iterator it;
61+
bool by_ref;
62+
} spl_array_iterator;
63+
5964
static inline spl_array_object *spl_array_from_obj(zend_object *obj) /* {{{ */ {
6065
return (spl_array_object*)((char*)(obj) - XtOffsetOf(spl_array_object, std));
6166
}
@@ -978,12 +983,34 @@ static int spl_array_it_valid(zend_object_iterator *iter) /* {{{ */
978983

979984
static zval *spl_array_it_get_current_data(zend_object_iterator *iter) /* {{{ */
980985
{
986+
spl_array_iterator *array_iter = (spl_array_iterator*)iter;
981987
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
982988
HashTable *aht = spl_array_get_hash_table(object);
983989
zval *data = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, object));
984990
if (data && Z_TYPE_P(data) == IS_INDIRECT) {
985991
data = Z_INDIRECT_P(data);
986992
}
993+
// ZEND_FE_FETCH_RW converts the value to a reference but doesn't know the source is a property.
994+
// Typed properties must add a type source to the reference, and readonly properties must fail.
995+
if (array_iter->by_ref
996+
&& Z_TYPE_P(data) != IS_REFERENCE
997+
&& Z_TYPE(object->array) == IS_OBJECT
998+
&& !(object->ar_flags & (SPL_ARRAY_IS_SELF|SPL_ARRAY_USE_OTHER))) {
999+
zend_string *key;
1000+
zend_hash_get_current_key_ex(aht, &key, NULL, spl_array_get_pos_ptr(aht, object));
1001+
zend_class_entry *ce = Z_OBJCE(object->array);
1002+
zend_property_info *prop_info = zend_get_property_info(ce, key, true);
1003+
if (ZEND_TYPE_IS_SET(prop_info->type)) {
1004+
if (prop_info->flags & ZEND_ACC_READONLY) {
1005+
zend_throw_error(NULL,
1006+
"Cannot acquire reference to readonly property %s::$%s",
1007+
ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(key));
1008+
return NULL;
1009+
}
1010+
ZVAL_NEW_REF(data, data);
1011+
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), prop_info);
1012+
}
1013+
}
9871014
return data;
9881015
}
9891016
/* }}} */
@@ -1103,13 +1130,14 @@ static const zend_object_iterator_funcs spl_array_it_funcs = {
11031130

11041131
static zend_object_iterator *spl_array_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
11051132
{
1106-
zend_object_iterator *iterator = emalloc(sizeof(zend_object_iterator));
1107-
zend_iterator_init(iterator);
1133+
spl_array_iterator *iterator = emalloc(sizeof(spl_array_iterator));
1134+
zend_iterator_init(&iterator->it);
11081135

1109-
ZVAL_OBJ_COPY(&iterator->data, Z_OBJ_P(object));
1110-
iterator->funcs = &spl_array_it_funcs;
1136+
ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object));
1137+
iterator->it.funcs = &spl_array_it_funcs;
1138+
iterator->by_ref = by_ref;
11111139

1112-
return iterator;
1140+
return &iterator->it;
11131141
}
11141142
/* }}} */
11151143

0 commit comments

Comments
 (0)