Skip to content

Commit d19e4da

Browse files
committed
Fix segfault when DOMParentNode::prepend() is called when the child disappears
Closes GH-11906.
1 parent 4cbc66d commit d19e4da

File tree

3 files changed

+77
-33
lines changed

3 files changed

+77
-33
lines changed

NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ PHP NEWS
2323
. Fix manually calling __construct() on DOM classes. (nielsdos)
2424
. Fixed bug GH-11830 (ParentNode methods should perform their checks
2525
upfront). (nielsdos)
26+
. Fix segfault when DOMParentNode::prepend() is called when the child
27+
disappears. (nielsdos)
2628

2729
- FFI:
2830
. Fix leaking definitions when using FFI::cdef()->new(...). (ilutov)

ext/dom/parentnode.c

+30-33
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,33 @@ static zend_result dom_sanity_check_node_list_for_insertion(php_libxml_ref_obj *
280280
return SUCCESS;
281281
}
282282

283+
static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr fragment)
284+
{
285+
if (!insertion_point) {
286+
/* Place it as last node */
287+
if (parentNode->children) {
288+
/* There are children */
289+
newchild->prev = parentNode->last;
290+
parentNode->last->next = newchild;
291+
} else {
292+
/* No children, because they moved out when they became a fragment */
293+
parentNode->children = newchild;
294+
}
295+
parentNode->last = fragment->last;
296+
} else {
297+
/* Insert fragment before insertion_point */
298+
fragment->last->next = insertion_point;
299+
if (insertion_point->prev) {
300+
insertion_point->prev->next = newchild;
301+
newchild->prev = insertion_point->prev;
302+
}
303+
insertion_point->prev = fragment->last;
304+
if (parentNode->children == insertion_point) {
305+
parentNode->children = newchild;
306+
}
307+
}
308+
}
309+
283310
void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc)
284311
{
285312
xmlNode *parentNode = dom_object_get_node(context);
@@ -331,21 +358,18 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
331358
return;
332359
}
333360

334-
xmlNodePtr newchild, nextsib;
335361
xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
336362

337363
if (fragment == NULL) {
338364
return;
339365
}
340366

341-
newchild = fragment->children;
342-
nextsib = parentNode->children;
367+
xmlNode *newchild = fragment->children;
343368

344369
if (newchild) {
345370
xmlNodePtr last = fragment->last;
346-
parentNode->children = newchild;
347-
fragment->last->next = nextsib;
348-
nextsib->prev = last;
371+
372+
dom_pre_insert(parentNode->children, parentNode, newchild, fragment);
349373

350374
dom_fragment_assign_parent_node(parentNode, fragment);
351375

@@ -355,33 +379,6 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
355379
xmlFree(fragment);
356380
}
357381

358-
static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr fragment)
359-
{
360-
if (!insertion_point) {
361-
/* Place it as last node */
362-
if (parentNode->children) {
363-
/* There are children */
364-
newchild->prev = parentNode->last;
365-
parentNode->last->next = newchild;
366-
} else {
367-
/* No children, because they moved out when they became a fragment */
368-
parentNode->children = newchild;
369-
}
370-
parentNode->last = fragment->last;
371-
} else {
372-
/* Insert fragment before insertion_point */
373-
fragment->last->next = insertion_point;
374-
if (insertion_point->prev) {
375-
insertion_point->prev->next = newchild;
376-
newchild->prev = insertion_point->prev;
377-
}
378-
insertion_point->prev = fragment->last;
379-
if (parentNode->children == insertion_point) {
380-
parentNode->children = newchild;
381-
}
382-
}
383-
}
384-
385382
void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
386383
{
387384
/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-after */

ext/dom/tests/gh11906.phpt

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
GH-11906 (prepend without children after creating fragment results in segfault)
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
$doc = new DOMDocument;
8+
$doc->loadXML(<<<XML
9+
<?xml version="1.0"?>
10+
<container>
11+
<child/>
12+
</container>
13+
XML);
14+
15+
$container = $doc->documentElement;
16+
$child = $container->firstElementChild;
17+
18+
$test = $doc->createElement('foo');
19+
$test->append($child);
20+
echo "--- document output ---\n";
21+
echo $doc->saveXML();
22+
echo "--- \$test output ---\n";
23+
echo $doc->saveXML($test), "\n";
24+
$test->prepend($child);
25+
echo "--- document output ---\n";
26+
echo $doc->saveXML();
27+
echo "--- \$test output ---\n";
28+
echo $doc->saveXML($test), "\n";
29+
$test->append($child);
30+
?>
31+
--EXPECT--
32+
--- document output ---
33+
<?xml version="1.0"?>
34+
<container>
35+
36+
</container>
37+
--- $test output ---
38+
<foo><child/></foo>
39+
--- document output ---
40+
<?xml version="1.0"?>
41+
<container>
42+
43+
</container>
44+
--- $test output ---
45+
<foo><child/></foo>

0 commit comments

Comments
 (0)