diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index ec53935ac9d2e..cdf628cfb4ee6 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -42,6 +42,8 @@ ZEND_GET_MODULE(spl_fixedarray) typedef struct _spl_fixedarray { zend_long size; zval *elements; + /* True if this was modified after the last call to get_properties or the hash table wasn't rebuilt. */ + bool should_rebuild_properties; } spl_fixedarray; typedef struct _spl_fixedarray_object { @@ -104,6 +106,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size) array->size = 0; /* reset size in case ecalloc() fails */ array->elements = safe_emalloc(size, sizeof(zval), 0); array->size = size; + array->should_rebuild_properties = true; spl_fixedarray_init_elems(array, 0, size); } else { spl_fixedarray_default_ctor(array); @@ -166,6 +169,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size) /* nothing to do */ return; } + array->should_rebuild_properties = true; /* first initialization */ if (array->size == 0) { @@ -205,6 +209,22 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj) HashTable *ht = zend_std_get_properties(obj); if (!spl_fixedarray_empty(&intern->array)) { + /* + * Usually, the reference count of the hash table is 1, + * except during cyclic reference cycles. + * + * Maintain the DEBUG invariant that a hash table isn't modified during iteration, + * and avoid unnecessary work rebuilding a hash table for unmodified properties. + * + * See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt + * Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches. + */ + if (!intern->array.should_rebuild_properties) { + /* Return the same hash table so that recursion cycle detection works in internal functions. */ + return ht; + } + intern->array.should_rebuild_properties = false; + zend_long j = zend_hash_num_elements(ht); if (GC_REFCOUNT(ht) > 1) { @@ -354,6 +374,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off } return &EG(uninitialized_zval); } + if (type != BP_VAR_IS && type != BP_VAR_R) { + intern->array.should_rebuild_properties = true; + } return spl_fixedarray_object_read_dimension_helper(intern, offset); } @@ -378,6 +401,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object * zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0); return; } else { + intern->array.should_rebuild_properties = true; /* Fix #81429 */ zval *ptr = &(intern->array.elements[index]); zval tmp; @@ -425,6 +449,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object * zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0); return; } else { + intern->array.should_rebuild_properties = true; zval_ptr_dtor(&(intern->array.elements[index])); ZVAL_NULL(&intern->array.elements[index]); } diff --git a/ext/spl/tests/fixedarray_023.phpt b/ext/spl/tests/fixedarray_023.phpt new file mode 100644 index 0000000000000..1d60a2ce6d9f7 --- /dev/null +++ b/ext/spl/tests/fixedarray_023.phpt @@ -0,0 +1,36 @@ +--TEST-- +SPL: FixedArray: Infinite loop in var_export bugfix +--FILE-- + +--EXPECTF-- +Warning: var_export does not handle circular references in %s on line 8 + +Warning: var_export does not handle circular references in %s on line 8 +SplFixedArray::__set_state(array( + 0 => NAN, + 1 => 0.0, + 2 => NULL, + 3 => NULL, +)) +object(SplFixedArray)#2 (4) refcount(6){ + [0]=> + float(NAN) + [1]=> + float(-0) + [2]=> + *RECURSION* + [3]=> + *RECURSION* +} \ No newline at end of file