From 494312e5d5892b134443b4af8658d663845b8b11 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 19 Aug 2026 13:36:12 +0100 Subject: [PATCH] ext/dom: DOMNode::insertBefore() dropping the node used as its own reference. Fix #23365 insertBefore($n, $n) unlinked the node, then rebuilt its position from the pointers the unlink had just cleared, leaving it out of the document with a self-referencing sibling list, freed twice at teardown. Retarget the reference to the node's next sibling, as the modern DOM already does. --- ext/dom/node.c | 5 +++- ext/dom/tests/gh23365.phpt | 53 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 ext/dom/tests/gh23365.phpt diff --git a/ext/dom/node.c b/ext/dom/node.c index a42dfedc32a5..49e400ef5302 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -931,6 +931,9 @@ static void dom_node_insert_before_legacy(zval *return_value, zval *ref, dom_obj php_dom_throw_error(NOT_FOUND_ERR, stricterror); RETURN_FALSE; } + if (refp == child) { + refp = child->next; + } } if (child->doc == NULL && parentp->doc != NULL) { @@ -940,7 +943,7 @@ static void dom_node_insert_before_legacy(zval *return_value, zval *ref, dom_obj php_libxml_invalidate_node_list_cache(intern->document); - if (ref != NULL) { + if (refp != NULL) { if (child->parent != NULL) { xmlUnlinkNode(child); } diff --git a/ext/dom/tests/gh23365.phpt b/ext/dom/tests/gh23365.phpt new file mode 100644 index 000000000000..bc283b0659d3 --- /dev/null +++ b/ext/dom/tests/gh23365.phpt @@ -0,0 +1,53 @@ +--TEST-- +GH-23365 (DOMNode::insertBefore($n, $n) drops the node and leaves a self-referencing sibling list) +--CREDITS-- +Alexandre Daubois +--EXTENSIONS-- +dom +--FILE-- +loadXML('text'); +$root = $doc->documentElement; + +$text = $root->firstChild; +var_dump($root->insertBefore($text, $text) === $text); +var_dump($root->childNodes->length); +var_dump($text->parentNode === $root, $text->nextSibling === $text, $text->previousSibling === $text); + +$child = $root->lastChild; +var_dump($root->insertBefore($child, $child) === $child); +var_dump($root->childNodes->length); + +echo $doc->saveXML($root), PHP_EOL; + +$doc2 = new DOMDocument(); +$doc2->loadXML(''); +$el = $doc2->documentElement; +$attr = $el->getAttributeNode('a'); +var_dump($el->insertBefore($attr, $attr) === $attr); +echo $doc2->saveXML($el), PHP_EOL; + +$doc3 = new DOMDocument(); +$root3 = $doc3->appendChild($doc3->createElement('root')); +$root3->appendChild($doc3->createTextNode('A')); +$t = $root3->appendChild($doc3->createTextNode('B')); +$root3->insertBefore($t, $t); +$root3->appendChild($t); +echo $doc3->saveXML($root3), PHP_EOL; +unset($t, $root3, $doc3); +echo "done", PHP_EOL; +?> +--EXPECT-- +bool(true) +int(2) +bool(true) +bool(false) +bool(false) +bool(true) +int(2) +text +bool(true) + +AB +done