Skip to content

Commit 23ba4cd

Browse files
committed
Align DOMChildNode parent checks with spec
Closes GH-11905.
1 parent e157da1 commit 23ba4cd

File tree

6 files changed

+98
-36
lines changed

6 files changed

+98
-36
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ PHP NEWS
77

88
- DOM:
99
. adoptNode now respects the strict error checking property. (nielsdos)
10+
. Align DOMChildNode parent checks with spec. (nielsdos)
1011

1112
- Opcache:
1213
. Avoid resetting JIT counter handlers from multiple processes/threads.

UPGRADING

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ PHP 8.3 UPGRADE NOTES
4646
iterating over the WeakMap (reachability via iteration is considered weak).
4747
Previously, such entries would never be automatically removed.
4848

49+
- DOM:
50+
. DOMChildNode::after(), DOMChildNode::before(), DOMChildNode::replaceWith()
51+
on a node that has no parent is now a no-op instead of a hierarchy exception,
52+
which is the behaviour spec demands.
53+
. Using the DOMParentNode and DOMChildNode methods without a document now works
54+
instead of throwing a HIERARCHY_REQUEST_ERR DOMException. This is in line with
55+
the behaviour spec demands.
56+
4957
- FFI:
5058
. C functions that have a return type of void now return null instead of
5159
returning the following object object(FFI\CData:void) { }

ext/dom/parentnode.c

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ static void dom_fragment_assign_parent_node(xmlNodePtr parentNode, xmlNodePtr fr
240240

241241
static zend_result dom_sanity_check_node_list_for_insertion(php_libxml_ref_obj *document, xmlNodePtr parentNode, zval *nodes, int nodesc)
242242
{
243-
if (document == NULL) {
244-
php_dom_throw_error(HIERARCHY_REQUEST_ERR, 1);
243+
if (UNEXPECTED(parentNode == NULL)) {
244+
/* No error required, this must be a no-op per spec */
245245
return FAILURE;
246246
}
247247

@@ -391,10 +391,9 @@ void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc)
391391

392392
/* Spec step 1 */
393393
parentNode = prevsib->parent;
394-
/* Spec step 2 */
395-
if (!parentNode) {
396-
int stricterror = dom_get_strict_error(context->document);
397-
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
394+
395+
/* Sanity check for fragment, includes spec step 2 */
396+
if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
398397
return;
399398
}
400399

@@ -409,10 +408,6 @@ void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc)
409408

410409
doc = prevsib->doc;
411410

412-
if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
413-
return;
414-
}
415-
416411
php_libxml_invalidate_node_list_cache_from_doc(doc);
417412

418413
/* Spec step 4: convert nodes into fragment */
@@ -448,10 +443,9 @@ void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc)
448443

449444
/* Spec step 1 */
450445
parentNode = nextsib->parent;
451-
/* Spec step 2 */
452-
if (!parentNode) {
453-
int stricterror = dom_get_strict_error(context->document);
454-
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
446+
447+
/* Sanity check for fragment, includes spec step 2 */
448+
if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
455449
return;
456450
}
457451

@@ -466,10 +460,6 @@ void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc)
466460

467461
doc = nextsib->doc;
468462

469-
if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
470-
return;
471-
}
472-
473463
php_libxml_invalidate_node_list_cache_from_doc(doc);
474464

475465
/* Spec step 4: convert nodes into fragment */
@@ -537,7 +527,7 @@ void dom_child_node_remove(dom_object *context)
537527
return;
538528
}
539529

540-
php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr);
530+
php_libxml_invalidate_node_list_cache_from_doc(child->doc);
541531

542532
xmlUnlinkNode(child);
543533
}
@@ -550,10 +540,9 @@ void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
550540

551541
/* Spec step 1 */
552542
xmlNodePtr parentNode = child->parent;
553-
/* Spec step 2 */
554-
if (!parentNode) {
555-
int stricterror = dom_get_strict_error(context->document);
556-
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
543+
544+
/* Sanity check for fragment, includes spec step 2 */
545+
if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
557546
return;
558547
}
559548

@@ -571,11 +560,8 @@ void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
571560
viable_next_sibling = viable_next_sibling->next;
572561
}
573562

574-
if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
575-
return;
576-
}
577-
578-
php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr);
563+
xmlDocPtr doc = parentNode->doc;
564+
php_libxml_invalidate_node_list_cache_from_doc(doc);
579565

580566
/* Spec step 4: convert nodes into fragment */
581567
xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
@@ -586,7 +572,6 @@ void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
586572
/* Spec step 5: perform the replacement */
587573

588574
xmlNodePtr newchild = fragment->children;
589-
xmlDocPtr doc = parentNode->doc;
590575

591576
/* Unlink it unless it became a part of the fragment.
592577
* Freeing will be taken care of by the lifetime of the returned dom object. */
@@ -621,7 +606,7 @@ void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t
621606
return;
622607
}
623608

624-
php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr);
609+
php_libxml_invalidate_node_list_cache_from_doc(thisp->doc);
625610

626611
dom_remove_all_children(thisp);
627612

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
DOMChildNode methods without a parent
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+
foreach (['before', 'after', 'replaceWith'] as $method) {
20+
echo "--- $method ---\n";
21+
$test->$method($child);
22+
echo $doc->saveXML();
23+
echo $doc->saveXML($test), "\n";
24+
}
25+
?>
26+
--EXPECT--
27+
--- before ---
28+
<?xml version="1.0"?>
29+
<container>
30+
<child/>
31+
</container>
32+
<foo/>
33+
--- after ---
34+
<?xml version="1.0"?>
35+
<container>
36+
<child/>
37+
</container>
38+
<foo/>
39+
--- replaceWith ---
40+
<?xml version="1.0"?>
41+
<container>
42+
<child/>
43+
</container>
44+
<foo/>

ext/dom/tests/bug79968.phpt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ dom
77

88
$cdata = new DOMText;
99

10-
try {
11-
$cdata->before("string");
12-
} catch (DOMException $e) {
13-
echo $e->getMessage();
14-
}
10+
$cdata->before("string");
11+
$cdata->after("string");
12+
$cdata->replaceWith("string");
13+
14+
$dom = new DOMDocument();
15+
$dom->adoptNode($cdata);
16+
var_dump($dom->saveXML($cdata));
17+
1518
?>
1619
--EXPECT--
17-
Hierarchy Request Error
20+
string(0) ""
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
DOMElement: DOMChildNode, DOMParentNode modifications without a document
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$element = new DOMElement("p", " Hello World! ");
9+
$element->append("APPENDED");
10+
$element->prepend("PREPENDED");
11+
$element->after("AFTER");
12+
$element->before("BEFORE");
13+
$element->replaceWith("REPLACE");
14+
15+
$doc = new DOMDocument();
16+
$doc->adoptNode($element);
17+
echo $doc->saveXML($element), "\n";
18+
19+
?>
20+
--EXPECT--
21+
<p>PREPENDED Hello World! APPENDED</p>

0 commit comments

Comments
 (0)