@@ -199,6 +199,55 @@ static HashTable* spl_fixedarray_object_get_gc(zend_object *obj, zval **table, i
199199 return ht ;
200200}
201201
202+ static zend_bool spl_fixedarray_shallow_identical (zval * v1 , zval * v2 )
203+ {
204+ if (Z_TYPE_P (v1 ) != Z_TYPE_P (v2 )) {
205+ return false;
206+ }
207+ switch (Z_TYPE_P (v1 )) {
208+ case IS_NULL :
209+ case IS_FALSE :
210+ case IS_TRUE :
211+ return true;
212+ case IS_LONG :
213+ return Z_LVAL_P (v1 ) == Z_LVAL_P (v2 );
214+ case IS_DOUBLE : {
215+ double d1 = Z_DVAL_P (v1 );
216+ double d2 = Z_DVAL_P (v2 );
217+ /* Also check for NAN */
218+ return d1 == d2 || (d1 != d1 && d2 != d2 );
219+ }
220+ case IS_STRING :
221+ return Z_STR_P (v1 ) == Z_STR_P (v2 ); /* Same pointer */
222+ case IS_ARRAY :
223+ return Z_ARR_P (v1 ) == Z_ARR_P (v2 ); /* Same pointer */
224+ case IS_OBJECT :
225+ return Z_OBJ_P (v1 ) == Z_OBJ_P (v2 );
226+ case IS_RESOURCE :
227+ return Z_RES_P (v1 ) == Z_RES_P (v2 );
228+ default :
229+ return false;
230+ }
231+ }
232+
233+ static zend_bool spl_fixedarray_object_needs_rebuild (spl_fixedarray_object * intern , HashTable * ht , zend_long j )
234+ {
235+ for (zend_long i = 0 ; i < intern -> array .size ; i ++ ) {
236+ zval * old = zend_hash_index_find (ht , i );
237+ if (old == NULL || !spl_fixedarray_shallow_identical (& intern -> array .elements [i ], old )) {
238+ return true;
239+ }
240+ }
241+ if (j > intern -> array .size ) {
242+ for (zend_long i = intern -> array .size ; i < j ; ++ i ) {
243+ if (zend_hash_index_exists (ht , i )) {
244+ return true;
245+ }
246+ }
247+ }
248+ return false;
249+ }
250+
202251static HashTable * spl_fixedarray_object_get_properties (zend_object * obj )
203252{
204253 spl_fixedarray_object * intern = spl_fixed_array_from_obj (obj );
@@ -208,6 +257,19 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
208257 zend_long j = zend_hash_num_elements (ht );
209258
210259 if (GC_REFCOUNT (ht ) > 1 ) {
260+ /*
261+ * Usually, the reference count of the hash table is 1,
262+ * except during cyclic reference cycles.
263+ *
264+ * Attempt to maintain the DEBUG invariant that a hash table isn't modified during iteration.
265+ *
266+ * See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt
267+ * Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches.
268+ */
269+ if (!spl_fixedarray_object_needs_rebuild (intern , ht , j )) {
270+ /* Return the same hash table so that recursion cycle detection works in internal functions. */
271+ return ht ;
272+ }
211273 intern -> std .properties = zend_array_dup (ht );
212274 if (!(GC_FLAGS (ht ) & GC_IMMUTABLE )) {
213275 GC_DELREF (ht );
0 commit comments