Skip to content

Commit

Permalink
Fix by ref assignments for ArrayObject only
Browse files Browse the repository at this point in the history
  • Loading branch information
Girgias committed Jun 10, 2024
1 parent a05e59d commit cbb5bc0
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 10 deletions.
6 changes: 6 additions & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -2810,6 +2810,12 @@ static zend_never_inline void zend_fetch_object_dimension_address(zval *result,
if (
!Z_ISREF_P(retval)
&& Z_TYPE_P(retval) != IS_OBJECT
/* Support indirect for:
* $ao[$i] = &$var;
* and
* $ao[] = &$var;
* cases */
&& Z_TYPE_P(retval) != IS_INDIRECT
) {
zend_class_entry *ce = obj->ce;
zend_throw_error(NULL, "%s::%s() must return a reference type",
Expand Down
71 changes: 66 additions & 5 deletions ext/spl/spl_array.c
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,27 @@ static zval *spl_array_fetch_dimension(zend_object *object, /* const */ zval *of
return spl_array_read_dimension_ex(object, offset, BP_VAR_W, rv);
}

/* We have a dedicated handler for ArrayObject as it needs to handle constructs like:
$ao = new ArrayObject;
foreach ([1, 2, 3] as $i => $var)
{
$ao[$i] = &$var;
}
$ao[] = &$var;
* which are normally reserved for arrays */
static zval *spl_array_object_fetch_dimension(zend_object *object, /* const */ zval *offset, zval *rv)
{
spl_array_object *intern = spl_array_from_obj(object);
zval *ret = spl_array_get_dimension_ptr(intern, object->ce->name, offset, BP_VAR_W);

if (ret == &EG(error_zval) || EG(exception)) {
return NULL;
}

ZVAL_INDIRECT(rv, ret);
return rv;
}

/*
* The assertion(HT_ASSERT_RC1(ht)) failed because the refcount was increased manually when intern->is_child is true.
* We have to set the refcount to 1 to make assertion success and restore the refcount to the original value after
Expand Down Expand Up @@ -500,13 +521,44 @@ static zval* spl_array_fetch_append(zend_object *object, zval *rv)
spl_array_set_refcount(intern->is_child, ht, refcount);
}

// TODO Use indirect to fix "Cannot assign by reference to an array dimension of an object" Error being thrown?
ZVAL_MAKE_REF(ret);
ZVAL_COPY(rv, ret);

return rv;
}

/* We have a dedicated handler for ArrayObject as it needs to handle constructs like:
$ao = new ArrayObject;
foreach ([1, 2, 3] as $i => $var)
{
$ao[$i] = &$var;
}
$ao[] = &$var;
* which are normally reserved for arrays */
static zval *spl_array_object_fetch_append(zend_object *object, zval *rv)
{
spl_array_object *intern = spl_array_from_obj(object);

if (spl_array_is_object(intern)) {
zend_throw_error(NULL, "Cannot append properties to objects, use %s::offsetSet() instead", ZSTR_VAL(object->ce->name));
return NULL;
}

HashTable *ht = spl_array_get_hash_table(intern);
uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1);

zval dummy;
ZVAL_NULL(&dummy);
zval *ret = zend_hash_next_index_insert(ht, &dummy);

if (refcount) {
spl_array_set_refcount(intern->is_child, ht, refcount);
}

ZVAL_INDIRECT(rv, ret);
return rv;
}

static void spl_array_unset_dimension(zend_object *object, /* const */ zval *offset) /* {{{ */
{
spl_array_object *intern = spl_array_from_obj(object);
Expand Down Expand Up @@ -1821,7 +1873,16 @@ PHP_METHOD(RecursiveArrayIterator, getChildren)
}
/* }}} */

static /* const */ zend_class_dimensions_functions spl_array_dimensions_functions = {
static /* const */ zend_class_dimensions_functions spl_array_object_dimensions_functions = {
.read_dimension = spl_array_read_dimension,
.has_dimension = spl_array_has_dimension,
.fetch_dimension = spl_array_object_fetch_dimension,
.write_dimension = spl_array_write_dimension,
.append = spl_array_append,
.fetch_append = spl_array_object_fetch_append,
.unset_dimension = spl_array_unset_dimension
};
static /* const */ zend_class_dimensions_functions spl_array_iterator_dimensions_functions = {
.read_dimension = spl_array_read_dimension,
.has_dimension = spl_array_has_dimension,
.fetch_dimension = spl_array_fetch_dimension,
Expand All @@ -1845,7 +1906,7 @@ PHP_MINIT_FUNCTION(spl_array)
);
spl_ce_ArrayObject->create_object = spl_array_object_new;
spl_ce_ArrayObject->default_object_handlers = &spl_handler_ArrayObject;
spl_ce_ArrayObject->dimension_handlers = &spl_array_dimensions_functions;
spl_ce_ArrayObject->dimension_handlers = &spl_array_object_dimensions_functions;

memcpy(&spl_handler_ArrayObject, &std_object_handlers, sizeof(zend_object_handlers));

Expand Down Expand Up @@ -1877,14 +1938,14 @@ PHP_MINIT_FUNCTION(spl_array)
spl_ce_ArrayIterator->create_object = spl_array_object_new;
spl_ce_ArrayIterator->default_object_handlers = &spl_handler_ArrayIterator;
spl_ce_ArrayIterator->get_iterator = spl_array_get_iterator;
spl_ce_ArrayIterator->dimension_handlers = &spl_array_dimensions_functions;
spl_ce_ArrayIterator->dimension_handlers = &spl_array_iterator_dimensions_functions;

memcpy(&spl_handler_ArrayIterator, &spl_handler_ArrayObject, sizeof(zend_object_handlers));

spl_ce_RecursiveArrayIterator = register_class_RecursiveArrayIterator(spl_ce_ArrayIterator, spl_ce_RecursiveIterator);
spl_ce_RecursiveArrayIterator->create_object = spl_array_object_new;
spl_ce_RecursiveArrayIterator->get_iterator = spl_array_get_iterator;
spl_ce_RecursiveArrayIterator->dimension_handlers = &spl_array_dimensions_functions;
spl_ce_RecursiveArrayIterator->dimension_handlers = &spl_array_iterator_dimensions_functions;

return SUCCESS;
}
Expand Down
12 changes: 12 additions & 0 deletions ext/spl/tests/ArrayObject/indirect_assign_reference_to_offset.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ foreach ([1, 2, 3] as $i => $var)
{
$ao[$i] = &$var;
}
var_dump($ao);
$ao[] = &$var;
var_dump($ao);
?>
Expand All @@ -36,6 +37,17 @@ array(4) {
&int(3)
}
Using ArrayObject
object(ArrayObject)#1 (1) {
["storage":"ArrayObject":private]=>
array(3) {
[0]=>
&int(3)
[1]=>
&int(3)
[2]=>
&int(3)
}
}
object(ArrayObject)#1 (1) {
["storage":"ArrayObject":private]=>
array(4) {
Expand Down
14 changes: 9 additions & 5 deletions ext/spl/tests/iterator_035.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@ $tmp = 1;

$a = new ArrayIterator();
$a[] = $tmp;
$a[] = &$tmp;

var_dump($a);
try {
$a[] = &$tmp;
var_dump($a);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}

echo "Done\n";
?>
--EXPECT--
object(ArrayIterator)#1 (1) {
["storage":"ArrayIterator":private]=>
array(2) {
array(1) {
[0]=>
int(1)
[1]=>
&int(1)
}
}
Error: Cannot assign by reference to an array dimension of an object
Done

0 comments on commit cbb5bc0

Please sign in to comment.