1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Fix #79700: Bad performance with namespaced nodes due to wrong libxml assumption

* Use a prepending strategy instead of appending in dom_set_old_ns()

Looping to the end of the list is wasteful. We can just put the new
nodes at the front of the list. I don't believe we can fully prepend,
because libxml2 may assume that the xml namespace is the first one, so
we'll put the new ones as the second one.

* Reuse namespaces from doc->oldNs if possible in dom_get_ns()

* Add a test for reconciling a reused namespace

* Explain why there can't be a cycle between oldNs and nsDef

Closes GH-11376.

Also fixes #77894.
This commit is contained in:
nielsdos
2023-06-05 21:56:51 +02:00
parent 50b4df18e0
commit a38e3c999f
3 changed files with 85 additions and 16 deletions

6
NEWS
View File

@@ -6,6 +6,12 @@ PHP NEWS
. Fix GH-11388 (Allow "final" modifier when importing a method from a trait).
(nielsdos)
- DOM:
. Fix #79700 (wrong use of libxml oldNs leads to performance problem).
(nielsdos)
. Fix #77894 (DOMNode::C14N() very slow on generated DOMDocuments even after
normalisation). (nielsdos)
08 Jun 2023, PHP 8.3.0alpha1
- CLI:

View File

@@ -1369,11 +1369,16 @@ void dom_normalize (xmlNodePtr nodep)
/* {{{ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) */
void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) {
xmlNs *cur;
if (doc == NULL)
return;
ZEND_ASSERT(ns->next == NULL);
/* Note: we'll use a prepend strategy instead of append to
* make sure we don't lose performance when the list is long.
* As libxml2 could assume the xml node is the first one, we'll place our
* new entries after the first one. */
if (doc->oldNs == NULL) {
doc->oldNs = (xmlNsPtr) xmlMalloc(sizeof(xmlNs));
if (doc->oldNs == NULL) {
@@ -1383,13 +1388,10 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) {
doc->oldNs->type = XML_LOCAL_NAMESPACE;
doc->oldNs->href = xmlStrdup(XML_XML_NAMESPACE);
doc->oldNs->prefix = xmlStrdup((const xmlChar *)"xml");
} else {
ns->next = doc->oldNs->next;
}
cur = doc->oldNs;
while (cur->next != NULL) {
cur = cur->next;
}
cur->next = ns;
doc->oldNs->next = ns;
}
/* }}} end dom_set_old_ns */
@@ -1411,6 +1413,9 @@ static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep)
} else {
prevns->next = nsdftptr;
}
/* Note: we can't get here if the ns is already on the oldNs list.
* This is because in that case the definition won't be on the node, and
* therefore won't be in the nodep->nsDef list. */
dom_set_old_ns(doc, curns);
curns = prevns;
}
@@ -1509,22 +1514,38 @@ NAMESPACE_ERR: Raised if
/* {{{ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) */
xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) {
xmlNsPtr nsptr = NULL;
*errorcode = 0;
xmlNsPtr nsptr;
if (! ((prefix && !strcmp (prefix, "xml") && strcmp(uri, (char *)XML_XML_NAMESPACE)) ||
(prefix && !strcmp (prefix, "xmlns") && strcmp(uri, (char *)DOM_XMLNS_NAMESPACE)) ||
(prefix && !strcmp(uri, (char *)DOM_XMLNS_NAMESPACE) && strcmp (prefix, "xmlns")))) {
/* Reuse the old namespaces from doc->oldNs if possible, before creating a new one.
* This will prevent the oldNs list from growing with duplicates. */
xmlDocPtr doc = nodep->doc;
if (doc && doc->oldNs != NULL) {
nsptr = doc->oldNs;
do {
if (xmlStrEqual(nsptr->prefix, (xmlChar *)prefix) && xmlStrEqual(nsptr->href, (xmlChar *)uri)) {
goto out;
}
nsptr = nsptr->next;
} while (nsptr);
}
/* Couldn't reuse one, create a new one. */
nsptr = xmlNewNs(nodep, (xmlChar *)uri, (xmlChar *)prefix);
if (UNEXPECTED(nsptr == NULL)) {
goto err;
}
} else {
goto err;
}
if (nsptr == NULL) {
*errorcode = NAMESPACE_ERR;
}
out:
*errorcode = 0;
return nsptr;
err:
*errorcode = NAMESPACE_ERR;
return NULL;
}
/* }}} end dom_get_ns */

View File

@@ -0,0 +1,42 @@
--TEST--
Reconcile a reused namespace from doc->oldNs
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$root = $dom->createElementNS('http://www.w3.org/2000/xhtml', 'html');
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<html
xmlns="http://www.w3.org/2000/xhtml"
xmlns:a="http://example.com/A"
xmlns:b="http://example.com/B"
/>
XML);
$root = $dom->firstElementChild;
echo "Add first\n";
$element = $dom->createElementNS('http://example.com/B', 'p', 'Hello World');
$root->appendChild($element);
echo "Add second\n";
$element = $dom->createElementNS('http://example.com/A', 'p', 'Hello World');
$root->appendChild($element);
echo "Add third\n";
$element = $dom->createElementNS('http://example.com/A', 'p', 'Hello World');
$root->appendChild($element);
var_dump($dom->saveXML());
?>
--EXPECT--
Add first
Add second
Add third
string(201) "<?xml version="1.0"?>
<html xmlns="http://www.w3.org/2000/xhtml" xmlns:a="http://example.com/A" xmlns:b="http://example.com/B"><b:p>Hello World</b:p><a:p>Hello World</a:p><a:p>Hello World</a:p></html>
"