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