@@ -48,6 +48,8 @@ typedef struct _spl_fixedarray {
4848 zend_long size ;
4949 /* It is possible to resize this, so this can't be combined with the object */
5050 zval * elements ;
51+ /* True if this was modified after the last call to get_properties or the hash table wasn't rebuilt. */
52+ bool should_rebuild_properties ;
5153} spl_fixedarray ;
5254
5355typedef struct _spl_fixedarray_object {
@@ -106,6 +108,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
106108 array -> size = 0 ; /* reset size in case ecalloc() fails */
107109 array -> elements = safe_emalloc (size , sizeof (zval ), 0 );
108110 array -> size = size ;
111+ array -> should_rebuild_properties = true;
109112 spl_fixedarray_init_elems (array , 0 , size );
110113 } else {
111114 spl_fixedarray_default_ctor (array );
@@ -169,6 +172,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
169172 /* nothing to do */
170173 return ;
171174 }
175+ array -> should_rebuild_properties = true;
172176
173177 /* first initialization */
174178 if (array -> size == 0 ) {
@@ -208,6 +212,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
208212 HashTable * ht = zend_std_get_properties (obj );
209213
210214 if (!spl_fixedarray_empty (& intern -> array )) {
215+ /*
216+ * Usually, the reference count of the hash table is 1,
217+ * except during cyclic reference cycles.
218+ *
219+ * Maintain the DEBUG invariant that a hash table isn't modified during iteration,
220+ * and avoid unnecessary work rebuilding a hash table for unmodified properties.
221+ *
222+ * See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt
223+ * Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches.
224+ */
225+ if (!intern -> array .should_rebuild_properties ) {
226+ /* Return the same hash table so that recursion cycle detection works in internal functions. */
227+ return ht ;
228+ }
229+ intern -> array .should_rebuild_properties = false;
230+
211231 zend_long j = zend_hash_num_elements (ht );
212232
213233 if (GC_REFCOUNT (ht ) > 1 ) {
@@ -370,6 +390,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off
370390 }
371391
372392 spl_fixedarray_object * intern = spl_fixed_array_from_obj (object );
393+ if (type != BP_VAR_IS && type != BP_VAR_R ) {
394+ intern -> array .should_rebuild_properties = true;
395+ }
373396 return spl_fixedarray_object_read_dimension_helper (intern , offset );
374397}
375398
@@ -393,6 +416,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *
393416 zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
394417 return ;
395418 } else {
419+ intern -> array .should_rebuild_properties = true;
396420 /* Fix #81429 */
397421 zval * ptr = & (intern -> array .elements [index ]);
398422 zval tmp ;
@@ -433,6 +457,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *
433457 zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
434458 return ;
435459 } else {
460+ intern -> array .should_rebuild_properties = true;
436461 zval_ptr_dtor (& (intern -> array .elements [index ]));
437462 ZVAL_NULL (& intern -> array .elements [index ]);
438463 }
0 commit comments