Skip to content

Commit 9c3d110

Browse files
committed
Fix GH-21831: Defer removals during SplObjectStorage::removeAllExcept()
1 parent d43c523 commit 9c3d110

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

ext/spl/spl_observer.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,22 +633,37 @@ PHP_METHOD(SplObjectStorage, removeAllExcept)
633633
spl_SplObjectStorage *intern = Z_SPLOBJSTORAGE_P(ZEND_THIS);
634634
spl_SplObjectStorage *other;
635635
spl_SplObjectStorageElement *element;
636+
zend_object **to_remove = NULL;
637+
uint32_t to_remove_count = 0;
638+
uint32_t to_remove_capacity = 0;
636639

637640
if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, spl_ce_SplObjectStorage) == FAILURE) {
638641
RETURN_THROWS();
639642
}
640643

641644
other = Z_SPLOBJSTORAGE_P(obj);
642645

646+
/* Avoid mutating this storage while other->getHash() may re-enter it. */
643647
SPL_SAFE_HASH_FOREACH_PTR(&intern->storage, element) {
644648
zend_object *elem_obj = element->obj;
645649
GC_ADDREF(elem_obj);
646650
if (!spl_object_storage_contains(other, elem_obj)) {
647-
spl_object_storage_detach(intern, elem_obj);
651+
if (to_remove_count == to_remove_capacity) {
652+
to_remove_capacity = to_remove_capacity ? to_remove_capacity * 2 : 8;
653+
to_remove = safe_erealloc(to_remove, to_remove_capacity, sizeof(zend_object *), 0);
654+
}
655+
to_remove[to_remove_count++] = elem_obj;
656+
continue;
648657
}
649658
OBJ_RELEASE(elem_obj);
650659
} ZEND_HASH_FOREACH_END();
651660

661+
for (uint32_t i = 0; i < to_remove_count; i++) {
662+
spl_object_storage_detach(intern, to_remove[i]);
663+
OBJ_RELEASE(to_remove[i]);
664+
}
665+
efree(to_remove);
666+
652667
zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos);
653668
intern->index = 0;
654669

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
GH-21831: SplObjectStorage::removeAllExcept() tolerates re-entrant deletion in getHash()
3+
--FILE--
4+
<?php
5+
6+
class FilterStorage extends SplObjectStorage {
7+
public ?SplObjectStorage $other = null;
8+
9+
public function getHash($obj): string {
10+
if ($this->other) {
11+
$this->other->offsetUnset($obj);
12+
$this->other = null;
13+
}
14+
15+
return 'x';
16+
}
17+
}
18+
19+
$storage = new SplObjectStorage();
20+
$storage[new stdClass()] = null;
21+
22+
$filter = new FilterStorage();
23+
$filter->other = $storage;
24+
25+
var_dump($storage->removeAllExcept($filter));
26+
var_dump(count($storage));
27+
28+
?>
29+
--EXPECT--
30+
int(0)
31+
int(0)

0 commit comments

Comments
 (0)