@@ -44,6 +44,8 @@ typedef struct _spl_fixedarray {
44
44
zend_long size ;
45
45
/* It is possible to resize this, so this can't be combined with the object */
46
46
zval * elements ;
47
+ /* True if this was modified after the last call to get_properties or the hash table wasn't rebuilt. */
48
+ bool should_rebuild_properties ;
47
49
} spl_fixedarray ;
48
50
49
51
typedef struct _spl_fixedarray_methods {
@@ -110,6 +112,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
110
112
array -> size = 0 ; /* reset size in case ecalloc() fails */
111
113
array -> elements = safe_emalloc (size , sizeof (zval ), 0 );
112
114
array -> size = size ;
115
+ array -> should_rebuild_properties = true;
113
116
spl_fixedarray_init_elems (array , 0 , size );
114
117
} else {
115
118
spl_fixedarray_default_ctor (array );
@@ -173,6 +176,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
173
176
/* nothing to do */
174
177
return ;
175
178
}
179
+ array -> should_rebuild_properties = true;
176
180
177
181
/* first initialization */
178
182
if (array -> size == 0 ) {
@@ -212,6 +216,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
212
216
HashTable * ht = zend_std_get_properties (obj );
213
217
214
218
if (!spl_fixedarray_empty (& intern -> array )) {
219
+ /*
220
+ * Usually, the reference count of the hash table is 1,
221
+ * except during cyclic reference cycles.
222
+ *
223
+ * Maintain the DEBUG invariant that a hash table isn't modified during iteration,
224
+ * and avoid unnecessary work rebuilding a hash table for unmodified properties.
225
+ *
226
+ * See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt
227
+ * Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches.
228
+ */
229
+ if (!intern -> array .should_rebuild_properties ) {
230
+ /* Return the same hash table so that recursion cycle detection works in internal functions. */
231
+ return ht ;
232
+ }
233
+ intern -> array .should_rebuild_properties = false;
234
+
215
235
zend_long j = zend_hash_num_elements (ht );
216
236
217
237
if (GC_REFCOUNT (ht ) > 1 ) {
@@ -398,6 +418,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off
398
418
}
399
419
return & EG (uninitialized_zval );
400
420
}
421
+ if (type != BP_VAR_IS && type != BP_VAR_R ) {
422
+ intern -> array .should_rebuild_properties = true;
423
+ }
401
424
402
425
return spl_fixedarray_object_read_dimension_helper (intern , offset );
403
426
}
@@ -422,6 +445,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *
422
445
zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
423
446
return ;
424
447
} else {
448
+ intern -> array .should_rebuild_properties = true;
425
449
/* Fix #81429 */
426
450
zval * ptr = & (intern -> array .elements [index ]);
427
451
zval tmp ;
@@ -464,6 +488,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *
464
488
zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
465
489
return ;
466
490
} else {
491
+ intern -> array .should_rebuild_properties = true;
467
492
zval_ptr_dtor (& (intern -> array .elements [index ]));
468
493
ZVAL_NULL (& intern -> array .elements [index ]);
469
494
}
0 commit comments