Skip to content

Commit

Permalink
Fix phpGH-7958: Nested CallbackFilterIterator is leaking memory
Browse files Browse the repository at this point in the history
We implement `zend_object_iterator_funcs.get_gc` for user iterators to
avoid the memory leak.
  • Loading branch information
cmb69 committed Feb 17, 2022
1 parent 40e0bdb commit 72498a4
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 1 deletion.
43 changes: 43 additions & 0 deletions Zend/tests/gh7958.phpt
@@ -0,0 +1,43 @@
--TEST--
GH-7958 (Nested CallbackFilterIterator is leaking memory)
--FILE--
<?php
class Action
{
public \Iterator $iterator;

public function __construct(array $data)
{
$this->iterator = new ArrayIterator($data);
echo '-- c ' . spl_object_id($this) . "\n";
}

public function __destruct()
{
echo '-- d ' . spl_object_id($this) . "\n";
}

public function filter()
{
$this->iterator = new \CallbackFilterIterator($this->iterator, fn() => true);
$this->iterator->rewind();
}
}

$action = new Action(['a', 'b']);
$action->filter();
$action->filter();
print_r(iterator_to_array($action->iterator));
$action = null;
gc_collect_cycles();
echo "==DONE==\n";
?>
--EXPECT--
-- c 1
Array
(
[0] => a
[1] => b
)
-- d 1
==DONE==
13 changes: 12 additions & 1 deletion Zend/zend_interfaces.c
Expand Up @@ -182,6 +182,17 @@ ZEND_API void zend_user_it_rewind(zend_object_iterator *_iter)
}
/* }}} */

ZEND_API HashTable *zend_user_it_get_gc(zend_object_iterator *_iter, zval **table, int *n)
{
zend_user_iterator *iter = (zend_user_iterator*)_iter;
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
if (!Z_ISUNDEF(iter->it.data)) {
zend_get_gc_buffer_add_zval(gc_buffer, &iter->it.data);
}
zend_get_gc_buffer_use(gc_buffer, table, n);
return NULL;
}

static const zend_object_iterator_funcs zend_interface_iterator_funcs_iterator = {
zend_user_it_dtor,
zend_user_it_valid,
Expand All @@ -190,7 +201,7 @@ static const zend_object_iterator_funcs zend_interface_iterator_funcs_iterator =
zend_user_it_move_forward,
zend_user_it_rewind,
zend_user_it_invalidate_current,
NULL, /* get_gc */
zend_user_it_get_gc,
};

/* {{{ zend_user_it_get_iterator */
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_interfaces.h
Expand Up @@ -55,6 +55,7 @@ ZEND_API void zend_user_it_get_current_key(zend_object_iterator *_iter, zval *ke
ZEND_API zval *zend_user_it_get_current_data(zend_object_iterator *_iter);
ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter);
ZEND_API void zend_user_it_invalidate_current(zend_object_iterator *_iter);
ZEND_API HashTable *zend_user_it_get_gc(zend_object_iterator *_iter, zval **table, int *n);

ZEND_API void zend_user_it_new_iterator(zend_class_entry *ce, zval *object, zval *iterator);
ZEND_API zend_object_iterator *zend_user_it_get_new_iterator(zend_class_entry *ce, zval *object, int by_ref);
Expand Down

0 comments on commit 72498a4

Please sign in to comment.