@@ -42,6 +42,8 @@ ZEND_GET_MODULE(spl_fixedarray)
4242typedef struct _spl_fixedarray {
4343 zend_long size ;
4444 zval * elements ;
45+ /* True if this was modified after the last call to get_properties or the hash table wasn't rebuilt. */
46+ bool should_rebuild_properties ;
4547} spl_fixedarray ;
4648
4749typedef struct _spl_fixedarray_object {
@@ -104,6 +106,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
104106 array -> size = 0 ; /* reset size in case ecalloc() fails */
105107 array -> elements = safe_emalloc (size , sizeof (zval ), 0 );
106108 array -> size = size ;
109+ array -> should_rebuild_properties = true;
107110 spl_fixedarray_init_elems (array , 0 , size );
108111 } else {
109112 spl_fixedarray_default_ctor (array );
@@ -166,6 +169,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
166169 /* nothing to do */
167170 return ;
168171 }
172+ array -> should_rebuild_properties = true;
169173
170174 /* first initialization */
171175 if (array -> size == 0 ) {
@@ -205,6 +209,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
205209 HashTable * ht = zend_std_get_properties (obj );
206210
207211 if (!spl_fixedarray_empty (& intern -> array )) {
212+ /*
213+ * Usually, the reference count of the hash table is 1,
214+ * except during cyclic reference cycles.
215+ *
216+ * Maintain the DEBUG invariant that a hash table isn't modified during iteration,
217+ * and avoid unnecessary work rebuilding a hash table for unmodified properties.
218+ *
219+ * See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt
220+ * Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches.
221+ */
222+ if (!intern -> array .should_rebuild_properties ) {
223+ /* Return the same hash table so that recursion cycle detection works in internal functions. */
224+ return ht ;
225+ }
226+ intern -> array .should_rebuild_properties = false;
227+
208228 zend_long j = zend_hash_num_elements (ht );
209229
210230 if (GC_REFCOUNT (ht ) > 1 ) {
@@ -354,6 +374,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off
354374 }
355375 return & EG (uninitialized_zval );
356376 }
377+ if (type != BP_VAR_IS && type != BP_VAR_R ) {
378+ intern -> array .should_rebuild_properties = true;
379+ }
357380
358381 return spl_fixedarray_object_read_dimension_helper (intern , offset );
359382}
@@ -378,6 +401,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *
378401 zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
379402 return ;
380403 } else {
404+ intern -> array .should_rebuild_properties = true;
381405 /* Fix #81429 */
382406 zval * ptr = & (intern -> array .elements [index ]);
383407 zval tmp ;
@@ -425,6 +449,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *
425449 zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
426450 return ;
427451 } else {
452+ intern -> array .should_rebuild_properties = true;
428453 zval_ptr_dtor (& (intern -> array .elements [index ]));
429454 ZVAL_NULL (& intern -> array .elements [index ]);
430455 }
0 commit comments