From 8388d3e7f08bfe356bc37c96bcbe6445eb19b704 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 13 Oct 2026 08:33:27 -0400 Subject: [PATCH] ext/simplexml: Report temporary and iterator values to the GC sxe_get_gc() reported only the properties table, so a cycle closed through sxe->tmp (the value behind a reference to a missing property) or through sxe->iter.data (the current iteration element) was never collected, nor was one through a foreach iterator, whose get_gc slot was NULL. Report all three. The other php_sxe_object fields hold strings or libxml state, and declared properties of subclasses are not writable from userland. --- NEWS | 4 ++ ext/simplexml/simplexml.c | 18 +++++++-- ext/simplexml/tests/sxe_gc.phpt | 67 +++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 ext/simplexml/tests/sxe_gc.phpt diff --git a/NEWS b/NEWS index 4b34324de64b..a4cbe0255842 100644 --- a/NEWS +++ b/NEWS @@ -56,6 +56,10 @@ PHP NEWS . Fixed a crash in readline_info() when completion_append_character contains a non-ASCII byte. (Weilin Du) +- SimpleXML: + . Fixed memory leak of cycles through SimpleXMLElement temporary property + references and iterators. (Ilia Alshanetsky) + - SOAP: . Fixed use of uninitialized func in do_request() on OOM bailout. (David Carlier) diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index 9e5f293fe787..a8edf8c60a30 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -51,6 +51,7 @@ static zval *php_sxe_iterator_current_data(zend_object_iterator *iter); static void php_sxe_iterator_current_key(zend_object_iterator *iter, zval *key); static void php_sxe_iterator_move_forward(zend_object_iterator *iter); static void php_sxe_iterator_rewind(zend_object_iterator *iter); +static HashTable *php_sxe_iterator_get_gc(zend_object_iterator *iter, zval **table, int *n); static zend_result sxe_object_cast_ex(zend_object *readobj, zval *writeobj, int type); static void sxe_unlink_node(xmlNodePtr node) @@ -1195,10 +1196,14 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */ static HashTable *sxe_get_gc(zend_object *object, zval **table, int *n) /* {{{ */ { php_sxe_object *sxe; + zend_get_gc_buffer *gc_buffer; + sxe = php_sxe_fetch_object(object); + gc_buffer = zend_get_gc_buffer_create(); + zend_get_gc_buffer_add_zval(gc_buffer, &sxe->iter.data); + zend_get_gc_buffer_add_zval(gc_buffer, &sxe->tmp); + zend_get_gc_buffer_use(gc_buffer, table, n); - *table = NULL; - *n = 0; return sxe->properties; } /* }}} */ @@ -2372,7 +2377,7 @@ static const zend_object_iterator_funcs php_sxe_iterator_funcs = { /* {{{ */ php_sxe_iterator_move_forward, php_sxe_iterator_rewind, NULL, - NULL, /* get_gc */ + php_sxe_iterator_get_gc, }; /* }}} */ @@ -2579,6 +2584,13 @@ static void php_sxe_iterator_rewind(zend_object_iterator *iter) /* {{{ */ } /* }}} */ +static HashTable *php_sxe_iterator_get_gc(zend_object_iterator *iter, zval **table, int *n) +{ + *table = &iter->data; + *n = 1; + return NULL; +} + void *simplexml_export_node(zval *object) /* {{{ */ { php_sxe_object *sxe; diff --git a/ext/simplexml/tests/sxe_gc.phpt b/ext/simplexml/tests/sxe_gc.phpt new file mode 100644 index 000000000000..778804518e23 --- /dev/null +++ b/ext/simplexml/tests/sxe_gc.phpt @@ -0,0 +1,67 @@ +--TEST-- +SimpleXMLElement reports its temporary and iterator values to the cycle collector +--EXTENSIONS-- +simplexml +--FILE-- +get() === null; +} + +$sxe = new SimpleXMLElement(''); +$w = WeakReference::create($sxe); +$r =& $sxe->missing; +$r = $sxe; +unset($r, $sxe); +echo "missing property reference: "; +var_dump(collected($w)); + +$sxe = new SimpleXMLElement(''); +$w = WeakReference::create($sxe); +$sxe->rewind(); +$current = $sxe->current(); +$r =& $current->missing; +$r = $sxe; +unset($r, $current, $sxe); +echo "current element: "; +var_dump(collected($w)); + +$sxe = new SimpleXMLElement(''); +$w = WeakReference::create($sxe); +$sxe->rewind(); +$child = $sxe->current(); +$child->rewind(); +$grandchild = $child->current(); +$r =& $grandchild->missing; +$r = $sxe; +unset($r, $grandchild, $child, $sxe); +echo "nested current element: "; +var_dump(collected($w)); + +function iterate(SimpleXMLElement $sxe): Generator +{ + foreach ($sxe as $element) { + unset($sxe, $element); + yield; + } +} + +$sxe = new SimpleXMLElement(''); +$w = WeakReference::create($sxe); +$r =& $sxe->missing; +$gen = iterate($sxe); +$gen->current(); +$r = $gen; +unset($r, $gen, $sxe); +echo "foreach iterator in generator: "; +var_dump(collected($w)); + +?> +--EXPECT-- +missing property reference: bool(true) +current element: bool(true) +nested current element: bool(true) +foreach iterator in generator: bool(true)