Merge branch 'PHP-8.2'

* PHP-8.2:
  By-ref modification of typed and readonly props through ArrayIterator
This commit is contained in:
Ilija Tovilo 2023-03-25 16:24:08 +01:00
commit a86796e52a
No known key found for this signature in database
GPG key ID: A4F5D403F118200A
4 changed files with 128 additions and 5 deletions

View file

@ -0,0 +1,26 @@
--TEST--
Typed property type coercion through ArrayIterator
--FILE--
<?php
class A implements IteratorAggregate {
function __construct(
public string $foo = 'bar'
) {}
function getIterator(): Traversable {
return new ArrayIterator($this);
}
}
$obj = new A;
foreach ($obj as $k => &$v) {
$v = 42;
}
var_dump($obj);
?>
--EXPECT--
object(A)#1 (1) {
["foo"]=>
&string(2) "42"
}

View file

@ -0,0 +1,39 @@
--TEST--
Typed property type error through ArrayIterator
--FILE--
<?php
class A implements IteratorAggregate {
function __construct(
public string $foo = 'bar'
) {}
function getIterator(): Traversable {
return new ArrayIterator($this);
}
}
$obj = new A;
foreach ($obj as $k => &$v) {
try {
$v = [];
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
}
foreach ($obj as $k => &$v) {
try {
$v = [];
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
}
var_dump($obj);
?>
--EXPECT--
Cannot assign array to reference held by property A::$foo of type string
Cannot assign array to reference held by property A::$foo of type string
object(A)#1 (1) {
["foo"]=>
&string(3) "bar"
}

View file

@ -0,0 +1,30 @@
--TEST--
Readonly property modification error through ArrayIterator
--FILE--
<?php
class A implements IteratorAggregate {
function __construct(
public readonly string $foo = 'bar'
) {}
function getIterator(): Traversable {
return new ArrayIterator($this);
}
}
$obj = new A;
try {
foreach ($obj as $k => &$v) {}
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
var_dump($obj);
?>
--EXPECT--
Cannot acquire reference to readonly property A::$foo
object(A)#1 (1) {
["foo"]=>
string(3) "bar"
}

View file

@ -56,6 +56,11 @@ typedef struct _spl_array_object {
zend_object std;
} spl_array_object;
typedef struct _spl_array_iterator {
zend_object_iterator it;
bool by_ref;
} spl_array_iterator;
static inline spl_array_object *spl_array_from_obj(zend_object *obj) /* {{{ */ {
return (spl_array_object*)((char*)(obj) - XtOffsetOf(spl_array_object, std));
}
@ -978,12 +983,34 @@ static int spl_array_it_valid(zend_object_iterator *iter) /* {{{ */
static zval *spl_array_it_get_current_data(zend_object_iterator *iter) /* {{{ */
{
spl_array_iterator *array_iter = (spl_array_iterator*)iter;
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
HashTable *aht = spl_array_get_hash_table(object);
zval *data = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, object));
if (data && Z_TYPE_P(data) == IS_INDIRECT) {
data = Z_INDIRECT_P(data);
}
// ZEND_FE_FETCH_RW converts the value to a reference but doesn't know the source is a property.
// Typed properties must add a type source to the reference, and readonly properties must fail.
if (array_iter->by_ref
&& Z_TYPE_P(data) != IS_REFERENCE
&& Z_TYPE(object->array) == IS_OBJECT
&& !(object->ar_flags & (SPL_ARRAY_IS_SELF|SPL_ARRAY_USE_OTHER))) {
zend_string *key;
zend_hash_get_current_key_ex(aht, &key, NULL, spl_array_get_pos_ptr(aht, object));
zend_class_entry *ce = Z_OBJCE(object->array);
zend_property_info *prop_info = zend_get_property_info(ce, key, true);
if (ZEND_TYPE_IS_SET(prop_info->type)) {
if (prop_info->flags & ZEND_ACC_READONLY) {
zend_throw_error(NULL,
"Cannot acquire reference to readonly property %s::$%s",
ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(key));
return NULL;
}
ZVAL_NEW_REF(data, data);
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), prop_info);
}
}
return data;
}
/* }}} */
@ -1103,13 +1130,14 @@ static const zend_object_iterator_funcs spl_array_it_funcs = {
static zend_object_iterator *spl_array_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
{
zend_object_iterator *iterator = emalloc(sizeof(zend_object_iterator));
zend_iterator_init(iterator);
spl_array_iterator *iterator = emalloc(sizeof(spl_array_iterator));
zend_iterator_init(&iterator->it);
ZVAL_OBJ_COPY(&iterator->data, Z_OBJ_P(object));
iterator->funcs = &spl_array_it_funcs;
ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object));
iterator->it.funcs = &spl_array_it_funcs;
iterator->by_ref = by_ref;
return iterator;
return &iterator->it;
}
/* }}} */