@@ -48,6 +48,8 @@ typedef struct _spl_fixedarray {
48
48
zend_long size ;
49
49
/* It is possible to resize this, so this can't be combined with the object */
50
50
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 ;
51
53
} spl_fixedarray ;
52
54
53
55
typedef struct _spl_fixedarray_object {
@@ -106,6 +108,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size)
106
108
array -> size = 0 ; /* reset size in case ecalloc() fails */
107
109
array -> elements = safe_emalloc (size , sizeof (zval ), 0 );
108
110
array -> size = size ;
111
+ array -> should_rebuild_properties = true;
109
112
spl_fixedarray_init_elems (array , 0 , size );
110
113
} else {
111
114
spl_fixedarray_default_ctor (array );
@@ -169,6 +172,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size)
169
172
/* nothing to do */
170
173
return ;
171
174
}
175
+ array -> should_rebuild_properties = true;
172
176
173
177
/* first initialization */
174
178
if (array -> size == 0 ) {
@@ -208,6 +212,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj)
208
212
HashTable * ht = zend_std_get_properties (obj );
209
213
210
214
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
+
211
231
zend_long j = zend_hash_num_elements (ht );
212
232
213
233
if (GC_REFCOUNT (ht ) > 1 ) {
@@ -370,6 +390,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off
370
390
}
371
391
372
392
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
+ }
373
396
return spl_fixedarray_object_read_dimension_helper (intern , offset );
374
397
}
375
398
@@ -393,6 +416,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *
393
416
zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
394
417
return ;
395
418
} else {
419
+ intern -> array .should_rebuild_properties = true;
396
420
/* Fix #81429 */
397
421
zval * ptr = & (intern -> array .elements [index ]);
398
422
zval tmp ;
@@ -433,6 +457,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *
433
457
zend_throw_exception (spl_ce_RuntimeException , "Index invalid or out of range" , 0 );
434
458
return ;
435
459
} else {
460
+ intern -> array .should_rebuild_properties = true;
436
461
zval_ptr_dtor (& (intern -> array .elements [index ]));
437
462
ZVAL_NULL (& intern -> array .elements [index ]);
438
463
}
0 commit comments