Fix GH-11288 and GH-11289 and GH-11290 and GH-9142: DOMExceptions and segfaults with replaceWith

This replaces the implementation of before and after with one following
the spec very strictly, instead of trying to figure out the state we're
in by looking at the pointers. Also relaxes the condition on text node
copying to prevent working on a stale node pointer.

Closes GH-11299.
This commit is contained in:
nielsdos 2023-05-22 23:50:06 +02:00
parent 8946b7b141
commit cba335d61e
10 changed files with 490 additions and 173 deletions

4
NEWS
View File

@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.1.21
- DOM:
. Fixed bugs GH-11288 and GH-11289 and GH-11290 and GH-9142 (DOMExceptions
and segfaults with replaceWith). (nielsdos)
- Opcache:
. Fix allocation loop in zend_shared_alloc_startup(). (nielsdos)
. Access violation on smm_shared_globals with ALLOC_FALLBACK. (KoudelkaB)

View File

@ -124,6 +124,23 @@ int dom_parent_node_child_element_count(dom_object *obj, zval *retval)
}
/* }}} */
static bool dom_is_node_in_list(const zval *nodes, int nodesc, const xmlNodePtr node_to_find)
{
for (int i = 0; i < nodesc; i++) {
if (Z_TYPE(nodes[i]) == IS_OBJECT) {
const zend_class_entry *ce = Z_OBJCE(nodes[i]);
if (instanceof_function(ce, dom_node_class_entry)) {
if (dom_object_get_node(Z_DOMOBJ_P(nodes + i)) == node_to_find) {
return true;
}
}
}
}
return false;
}
xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, int nodesc)
{
int i;
@ -177,17 +194,16 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod
goto hierarchy_request_err;
}
/*
* 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) {
/* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild):
* "Add a new node to @parent, at the end of the child (or property) list merging adjacent TEXT nodes (in which case @cur is freed)".
* So we must take a copy if this situation arises to prevent a use-after-free. */
bool will_free = newNode->type == XML_TEXT_NODE && fragment->last && fragment->last->type == XML_TEXT_NODE;
if (will_free) {
newNode = xmlCopyNode(newNode, 1);
}
if (!xmlAddChild(fragment, newNode)) {
if (nodesc > 1) {
if (will_free) {
xmlFreeNode(newNode);
}
goto hierarchy_request_err;
@ -303,25 +319,64 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
xmlFree(fragment);
}
static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr fragment)
{
if (!insertion_point) {
/* Place it as last node */
if (parentNode->children) {
/* There are children */
fragment->last->prev = parentNode->last;
newchild->prev = parentNode->last->prev;
parentNode->last->next = newchild;
} else {
/* No children, because they moved out when they became a fragment */
parentNode->children = newchild;
parentNode->last = newchild;
}
} else {
/* Insert fragment before insertion_point */
fragment->last->next = insertion_point;
if (insertion_point->prev) {
insertion_point->prev->next = newchild;
newchild->prev = insertion_point->prev;
}
insertion_point->prev = newchild;
if (parentNode->children == insertion_point) {
parentNode->children = newchild;
}
}
}
void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
{
/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-after */
xmlNode *prevsib = dom_object_get_node(context);
xmlNodePtr newchild, parentNode;
xmlNode *fragment, *nextsib;
xmlNode *fragment;
xmlDoc *doc;
bool afterlastchild;
int stricterror = dom_get_strict_error(context->document);
if (!prevsib->parent) {
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
/* Spec step 1 */
parentNode = prevsib->parent;
/* Spec step 2 */
if (!parentNode) {
int stricterror = dom_get_strict_error(context->document);
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
return;
}
/* Spec step 3: find first following child not in nodes; otherwise null */
xmlNodePtr viable_next_sibling = prevsib->next;
while (viable_next_sibling) {
if (!dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
break;
}
viable_next_sibling = viable_next_sibling->next;
}
doc = prevsib->doc;
parentNode = prevsib->parent;
nextsib = prevsib->next;
afterlastchild = (nextsib == NULL);
/* Spec step 4: convert nodes into fragment */
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
if (fragment == NULL) {
@ -331,40 +386,9 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (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 node).
*/
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;
}
/* Step 5: place fragment into the parent before viable_next_sibling */
dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment);
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(doc, newchild);
}
@ -374,17 +398,34 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
{
/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-before */
xmlNode *nextsib = dom_object_get_node(context);
xmlNodePtr newchild, prevsib, parentNode;
xmlNode *fragment, *afternextsib;
xmlNodePtr newchild, parentNode;
xmlNode *fragment;
xmlDoc *doc;
bool beforefirstchild;
/* Spec step 1 */
parentNode = nextsib->parent;
/* Spec step 2 */
if (!parentNode) {
int stricterror = dom_get_strict_error(context->document);
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
return;
}
/* Spec step 3: find first following child not in nodes; otherwise null */
xmlNodePtr viable_previous_sibling = nextsib->prev;
while (viable_previous_sibling) {
if (!dom_is_node_in_list(nodes, nodesc, viable_previous_sibling)) {
break;
}
viable_previous_sibling = viable_previous_sibling->prev;
}
doc = nextsib->doc;
prevsib = nextsib->prev;
afternextsib = nextsib->next;
parentNode = nextsib->parent;
beforefirstchild = !prevsib;
/* Spec step 4: convert nodes into fragment */
fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
if (fragment == NULL) {
@ -394,37 +435,14 @@ 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;
/* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */
if (!viable_previous_sibling) {
viable_previous_sibling = parentNode->children;
} 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;
viable_previous_sibling = viable_previous_sibling->next;
}
if (parentNode->children == nextsib) {
parentNode->children = newchild;
} else {
prevsib->next = newchild;
}
fragment->last->next = nextsib;
if (nextsib) {
nextsib->prev = fragment->last;
}
newchild->prev = prevsib;
/* Step 6: place fragment into the parent after viable_previous_sibling */
dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment);
dom_fragment_assign_parent_node(parentNode, fragment);
dom_reconcile_ns(doc, newchild);

View File

@ -8,84 +8,84 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before($target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "1 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before($target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "2 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before($doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "3 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before($doc->documentElement->firstChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "4 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before($target, $doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "5 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before($doc->documentElement->lastChild, $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "6 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before($target, $doc->documentElement->firstChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "7 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before($doc->documentElement->firstChild, $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "8 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before('bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "9 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before('bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "10 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before($target, 'bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "11 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before('bar', $target, 'baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "12 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before('bar', 'baz', $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "13 ", $doc->saveXML($doc->documentElement).PHP_EOL;
@ -93,19 +93,19 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before($target, 'bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "14 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before('bar', $target, 'baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "15 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before('bar', 'baz', $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "16 ", $doc->saveXML($doc->documentElement).PHP_EOL;
@ -113,21 +113,21 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before('bar', $target, $doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "17 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before($target, 'bar', $doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "18 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->before($target, $doc->documentElement->lastChild, 'bar');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "19 ", $doc->saveXML($doc->documentElement).PHP_EOL;
@ -136,43 +136,43 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before('bar', $doc->documentElement->firstChild, $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "20 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before($doc->documentElement->firstChild, 'bar', $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "21 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before($doc->documentElement->firstChild, $target, 'bar');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "22 ", $doc->saveXML($doc->documentElement).PHP_EOL;
?>
--EXPECTF--
<a>foo<last/></a>
<a>foo<last/></a>
<a><last/>foo</a>
<a>foo<last/></a>
<a>foo<last/></a>
<a><last/>foo</a>
<a><last/>foo</a>
<a>foo<last/></a>
<a>barbazfoo<last/></a>
<a>foobarbaz<last/></a>
<a>foobarbaz<last/></a>
<a>barfoobaz<last/></a>
<a>barbazfoo<last/></a>
<a>foo<last/>barbaz</a>
<a>foobar<last/>baz</a>
<a>foobarbaz<last/></a>
<a>barfoo<last/></a>
<a>foobar<last/></a>
<a>foo<last/>bar</a>
<a>barfoo<last/></a>
<a>foobar<last/></a>
<a>foo<last/>bar</a>
1 <a>foo<last/></a>
2 <a>foo<last/></a>
3 <a><last/>foo</a>
4 <a>foo<last/></a>
5 <a>foo<last/></a>
6 <a><last/>foo</a>
7 <a><last/>foo</a>
8 <a>foo<last/></a>
9 <a>barbazfoo<last/></a>
10 <a>foobarbaz<last/></a>
11 <a>foobarbaz<last/></a>
12 <a>barfoobaz<last/></a>
13 <a>barbazfoo<last/></a>
14 <a>foo<last/>barbaz</a>
15 <a>foobar<last/>baz</a>
16 <a>foobarbaz<last/></a>
17 <a>barfoo<last/></a>
18 <a>foobar<last/></a>
19 <a>foo<last/>bar</a>
20 <a>barfoo<last/></a>
21 <a>foobar<last/></a>
22 <a>foo<last/>bar</a>

View File

@ -8,84 +8,84 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after($target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "1 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after($target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "2 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after($doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "3 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after($doc->documentElement->firstChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "4 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after($target, $doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "5 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after($doc->documentElement->lastChild, $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "6 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after($target, $doc->documentElement->firstChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "7 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after($doc->documentElement->firstChild, $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "8 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after('bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "9 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after('bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "10 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after($target, 'bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "11 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after('bar', $target, 'baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "12 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after('bar', 'baz', $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "13 ", $doc->saveXML($doc->documentElement).PHP_EOL;
@ -93,19 +93,19 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after($target, 'bar','baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "14 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after('bar', $target, 'baz');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "15 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after('bar', 'baz', $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "16 ", $doc->saveXML($doc->documentElement).PHP_EOL;
@ -113,21 +113,21 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after('bar', $target, $doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "17 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after($target, 'bar', $doc->documentElement->lastChild);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "18 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->firstChild;
$target->after($target, $doc->documentElement->lastChild, 'bar');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "19 ", $doc->saveXML($doc->documentElement).PHP_EOL;
@ -136,43 +136,43 @@ $doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after('bar', $doc->documentElement->firstChild, $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "20 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after($doc->documentElement->firstChild, 'bar', $target);
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "21 ", $doc->saveXML($doc->documentElement).PHP_EOL;
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->after($doc->documentElement->firstChild, $target, 'bar');
echo $doc->saveXML($doc->documentElement).PHP_EOL;
echo "22 ", $doc->saveXML($doc->documentElement).PHP_EOL;
?>
--EXPECTF--
<a>foo<last/></a>
<a>foo<last/></a>
<a>foo<last/></a>
<a><last/>foo</a>
<a>foo<last/></a>
<a><last/>foo</a>
<a><last/>foo</a>
<a>foo<last/></a>
<a>foobarbaz<last/></a>
<a>foo<last/>barbaz</a>
<a>foobarbaz<last/></a>
<a>barfoobaz<last/></a>
<a>barbazfoo<last/></a>
<a>foo<last/>barbaz</a>
<a>foobar<last/>baz</a>
<a>foobarbaz<last/></a>
<a>barfoo<last/></a>
<a>foobar<last/></a>
<a>foo<last/>bar</a>
<a>barfoo<last/></a>
<a>foobar<last/></a>
<a>foo<last/>bar</a>
1 <a>foo<last/></a>
2 <a>foo<last/></a>
3 <a>foo<last/></a>
4 <a><last/>foo</a>
5 <a>foo<last/></a>
6 <a><last/>foo</a>
7 <a><last/>foo</a>
8 <a>foo<last/></a>
9 <a>foobarbaz<last/></a>
10 <a>foo<last/>barbaz</a>
11 <a>foobarbaz<last/></a>
12 <a>barfoobaz<last/></a>
13 <a>barbazfoo<last/></a>
14 <a>foo<last/>barbaz</a>
15 <a>foobar<last/>baz</a>
16 <a>foobarbaz<last/></a>
17 <a>barfoo<last/></a>
18 <a>foobar<last/></a>
19 <a>foo<last/>bar</a>
20 <a>barfoo<last/></a>
21 <a>foobar<last/></a>
22 <a>foo<last/>bar</a>

View File

@ -0,0 +1,120 @@
--TEST--
Bug #80602 (Segfault when using DOMChildNode::before()) - use-after-free variation
--FILE--
<?php
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
$target->before('bar', $doc->documentElement->firstChild, 'baz');
echo $doc->saveXML($doc->documentElement), "\n";
var_dump($target);
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/></a>');
$target = $doc->documentElement->lastChild;
// Note: after instead of before
$target->after('bar', $doc->documentElement->firstChild, 'baz');
echo $doc->saveXML($doc->documentElement), "\n";
var_dump($target);
?>
--EXPECTF--
<a>barfoobaz<last/></a>
object(DOMElement)#3 (23) {
["schemaTypeInfo"]=>
NULL
["tagName"]=>
string(4) "last"
["firstElementChild"]=>
NULL
["lastElementChild"]=>
NULL
["childElementCount"]=>
int(0)
["previousElementSibling"]=>
NULL
["nextElementSibling"]=>
NULL
["nodeName"]=>
string(4) "last"
["nodeValue"]=>
string(0) ""
["nodeType"]=>
int(1)
["parentNode"]=>
string(22) "(object value omitted)"
["childNodes"]=>
string(22) "(object value omitted)"
["firstChild"]=>
NULL
["lastChild"]=>
NULL
["previousSibling"]=>
string(22) "(object value omitted)"
["nextSibling"]=>
NULL
["attributes"]=>
string(22) "(object value omitted)"
["ownerDocument"]=>
string(22) "(object value omitted)"
["namespaceURI"]=>
NULL
["prefix"]=>
string(0) ""
["localName"]=>
string(4) "last"
["baseURI"]=>
string(%d) %s
["textContent"]=>
string(0) ""
}
<a><last/>barfoobaz</a>
object(DOMElement)#2 (23) {
["schemaTypeInfo"]=>
NULL
["tagName"]=>
string(4) "last"
["firstElementChild"]=>
NULL
["lastElementChild"]=>
NULL
["childElementCount"]=>
int(0)
["previousElementSibling"]=>
NULL
["nextElementSibling"]=>
NULL
["nodeName"]=>
string(4) "last"
["nodeValue"]=>
string(0) ""
["nodeType"]=>
int(1)
["parentNode"]=>
string(22) "(object value omitted)"
["childNodes"]=>
string(22) "(object value omitted)"
["firstChild"]=>
NULL
["lastChild"]=>
NULL
["previousSibling"]=>
NULL
["nextSibling"]=>
string(22) "(object value omitted)"
["attributes"]=>
string(22) "(object value omitted)"
["ownerDocument"]=>
string(22) "(object value omitted)"
["namespaceURI"]=>
NULL
["prefix"]=>
string(0) ""
["localName"]=>
string(4) "last"
["baseURI"]=>
string(%d) %s
["textContent"]=>
string(0) ""
}

View File

@ -0,0 +1,33 @@
--TEST--
Bug #80602 (Segfault when using DOMChildNode::before()) - after text merge variation
--FILE--
<?php
$doc = new \DOMDocument();
$doc->loadXML('<a>foo<last/>bar</a>');
$foo = $doc->firstChild->firstChild;
$bar = $doc->firstChild->lastChild;
$foo->after($bar);
var_dump($doc->saveXML());
$foo->nodeValue = "x";
var_dump($doc->saveXML());
$bar->nodeValue = "y";
var_dump($doc->saveXML());
?>
--EXPECT--
string(43) "<?xml version="1.0"?>
<a>foobar<last/></a>
"
string(41) "<?xml version="1.0"?>
<a>xbar<last/></a>
"
string(39) "<?xml version="1.0"?>
<a>xy<last/></a>
"

View File

@ -0,0 +1,67 @@
--TEST--
GH-11288 (Error: Couldn't fetch DOMElement introduced in 8.2.6, 8.1.19)
--FILE--
<?php
$html = <<<HTML
<!DOCTYPE HTML>
<html>
<span class="unwrap_me"><i><span class="foo">Lorem</span></i><span class="foo">ipsum</span></span>
</html>
HTML;
$dom = new DOMDocument();
$dom->loadHTML($html);
$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator());
foreach ($spans as $span) {
if ('unwrap_me' === $span->getAttribute('class')) {
$fragment = $dom->createDocumentFragment();
$fragment->append(...$span->childNodes);
$span->parentNode?->replaceChild($fragment, $span);
}
}
var_dump(str_replace("\n", "", $dom->saveHTML()));
$html = <<<HTML
<!DOCTYPE HTML>
<html>
<span class="unwrap_me"><i><span class="foo">Lorem</span></i><span class="foo">ipsum</span></span>
</html>
HTML;
$dom = new DOMDocument();
$dom->loadHTML($html);
$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator());
foreach ($spans as $span) {
if ('unwrap_me' === $span->getAttribute('class')) {
$span->replaceWith(...$span->childNodes);
}
}
var_dump(str_replace("\n", "", $dom->saveHTML()));
$html = <<<HTML
<!DOCTYPE HTML>
<html>
<span class="unwrap_me"><i><span class="foo">Lorem</span></i><span class="foo">ipsum</span></span>
</html>
HTML;
$dom = new DOMDocument();
$dom->loadHTML($html);
$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator());
foreach ($spans as $span) {
if ('unwrap_me' === $span->getAttribute('class')) {
$span->replaceWith('abc');
}
}
var_dump(str_replace("\n", "", $dom->saveHTML()));
?>
--EXPECT--
string(108) "<!DOCTYPE HTML><html><body><i><span class="foo">Lorem</span></i><span class="foo">ipsum</span></body></html>"
string(108) "<!DOCTYPE HTML><html><body><i><span class="foo">Lorem</span></i><span class="foo">ipsum</span></body></html>"
string(44) "<!DOCTYPE HTML><html><body>abc</body></html>"

View File

@ -0,0 +1,28 @@
--TEST--
GH-11289 (DOMException: Not Found Error introduced in 8.2.6, 8.1.19)
--FILE--
<?php
$html = <<<HTML
<!DOCTYPE HTML>
<html>
<body>
<div></div>
</body>
</html>
HTML;
$dom = new DOMDocument();
$dom->loadHTML($html);
$divs = iterator_to_array($dom->getElementsByTagName('div')->getIterator());
foreach ($divs as $div) {
$fragment = $dom->createDocumentFragment();
$fragment->appendXML('<p>Hi!</p>');
$div->replaceWith(...$fragment->childNodes);
}
var_dump(str_replace("\n", "", $dom->saveHTML()));
?>
--EXPECT--
string(55) "<!DOCTYPE HTML><html><body> <p>Hi!</p></body></html>"

View File

@ -0,0 +1,27 @@
--TEST--
GH-11290 (DOMElement::replaceWith causes crash)
--FILE--
<?php
$html = <<<HTML
<!DOCTYPE HTML>
<html>
<body>
<p><span class="unwrap_me">Lorem</span><span class="unwrap_me">ipsum</span><span class="unwrap_me">dolor</span></p>
</body>
</html>
HTML;
$dom = new DOMDocument();
$dom->loadHTML($html);
$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator());
foreach ($spans as $span) {
if ('unwrap_me' === $span->getAttribute('class')) {
$span->replaceWith(...$span->childNodes);
}
}
var_dump(str_replace("\n", "", $dom->saveHTML()));
?>
--EXPECT--
string(67) "<!DOCTYPE HTML><html><body> <p>Loremipsumdolor</p></body></html>"

20
ext/dom/tests/gh9142.phpt Normal file
View File

@ -0,0 +1,20 @@
--TEST--
GH-9142 (DOMChildNode replaceWith() double-free error when replacing elements not separated by any whitespace)
--FILE--
<?php
$document = '<var>One</var><var>Two</var>';
($dom = new DOMDocument('1.0', 'UTF-8'))->loadHTML($document);
foreach ((new DOMXPath($dom))->query('//var') as $var) {
$var->replaceWith($dom->createElement('p', $var->nodeValue));
}
var_dump($dom->saveHTML());
?>
--EXPECT--
string(154) "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>One</p><p>Two</p></body></html>
"