Skip to content

Commit cdbbc54

Browse files
Fix GH-17935: ArrayObject unserialize assertion failure on later modification
1 parent 46e55dd commit cdbbc54

File tree

5 files changed

+34
-60
lines changed

5 files changed

+34
-60
lines changed

ext/spl/spl_array.c

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ typedef struct _spl_array_object {
4444
uint32_t ht_iter;
4545
int ar_flags;
4646
unsigned char nApplyCount;
47-
bool is_child;
48-
Bucket *bucket;
4947
zend_function *fptr_offset_get;
5048
zend_function *fptr_offset_set;
5149
zend_function *fptr_offset_has;
@@ -166,8 +164,6 @@ static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_o
166164
object_properties_init(&intern->std, class_type);
167165

168166
intern->ar_flags = 0;
169-
intern->is_child = false;
170-
intern->bucket = NULL;
171167
intern->ce_get_iterator = spl_ce_ArrayIterator;
172168
if (orig) {
173169
spl_array_object *other = spl_array_from_obj(orig);
@@ -466,20 +462,12 @@ static zval *spl_array_read_dimension(zend_object *object, zval *offset, int typ
466462
} /* }}} */
467463

468464
/*
469-
* The assertion(HT_ASSERT_RC1(ht)) failed because the refcount was increased manually when intern->is_child is true.
470-
* We have to set the refcount to 1 to make assertion success and restore the refcount to the original value after
471-
* modifying the array when intern->is_child is true.
465+
* Before modifying the internal array, we must ensure exclusive ownership
466+
* to satisfy HT_ASSERT_RC1 in Zend hash operations.
467+
*
468+
* SEPARATE_ARRAY implements CoW: if the array is shared (RC > 1),
469+
* it creates a duplicate via zend_array_dup() and updates intern->array.
472470
*/
473-
static uint32_t spl_array_set_refcount(bool is_child, HashTable *ht, uint32_t refcount) /* {{{ */
474-
{
475-
uint32_t old_refcount = 0;
476-
if (is_child) {
477-
old_refcount = GC_REFCOUNT(ht);
478-
GC_SET_REFCOUNT(ht, refcount);
479-
}
480-
481-
return old_refcount;
482-
} /* }}} */
483471

484472
static void spl_array_write_dimension_ex(int check_inherited, zend_object *object, zval *offset, zval *value) /* {{{ */
485473
{
@@ -505,18 +493,15 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
505493

506494
Z_TRY_ADDREF_P(value);
507495

508-
uint32_t refcount = 0;
509496
if (!offset || Z_TYPE_P(offset) == IS_NULL) {
497+
if (Z_TYPE(intern->array) == IS_ARRAY) {
498+
SEPARATE_ARRAY(&intern->array);
499+
}
510500
ht = spl_array_get_hash_table(intern);
511501
if (UNEXPECTED(ht == intern->sentinel_array)) {
512502
return;
513503
}
514-
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
515504
zend_hash_next_index_insert(ht, value);
516-
517-
if (refcount) {
518-
spl_array_set_refcount(intern->is_child, ht, refcount);
519-
}
520505
return;
521506
}
522507

@@ -526,22 +511,20 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
526511
return;
527512
}
528513

514+
if (Z_TYPE(intern->array) == IS_ARRAY) {
515+
SEPARATE_ARRAY(&intern->array);
516+
}
529517
ht = spl_array_get_hash_table(intern);
530518
if (UNEXPECTED(ht == intern->sentinel_array)) {
531519
spl_hash_key_release(&key);
532520
return;
533521
}
534-
refcount = spl_array_set_refcount(intern->is_child, ht, 1);
535522
if (key.key) {
536523
zend_hash_update_ind(ht, key.key, value);
537524
spl_hash_key_release(&key);
538525
} else {
539526
zend_hash_index_update(ht, key.h, value);
540527
}
541-
542-
if (refcount) {
543-
spl_array_set_refcount(intern->is_child, ht, refcount);
544-
}
545528
} /* }}} */
546529

547530
static void spl_array_write_dimension(zend_object *object, zval *offset, zval *value) /* {{{ */
@@ -570,8 +553,10 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec
570553
return;
571554
}
572555

556+
if (Z_TYPE(intern->array) == IS_ARRAY) {
557+
SEPARATE_ARRAY(&intern->array);
558+
}
573559
ht = spl_array_get_hash_table(intern);
574-
uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1);
575560

576561
if (key.key) {
577562
zval *data = zend_hash_find(ht, key.key);
@@ -597,10 +582,6 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec
597582
} else {
598583
zend_hash_index_del(ht, key.h);
599584
}
600-
601-
if (refcount) {
602-
spl_array_set_refcount(intern->is_child, ht, refcount);
603-
}
604585
} /* }}} */
605586

606587
static void spl_array_unset_dimension(zend_object *object, zval *offset) /* {{{ */
@@ -970,17 +951,7 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar
970951
if (Z_REFCOUNT_P(array) == 1) {
971952
ZVAL_COPY(&intern->array, array);
972953
} else {
973-
//??? TODO: try to avoid array duplication
974954
ZVAL_ARR(&intern->array, zend_array_dup(Z_ARR_P(array)));
975-
976-
if (intern->is_child) {
977-
Z_TRY_DELREF(intern->bucket->val);
978-
/*
979-
* replace bucket->val with copied array, so the changes between
980-
* parent and child object can affect each other.
981-
*/
982-
ZVAL_COPY(&intern->bucket->val, &intern->array);
983-
}
984955
}
985956
} else {
986957
php_error_docref(NULL, E_DEPRECATED,
@@ -1850,15 +1821,6 @@ PHP_METHOD(RecursiveArrayIterator, hasChildren)
18501821
static void spl_instantiate_child_arg(zend_class_entry *pce, zval *retval, zval *arg1, zval *arg2) /* {{{ */
18511822
{
18521823
object_init_ex(retval, pce);
1853-
spl_array_object *new_intern = Z_SPLARRAY_P(retval);
1854-
/*
1855-
* set new_intern->is_child is true to indicate that the object was created by
1856-
* RecursiveArrayIterator::getChildren() method.
1857-
*/
1858-
new_intern->is_child = true;
1859-
1860-
/* find the bucket of parent object. */
1861-
new_intern->bucket = (Bucket *)((char *)(arg1) - XtOffsetOf(Bucket, val));;
18621824
zend_call_known_instance_method_with_2_params(pce->constructor, Z_OBJ_P(retval), NULL, arg1, arg2);
18631825
}
18641826
/* }}} */

ext/spl/tests/ArrayObject/gh10519.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ $example->bugySetMethod(5, 'in here');
5151
var_dump(json_encode($example));
5252
var_dump(json_encode($a));
5353

54+
// child iterator modifications do not propagate to the parent iterator when working with arrays
5455
$b = [
5556
'test' => [
5657
'b' => [2 => '',3 => '',4 => ''],
@@ -66,5 +67,5 @@ var_dump(json_encode($b));
6667
Deprecated: ArrayIterator::__construct(): Using an object as a backing array for ArrayIterator is deprecated, as it allows violating class constraints and invariants in %s on line %d
6768
string(51) "{"test":{"a":{"2":"","3":"","4":"","5":"in here"}}}"
6869
string(51) "{"test":{"a":{"2":"","3":"","4":"","5":"in here"}}}"
69-
string(56) "{"test":{"b":{"2":"","3":"","4":"","5":"must be here"}}}"
70+
string(37) "{"test":{"b":{"2":"","3":"","4":""}}}"
7071
string(37) "{"test":{"b":{"2":"","3":"","4":""}}}"

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(5) "alter"
113+
string(4) "val2"
114114
[3]=>
115115
array(1) {
116116
[3]=>
117-
string(5) "alter"
117+
string(4) "val3"
118118
}
119119
}
120120
[4]=>
@@ -129,11 +129,11 @@ object(RecursiveArrayIterator)#%d (1) {
129129
[2]=>
130130
array(2) {
131131
[2]=>
132-
string(5) "alter"
132+
string(4) "val2"
133133
[3]=>
134134
array(1) {
135135
[3]=>
136-
string(5) "alter"
136+
string(4) "val3"
137137
}
138138
}
139139
[4]=>
@@ -146,11 +146,11 @@ array(3) {
146146
[2]=>
147147
array(2) {
148148
[2]=>
149-
string(5) "alter"
149+
string(4) "val2"
150150
[3]=>
151151
array(1) {
152152
[3]=>
153-
string(5) "alter"
153+
string(4) "val3"
154154
}
155155
}
156156
[4]=>

ext/spl/tests/bug42654_2.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ array(3) {
2626
[0]=>
2727
array(1) {
2828
["key2"]=>
29-
string(5) "alter"
29+
string(4) "val2"
3030
}
3131
["key3"]=>
3232
string(4) "val3"

ext/spl/tests/gh17935.phpt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
GH-17935 (ArrayObject __serialize() should not cause assertion failure on modification)
3+
--FILE--
4+
<?php
5+
$o = new ArrayObject();
6+
$s2 = $o->__serialize();
7+
$o['a'] = 'b';
8+
echo "Success\n";
9+
?>
10+
--EXPECT--
11+
Success

0 commit comments

Comments
 (0)