Skip to content

Commit 10d94ac

Browse files
committed
Fix "invalid state error" with cloned namespace declarations
Closes phpGH-11429.
1 parent e309fd8 commit 10d94ac

File tree

3 files changed

+117
-13
lines changed

3 files changed

+117
-13
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ PHP NEWS
3131
php_libxml_node_free_list()). (nielsdos)
3232
. Fixed bug #78577 (Crash in DOMNameSpace debug info handlers). (nielsdos)
3333
. Fix lifetime issue with getAttributeNodeNS(). (nielsdos)
34+
. Fix "invalid state error" with cloned namespace declarations. (nielsdos)
3435

3536
- Opcache:
3637
. Fix allocation loop in zend_shared_alloc_startup(). (nielsdos)

ext/dom/php_dom.c

+44-13
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ static HashTable dom_xpath_prop_handlers;
8989

9090
static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type);
9191
static void dom_object_namespace_node_free_storage(zend_object *object);
92+
static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original);
9293

9394
typedef int (*dom_read_t)(dom_object *obj, zval *retval);
9495
typedef int (*dom_write_t)(dom_object *obj, zval *newval);
@@ -477,6 +478,19 @@ PHP_FUNCTION(dom_import_simplexml)
477478

478479
static dom_object* dom_objects_set_class(zend_class_entry *class_type);
479480

481+
static void dom_update_refcount_after_clone(dom_object *original, xmlNodePtr original_node, dom_object *clone, xmlNodePtr cloned_node)
482+
{
483+
/* If we cloned a document then we must create new doc proxy */
484+
if (cloned_node->doc == original_node->doc) {
485+
clone->document = original->document;
486+
}
487+
php_libxml_increment_doc_ref((php_libxml_node_object *)clone, cloned_node->doc);
488+
php_libxml_increment_node_ptr((php_libxml_node_object *)clone, cloned_node, (void *)clone);
489+
if (original->document != clone->document) {
490+
dom_copy_doc_props(original->document, clone->document);
491+
}
492+
}
493+
480494
static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
481495
{
482496
dom_object *intern = php_dom_obj_from_obj(zobject);
@@ -489,15 +503,7 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
489503
if (node != NULL) {
490504
xmlNodePtr cloned_node = xmlDocCopyNode(node, node->doc, 1);
491505
if (cloned_node != NULL) {
492-
/* If we cloned a document then we must create new doc proxy */
493-
if (cloned_node->doc == node->doc) {
494-
clone->document = intern->document;
495-
}
496-
php_libxml_increment_doc_ref((php_libxml_node_object *)clone, cloned_node->doc);
497-
php_libxml_increment_node_ptr((php_libxml_node_object *)clone, cloned_node, (void *)clone);
498-
if (intern->document != clone->document) {
499-
dom_copy_doc_props(intern->document, clone->document);
500-
}
506+
dom_update_refcount_after_clone(intern, node, clone, cloned_node);
501507
}
502508

503509
}
@@ -509,6 +515,26 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
509515
}
510516
/* }}} */
511517

518+
static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject)
519+
{
520+
dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(zobject);
521+
zend_object *clone = dom_objects_namespace_node_new(intern->dom.std.ce);
522+
dom_object_namespace_node *clone_intern = php_dom_namespace_node_obj_from_obj(clone);
523+
524+
xmlNodePtr original_node = dom_object_get_node(&intern->dom);
525+
ZEND_ASSERT(original_node->type == XML_NAMESPACE_DECL);
526+
xmlNodePtr cloned_node = php_dom_create_fake_namespace_decl_node_ptr(original_node->parent, original_node->ns);
527+
528+
if (intern->parent_intern) {
529+
clone_intern->parent_intern = intern->parent_intern;
530+
GC_ADDREF(&clone_intern->parent_intern->std);
531+
}
532+
dom_update_refcount_after_clone(&intern->dom, original_node, &clone_intern->dom, cloned_node);
533+
534+
zend_objects_clone_members(clone, &intern->dom.std);
535+
return clone;
536+
}
537+
512538
static void dom_copy_prop_handler(zval *zv) /* {{{ */
513539
{
514540
dom_prop_handler *hnd = Z_PTR_P(zv);
@@ -577,6 +603,7 @@ PHP_MINIT_FUNCTION(dom)
577603
memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
578604
dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std);
579605
dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage;
606+
dom_object_namespace_node_handlers.clone_obj = dom_object_namespace_node_clone_obj;
580607

581608
zend_hash_init(&classes, 0, NULL, NULL, 1);
582609

@@ -1579,8 +1606,7 @@ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) {
15791606
}
15801607
/* }}} end dom_get_nsdecl */
15811608

1582-
/* Note: Assumes the additional lifetime was already added in the caller. */
1583-
xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern)
1609+
static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original)
15841610
{
15851611
xmlNodePtr attrp;
15861612
xmlNsPtr curns = xmlNewNs(NULL, original->href, NULL);
@@ -1593,11 +1619,16 @@ xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr origina
15931619
attrp->type = XML_NAMESPACE_DECL;
15941620
attrp->parent = nodep;
15951621
attrp->ns = curns;
1622+
return attrp;
1623+
}
15961624

1625+
/* Note: Assumes the additional lifetime was already added in the caller. */
1626+
xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern)
1627+
{
1628+
xmlNodePtr attrp = php_dom_create_fake_namespace_decl_node_ptr(nodep, original);
15971629
php_dom_create_object(attrp, return_value, parent_intern);
15981630
/* This object must exist, because we just created an object for it via php_dom_create_object(). */
1599-
dom_object *obj = ((php_libxml_node_ptr *)attrp->_private)->_private;
1600-
php_dom_namespace_node_obj_from_obj(&obj->std)->parent_intern = parent_intern;
1631+
php_dom_namespace_node_obj_from_obj(Z_OBJ_P(return_value))->parent_intern = parent_intern;
16011632
return attrp;
16021633
}
16031634

ext/dom/tests/clone_nodes.phpt

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
Clone nodes
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
echo "-- Clone DOMNameSpaceNode --\n";
9+
10+
$doc = new DOMDocument;
11+
$doc->loadXML('<foo xmlns="http://php.net/test" xmlns:foo="urn:foo" />');
12+
13+
$attr = $doc->documentElement->getAttributeNode('xmlns');
14+
var_dump($attr);
15+
16+
$attrClone = clone $attr;
17+
var_dump($attrClone->nodeValue);
18+
var_dump($attrClone->parentNode->nodeName);
19+
20+
unset($doc);
21+
unset($attr);
22+
23+
var_dump($attrClone->nodeValue);
24+
var_dump($attrClone->parentNode->nodeName);
25+
26+
echo "-- Clone DOMNode --\n";
27+
28+
$doc = new DOMDocument;
29+
$doc->loadXML('<foo><bar/></foo>');
30+
31+
$bar = $doc->documentElement->firstChild;
32+
$barClone = clone $bar;
33+
$bar->remove();
34+
unset($bar);
35+
36+
var_dump($barClone->nodeName);
37+
38+
$doc->firstElementChild->remove();
39+
unset($doc);
40+
41+
var_dump($barClone->nodeName);
42+
var_dump($barClone->parentNode);
43+
44+
?>
45+
--EXPECT--
46+
-- Clone DOMNameSpaceNode --
47+
object(DOMNameSpaceNode)#3 (8) {
48+
["nodeName"]=>
49+
string(5) "xmlns"
50+
["nodeValue"]=>
51+
string(19) "http://php.net/test"
52+
["nodeType"]=>
53+
int(18)
54+
["prefix"]=>
55+
string(0) ""
56+
["localName"]=>
57+
string(5) "xmlns"
58+
["namespaceURI"]=>
59+
string(19) "http://php.net/test"
60+
["ownerDocument"]=>
61+
string(22) "(object value omitted)"
62+
["parentNode"]=>
63+
string(22) "(object value omitted)"
64+
}
65+
string(19) "http://php.net/test"
66+
string(3) "foo"
67+
string(19) "http://php.net/test"
68+
string(3) "foo"
69+
-- Clone DOMNode --
70+
string(3) "bar"
71+
string(3) "bar"
72+
NULL

0 commit comments

Comments
 (0)