Skip to content

Commit 49b2ff5

Browse files
NathanFreemanGirgias
authored andcommitted
Fix GH-10519: Array Data Address Reference Issue
We need to carry around a reference to the underlying Bucket to be able to modify it by reference. Closes GH-10749 Signed-off-by: George Peter Banyard <[email protected]>
1 parent ad705af commit 49b2ff5

File tree

5 files changed

+176
-8
lines changed

5 files changed

+176
-8
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ PHP NEWS
2525
. Add missing error checks on EVP_MD_CTX_create() and EVP_VerifyInit().
2626
(nielsdos)
2727

28+
- SPL:
29+
. Fixed bug GH-10519 (Array Data Address Reference Issue). (Nathan Freeman)
30+
2831
16 Mar 2023, PHP 8.1.17
2932

3033
- Core:

ext/spl/spl_array.c

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
#include "php_spl.h"
3030
#include "spl_array_arginfo.h"
3131
#include "spl_functions.h"
32-
#include "spl_engine.h"
3332
#include "spl_iterators.h"
3433
#include "spl_array.h"
3534
#include "spl_exceptions.h"
@@ -63,6 +62,8 @@ typedef struct _spl_array_object {
6362
uint32_t ht_iter;
6463
int ar_flags;
6564
unsigned char nApplyCount;
65+
bool is_child;
66+
Bucket *bucket;
6667
zend_function *fptr_offset_get;
6768
zend_function *fptr_offset_set;
6869
zend_function *fptr_offset_has;
@@ -167,6 +168,8 @@ static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_o
167168
object_properties_init(&intern->std, class_type);
168169

169170
intern->ar_flags = 0;
171+
intern->is_child = false;
172+
intern->bucket = NULL;
170173
intern->ce_get_iterator = spl_ce_ArrayIterator;
171174
if (orig) {
172175
spl_array_object *other = spl_array_from_obj(orig);
@@ -477,6 +480,22 @@ static zval *spl_array_read_dimension(zend_object *object, zval *offset, int typ
477480
return spl_array_read_dimension_ex(1, object, offset, type, rv);
478481
} /* }}} */
479482

483+
/*
484+
* The assertion(HT_ASSERT_RC1(ht)) failed because the refcount was increased manually when intern->is_child is true.
485+
* We have to set the refcount to 1 to make assertion success and restore the refcount to the original value after
486+
* modifying the array when intern->is_child is true.
487+
*/
488+
static uint32_t spl_array_set_refcount(bool is_child, HashTable *ht, uint32_t refcount) /* {{{ */
489+
{
490+
uint32_t old_refcount = 0;
491+
if (is_child) {
492+
old_refcount = GC_REFCOUNT(ht);
493+
GC_SET_REFCOUNT(ht, refcount);
494+
}
495+
496+
return old_refcount;
497+
} /* }}} */
498+
480499
static void spl_array_write_dimension_ex(int check_inherited, zend_object *object, zval *offset, zval *value) /* {{{ */
481500
{
482501
spl_array_object *intern = spl_array_from_obj(object);
@@ -500,9 +519,16 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
500519
}
501520

502521
Z_TRY_ADDREF_P(value);
522+
523+
uint32_t refcount = 0;
503524
if (!offset || Z_TYPE_P(offset) == IS_NULL) {
504525
ht = spl_array_get_hash_table(intern);
526+
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
505527
zend_hash_next_index_insert(ht, value);
528+
529+
if (refcount) {
530+
spl_array_set_refcount(intern->is_child, ht, refcount);
531+
}
506532
return;
507533
}
508534

@@ -513,12 +539,17 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
513539
}
514540

515541
ht = spl_array_get_hash_table(intern);
542+
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
516543
if (key.key) {
517544
zend_hash_update_ind(ht, key.key, value);
518545
spl_hash_key_release(&key);
519546
} else {
520547
zend_hash_index_update(ht, key.h, value);
521548
}
549+
550+
if (refcount) {
551+
spl_array_set_refcount(intern->is_child, ht, refcount);
552+
}
522553
} /* }}} */
523554

524555
static void spl_array_write_dimension(zend_object *object, zval *offset, zval *value) /* {{{ */
@@ -548,6 +579,8 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec
548579
}
549580

550581
ht = spl_array_get_hash_table(intern);
582+
uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1);
583+
551584
if (key.key) {
552585
zval *data = zend_hash_find(ht, key.key);
553586
if (data) {
@@ -570,6 +603,10 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec
570603
} else {
571604
zend_hash_index_del(ht, key.h);
572605
}
606+
607+
if (refcount) {
608+
spl_array_set_refcount(intern->is_child, ht, refcount);
609+
}
573610
} /* }}} */
574611

575612
static void spl_array_unset_dimension(zend_object *object, zval *offset) /* {{{ */
@@ -1058,6 +1095,16 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar
10581095
} else {
10591096
//??? TODO: try to avoid array duplication
10601097
ZVAL_ARR(&intern->array, zend_array_dup(Z_ARR_P(array)));
1098+
1099+
if (intern->is_child) {
1100+
Z_TRY_DELREF_P(&intern->bucket->val);
1101+
/*
1102+
* replace bucket->val with copied array, so the changes between
1103+
* parent and child object can affect each other.
1104+
*/
1105+
intern->bucket->val = intern->array;
1106+
Z_TRY_ADDREF_P(&intern->array);
1107+
}
10611108
}
10621109
} else {
10631110
if (Z_OBJ_HT_P(array) == &spl_handler_ArrayObject || Z_OBJ_HT_P(array) == &spl_handler_ArrayIterator) {
@@ -1544,6 +1591,22 @@ PHP_METHOD(RecursiveArrayIterator, hasChildren)
15441591
}
15451592
/* }}} */
15461593

1594+
static void spl_instantiate_child_arg(zend_class_entry *pce, zval *retval, zval *arg1, zval *arg2) /* {{{ */
1595+
{
1596+
object_init_ex(retval, pce);
1597+
spl_array_object *new_intern = Z_SPLARRAY_P(retval);
1598+
/*
1599+
* set new_intern->is_child is true to indicate that the object was created by
1600+
* RecursiveArrayIterator::getChildren() method.
1601+
*/
1602+
new_intern->is_child = true;
1603+
1604+
/* find the bucket of parent object. */
1605+
new_intern->bucket = (Bucket *)((char *)(arg1) - XtOffsetOf(Bucket, val));;
1606+
zend_call_known_instance_method_with_2_params(pce->constructor, Z_OBJ_P(retval), NULL, arg1, arg2);
1607+
}
1608+
/* }}} */
1609+
15471610
/* {{{ Create a sub iterator for the current element (same class as $this) */
15481611
PHP_METHOD(RecursiveArrayIterator, getChildren)
15491612
{
@@ -1574,7 +1637,7 @@ PHP_METHOD(RecursiveArrayIterator, getChildren)
15741637
}
15751638

15761639
ZVAL_LONG(&flags, intern->ar_flags);
1577-
spl_instantiate_arg_ex2(Z_OBJCE_P(ZEND_THIS), return_value, entry, &flags);
1640+
spl_instantiate_child_arg(Z_OBJCE_P(ZEND_THIS), return_value, entry, &flags);
15781641
}
15791642
/* }}} */
15801643

ext/spl/tests/bug42654.phpt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@ object(RecursiveArrayIterator)#%d (1) {
110110
[2]=>
111111
array(2) {
112112
[2]=>
113-
string(4) "val2"
113+
string(5) "alter"
114114
[3]=>
115115
array(1) {
116116
[3]=>
117-
string(4) "val3"
117+
string(5) "alter"
118118
}
119119
}
120120
[4]=>
@@ -129,11 +129,11 @@ object(RecursiveArrayIterator)#%d (1) {
129129
[2]=>
130130
array(2) {
131131
[2]=>
132-
string(4) "val2"
132+
string(5) "alter"
133133
[3]=>
134134
array(1) {
135135
[3]=>
136-
string(4) "val3"
136+
string(5) "alter"
137137
}
138138
}
139139
[4]=>
@@ -146,11 +146,11 @@ array(3) {
146146
[2]=>
147147
array(2) {
148148
[2]=>
149-
string(4) "val2"
149+
string(5) "alter"
150150
[3]=>
151151
array(1) {
152152
[3]=>
153-
string(4) "val3"
153+
string(5) "alter"
154154
}
155155
}
156156
[4]=>

ext/spl/tests/bug42654_2.phpt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Bug #42654 (RecursiveIteratorIterator modifies only part of leaves)
3+
--FILE--
4+
<?php
5+
$data = array ('key1' => 'val1', array('key2' => 'val2'), 'key3' => 'val3');
6+
7+
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data));
8+
foreach($iterator as $foo) {
9+
$key = $iterator->key();
10+
switch($key) {
11+
case 'key1': // first level
12+
case 'key2': // recursive level
13+
echo "update $key".PHP_EOL;
14+
$iterator->offsetSet($key, 'alter');
15+
}
16+
}
17+
$copy = $iterator->getArrayCopy();
18+
var_dump($copy);
19+
?>
20+
--EXPECT--
21+
update key1
22+
update key2
23+
array(3) {
24+
["key1"]=>
25+
string(5) "alter"
26+
[0]=>
27+
array(1) {
28+
["key2"]=>
29+
string(5) "alter"
30+
}
31+
["key3"]=>
32+
string(4) "val3"
33+
}

ext/spl/tests/gh10519.phpt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
--TEST--
2+
Bug GH-10519 (Array Data Address Reference Issue)
3+
--FILE--
4+
<?php
5+
interface DataInterface extends JsonSerializable, RecursiveIterator, Iterator
6+
{
7+
public static function init(Traversable $data): DataInterface;
8+
}
9+
10+
class A extends RecursiveArrayIterator implements DataInterface
11+
{
12+
public function __construct($data)
13+
{
14+
parent::__construct($data);
15+
}
16+
17+
public static function init($data): DataInterface
18+
{
19+
return new static($data);
20+
}
21+
22+
public function getCols(string $colname): array
23+
{
24+
return array_column($this->getArrayCopy(), $colname);
25+
}
26+
27+
public function bugySetMethod($key, $value)
28+
{
29+
$data = &$this;
30+
31+
while ($data->hasChildren()) {
32+
$data = $data->getChildren();
33+
}
34+
$data->offsetSet($key, $value);
35+
}
36+
37+
public function jsonSerialize(): mixed
38+
{
39+
return $this;
40+
}
41+
}
42+
43+
$a = [
44+
'test' => [
45+
'a' => (object) [2 => '',3 => '',4 => ''],
46+
]
47+
];
48+
49+
$example = A::init($a);
50+
$example->bugySetMethod(5, 'in here');
51+
var_dump(json_encode($example));
52+
var_dump(json_encode($a));
53+
54+
$b = [
55+
'test' => [
56+
'b' => [2 => '',3 => '',4 => ''],
57+
]
58+
];
59+
$example = A::init($b);
60+
61+
$example->bugySetMethod(5, 'must be here');
62+
var_dump(json_encode($example));
63+
var_dump(json_encode($b));
64+
?>
65+
--EXPECT--
66+
string(51) "{"test":{"a":{"2":"","3":"","4":"","5":"in here"}}}"
67+
string(51) "{"test":{"a":{"2":"","3":"","4":"","5":"in here"}}}"
68+
string(56) "{"test":{"b":{"2":"","3":"","4":"","5":"must be here"}}}"
69+
string(37) "{"test":{"b":{"2":"","3":"","4":""}}}"

0 commit comments

Comments
 (0)