diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index e653d1173b24..1d6866b24253 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -633,6 +633,9 @@ PHP_METHOD(SplObjectStorage, removeAllExcept) spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS); spl_SplObjectStorage *other; spl_SplObjectStorageElement *element; + zend_object **to_remove = NULL; + uint32_t to_remove_count = 0; + uint32_t to_remove_capacity = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) { RETURN_THROWS(); @@ -640,15 +643,29 @@ PHP_METHOD(SplObjectStorage, removeAllExcept) other = Z_SPLOBJSTORAGE_P(obj); + /* Avoid mutating this storage while other->getHash() may re-enter it. */ SPL_SAFE_HASH_FOREACH_PTR(&intern->storage, element) { zend_object *elem_obj = element->obj; GC_ADDREF(elem_obj); if (!spl_object_storage_contains(other, elem_obj)) { - spl_object_storage_detach(intern, elem_obj); + if (to_remove_count == to_remove_capacity) { + to_remove_capacity = to_remove_capacity ? to_remove_capacity * 2 : 8; + to_remove = safe_erealloc(to_remove, to_remove_capacity, sizeof(zend_object *), 0); + } + to_remove[to_remove_count++] = elem_obj; + continue; } OBJ_RELEASE(elem_obj); } ZEND_HASH_FOREACH_END(); + for (uint32_t i = 0; i < to_remove_count; i++) { + spl_object_storage_detach(intern, to_remove[i]); + OBJ_RELEASE(to_remove[i]); + } + if (to_remove) { + efree(to_remove); + } + zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos); intern->index = 0; diff --git a/ext/spl/tests/SplObjectStorage/gh21831.phpt b/ext/spl/tests/SplObjectStorage/gh21831.phpt new file mode 100644 index 000000000000..41b42b835e32 --- /dev/null +++ b/ext/spl/tests/SplObjectStorage/gh21831.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-21831: SplObjectStorage::removeAllExcept() tolerates re-entrant deletion in getHash() +--FILE-- +other) { + $this->other->offsetUnset($obj); + $this->other = null; + } + + return 'x'; + } +} + +$storage = new SplObjectStorage(); +$storage[new stdClass()] = null; + +$filter = new FilterStorage(); +$filter->other = $storage; + +var_dump($storage->removeAllExcept($filter)); +var_dump(count($storage)); + +?> +--EXPECT-- +int(0) +int(0)