diff --git a/NEWS b/NEWS
index 3ee934bca5839..c7d4d4bed0295 100644
--- a/NEWS
+++ b/NEWS
@@ -948,6 +948,9 @@ PHP NEWS
. Fixed bug #81433 (DOMElement::setIdAttribute() called twice may remove ID).
(Viktor Volkov)
+ . Fixed bug #80602 (Segfault when using DOMChildNode::before()).
+ (Nathan Freeman)
+
- FFI:
. Fixed bug #79576 ("TYPE *" shows unhelpful message when type is not
defined). (Dmitry)
diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c
index 3d7e5e44a4b2c..2352624d0f4d7 100644
--- a/ext/dom/parentnode.c
+++ b/ext/dom/parentnode.c
@@ -181,6 +181,15 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod
return NULL;
}
+ /*
+ * xmlNewDocText function will always returns same address to the second parameter if the parameters are greater than or equal to three.
+ * If it's text, that's fine, but if it's an object, it can cause invalid pointer because many new nodes point to the same memory address.
+ * So we must copy the new node to avoid this situation.
+ */
+ if (nodesc > 1) {
+ newNode = xmlCopyNode(newNode, 1);
+ }
+
if (!xmlAddChild(fragment, newNode)) {
xmlFree(fragment);
@@ -302,7 +311,9 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
{
xmlNode *prevsib = dom_object_get_node(context);
xmlNodePtr newchild, parentNode;
- xmlNode *fragment;
+ xmlNode *fragment, *nextsib;
+ xmlDoc *doc;
+ bool afterlastchild;
int stricterror = dom_get_strict_error(context->document);
@@ -311,7 +322,10 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
return;
}
+ doc = prevsib->doc;
parentNode = prevsib->parent;
+ nextsib = prevsib->next;
+ afterlastchild = (nextsib == NULL);
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
if (fragment == NULL) {
@@ -321,13 +335,42 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
- fragment->last->next = prevsib->next;
- prevsib->next = newchild;
+ // first node and last node are both both parameters to DOMElement::after() method so nextsib and prevsib are null.
+ if (!parentNode->children) {
+ prevsib = nextsib = NULL;
+ } else if (afterlastchild) {
+ /*
+ * The new node will be inserted after last node, prevsib is last node.
+ * The first node is the parameter to DOMElement::after() if parentNode->children == prevsib is true
+ * and prevsib does not change, otherwise prevsib is parentNode->last(first mode).
+ */
+ prevsib = parentNode->children == prevsib ? prevsib : parentNode->last;
+ } else {
+ /*
+ * The new node will be inserted after first node, prevsib is first node.
+ * The first node is not the parameter to DOMElement::after() if parentNode->children == prevsib is true
+ * and prevsib does not change otherwise prevsib is null to mean that parentNode->children is the new node.
+ */
+ prevsib = parentNode->children == prevsib ? prevsib : NULL;
+ }
- newchild->prev = prevsib;
+ if (prevsib) {
+ fragment->last->next = prevsib->next;
+ if (prevsib->next) {
+ prevsib->next->prev = fragment->last;
+ }
+ prevsib->next = newchild;
+ } else {
+ parentNode->children = newchild;
+ if (nextsib) {
+ fragment->last->next = nextsib;
+ nextsib->prev = fragment->last;
+ }
+ }
+ newchild->prev = prevsib;
dom_fragment_assign_parent_node(parentNode, fragment);
- dom_reconcile_ns(prevsib->doc, newchild);
+ dom_reconcile_ns(doc, newchild);
}
xmlFree(fragment);
@@ -337,10 +380,15 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
{
xmlNode *nextsib = dom_object_get_node(context);
xmlNodePtr newchild, prevsib, parentNode;
- xmlNode *fragment;
+ xmlNode *fragment, *afternextsib;
+ xmlDoc *doc;
+ bool beforefirstchild;
+ doc = nextsib->doc;
prevsib = nextsib->prev;
+ afternextsib = nextsib->next;
parentNode = nextsib->parent;
+ beforefirstchild = !prevsib;
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
if (fragment == NULL) {
@@ -350,19 +398,40 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
+ // first node and last node are both both parameters to DOMElement::before() method so nextsib is null.
+ if (!parentNode->children) {
+ nextsib = NULL;
+ } else if (beforefirstchild) {
+ /*
+ * The new node will be inserted before first node, nextsib is first node and afternextsib is last node.
+ * The first node is not the parameter to DOMElement::before() if parentNode->children == nextsib is true
+ * and nextsib does not change, otherwise nextsib is the last node.
+ */
+ nextsib = parentNode->children == nextsib ? nextsib : afternextsib;
+ } else {
+ /*
+ * The new node will be inserted before last node, prevsib is first node and nestsib is last node.
+ * The first node is not the parameter to DOMElement::before() if parentNode->children == prevsib is true
+ * but last node may be, so use prevsib->next to determine the value of nextsib, otherwise nextsib does not change.
+ */
+ nextsib = parentNode->children == prevsib ? prevsib->next : nextsib;
+ }
+
if (parentNode->children == nextsib) {
parentNode->children = newchild;
} else {
prevsib->next = newchild;
}
+
fragment->last->next = nextsib;
- nextsib->prev = fragment->last;
+ if (nextsib) {
+ nextsib->prev = fragment->last;
+ }
newchild->prev = prevsib;
dom_fragment_assign_parent_node(parentNode, fragment);
-
- dom_reconcile_ns(nextsib->doc, newchild);
+ dom_reconcile_ns(doc, newchild);
}
xmlFree(fragment);
diff --git a/ext/dom/tests/bug80602.phpt b/ext/dom/tests/bug80602.phpt
new file mode 100644
index 0000000000000..9f041f686f516
--- /dev/null
+++ b/ext/dom/tests/bug80602.phpt
@@ -0,0 +1,178 @@
+--TEST--
+Bug #80602 (Segfault when using DOMChildNode::before())
+--FILE--
+loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($doc->documentElement->lastChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($target, $doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before('bar', $target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, 'bar', $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->before($target, $doc->documentElement->lastChild, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before('bar', $doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild, 'bar', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->before($doc->documentElement->firstChild, $target, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+?>
+--EXPECTF--
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+barbazfoo
+foobarbaz
+foobarbaz
+barfoobaz
+barbazfoo
+foobarbaz
+foobarbaz
+foobarbaz
+barfoo
+foobar
+foobar
+barfoo
+foobar
+foobar
diff --git a/ext/dom/tests/bug80602_2.phpt b/ext/dom/tests/bug80602_2.phpt
new file mode 100644
index 0000000000000..1151417c0f845
--- /dev/null
+++ b/ext/dom/tests/bug80602_2.phpt
@@ -0,0 +1,178 @@
+--TEST--
+Bug #80602 (Segfault when using DOMChildNode::after())
+--FILE--
+loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($doc->documentElement->lastChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($target, $doc->documentElement->firstChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($target, 'bar','baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar', $target, 'baz');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar', 'baz', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after('bar', $target, $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, 'bar', $doc->documentElement->lastChild);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->firstChild;
+$target->after($target, $doc->documentElement->lastChild, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after('bar', $doc->documentElement->firstChild, $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild, 'bar', $target);
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+
+$doc = new \DOMDocument();
+$doc->loadXML('foo');
+$target = $doc->documentElement->lastChild;
+$target->after($doc->documentElement->firstChild, $target, 'bar');
+echo $doc->saveXML($doc->documentElement).PHP_EOL;
+
+?>
+--EXPECTF--
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foo
+foobarbaz
+foobarbaz
+foobarbaz
+barfoobaz
+barbazfoo
+foobarbaz
+foobarbaz
+foobarbaz
+barfoo
+foobar
+foobar
+barfoo
+foobar
+foobar