@@ -44,6 +44,8 @@ typedef struct _spl_fixedarray {
4444 zend_long size ;
4545 /* It is possible to resize this, so this can't be combined with the object */
4646 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 ;
4749} spl_fixedarray ;
4850
4951typedef struct _spl_fixedarray_methods {
@@ -110,6 +112,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
110112 array -> size = 0 ; /* reset size in case ecalloc() fails */
111113 array -> elements = safe_emalloc (size , sizeof (zval ), 0 );
112114 array -> size = size ;
115+ array -> should_rebuild_properties = true;
113116 spl_fixedarray_init_elems (array , 0 , size );
114117 } else {
115118 spl_fixedarray_default_ctor (array );
@@ -173,6 +176,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
173176 /* nothing to do */
174177 return ;
175178 }
179+ array -> should_rebuild_properties = true;
176180
177181 /* first initialization */
178182 if (array -> size == 0 ) {
@@ -212,6 +216,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
212216 HashTable * ht = zend_std_get_properties (obj );
213217
214218 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+
215235 zend_long j = zend_hash_num_elements (ht );
216236
217237 if (GC_REFCOUNT (ht ) > 1 ) {
@@ -398,6 +418,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off
398418 }
399419 return & EG (uninitialized_zval );
400420 }
421+ if (type != BP_VAR_IS && type != BP_VAR_R ) {
422+ intern -> array .should_rebuild_properties = true;
423+ }
401424
402425 return spl_fixedarray_object_read_dimension_helper (intern , offset );
403426}
@@ -422,6 +445,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *
422445 zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
423446 return ;
424447 } else {
448+ intern -> array .should_rebuild_properties = true;
425449 /* Fix #81429 */
426450 zval * ptr = & (intern -> array .elements [index ]);
427451 zval tmp ;
@@ -464,6 +488,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *
464488 zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
465489 return ;
466490 } else {
491+ intern -> array .should_rebuild_properties = true;
467492 zval_ptr_dtor (& (intern -> array .elements [index ]));
468493 ZVAL_NULL (& intern -> array .elements [index ]);
469494 }
0 commit comments