@@ -42,6 +42,8 @@ ZEND_GET_MODULE(spl_fixedarray)
42
42
typedef struct _spl_fixedarray {
43
43
zend_long size ;
44
44
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 ;
45
47
} spl_fixedarray ;
46
48
47
49
typedef struct _spl_fixedarray_object {
@@ -104,6 +106,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
104
106
array -> size = 0 ; /* reset size in case ecalloc() fails */
105
107
array -> elements = safe_emalloc (size , sizeof (zval ), 0 );
106
108
array -> size = size ;
109
+ array -> should_rebuild_properties = true;
107
110
spl_fixedarray_init_elems (array , 0 , size );
108
111
} else {
109
112
spl_fixedarray_default_ctor (array );
@@ -166,6 +169,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
166
169
/* nothing to do */
167
170
return ;
168
171
}
172
+ array -> should_rebuild_properties = true;
169
173
170
174
/* first initialization */
171
175
if (array -> size == 0 ) {
@@ -205,6 +209,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
205
209
HashTable * ht = zend_std_get_properties (obj );
206
210
207
211
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
+
208
228
zend_long j = zend_hash_num_elements (ht );
209
229
210
230
if (GC_REFCOUNT (ht ) > 1 ) {
@@ -354,6 +374,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off
354
374
}
355
375
return & EG (uninitialized_zval );
356
376
}
377
+ if (type != BP_VAR_IS && type != BP_VAR_R ) {
378
+ intern -> array .should_rebuild_properties = true;
379
+ }
357
380
358
381
return spl_fixedarray_object_read_dimension_helper (intern , offset );
359
382
}
@@ -378,6 +401,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *
378
401
zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
379
402
return ;
380
403
} else {
404
+ intern -> array .should_rebuild_properties = true;
381
405
/* Fix #81429 */
382
406
zval * ptr = & (intern -> array .elements [index ]);
383
407
zval tmp ;
@@ -425,6 +449,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *
425
449
zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
426
450
return ;
427
451
} else {
452
+ intern -> array .should_rebuild_properties = true;
428
453
zval_ptr_dtor (& (intern -> array .elements [index ]));
429
454
ZVAL_NULL (& intern -> array .elements [index ]);
430
455
}
0 commit comments