Apply SimpleXML iterator fixes only on master

Many methods in SimpleXML reset the iterator when called. This has the
consequence that mixing these operations with loops can cause infinite
loops, or the loss of iteration data.
Some people may however rely on the resetting behaviour. To prevent
unintended breaks in stable branches, let's only apply the fix to master.

This reverts GH-12193, GH-12229, GG-12247 for stable branches while
keeping them on master, adding a note in UPGRADING as well.
This commit is contained in:
Niels Dossche 2023-09-28 19:53:01 +02:00
parent 1f5bea3452
commit b842ea4fa8
6 changed files with 33 additions and 193 deletions

5
NEWS
View File

@ -39,13 +39,8 @@ PHP NEWS
- SimpleXML:
. Fixed bug GH-12170 (Can't use xpath with comments in SimpleXML). (nielsdos)
. Fixed bug GH-12192 (SimpleXML infinite loop when getName() is called
within foreach). (nielsdos)
. Fixed bug GH-12223 (Entity reference produces infinite loop in
var_dump/print_r). (nielsdos)
. Fixed bug GH-12208 (SimpleXML infinite loop when a cast is used inside a
foreach). (nielsdos)
. Fixed bug #55098 (SimpleXML iteration produces infinite loop). (nielsdos)
. Fixed bug GH-12167 (Unable to get processing instruction contents in
SimpleXML). (nielsdos)
. Fixed bug GH-12169 (Unable to get comment contents in SimpleXML).

View File

@ -78,10 +78,10 @@ static void _node_as_zval(php_sxe_object *sxe, xmlNodePtr node, zval *value, SXE
}
/* }}} */
static xmlNodePtr php_sxe_get_first_node_non_destructive(php_sxe_object *sxe, xmlNodePtr node)
static xmlNodePtr php_sxe_get_first_node(php_sxe_object *sxe, xmlNodePtr node)
{
if (sxe && sxe->iter.type != SXE_ITER_NONE) {
return php_sxe_reset_iterator_no_clear_iter_data(sxe, false);
return php_sxe_reset_iterator(sxe, 1);
} else {
return node;
}
@ -165,7 +165,7 @@ static xmlNodePtr sxe_get_element_by_name(php_sxe_object *sxe, xmlNodePtr node,
if (sxe->iter.type == SXE_ITER_NONE) {
sxe->iter.type = SXE_ITER_CHILD;
}
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
sxe->iter.type = orgtype;
}
@ -251,11 +251,11 @@ long_dim:
if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
if (!member && node && node->parent &&
@ -303,7 +303,7 @@ long_dim:
xmlNodePtr mynode = node;
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
}
if (sxe->iter.type == SXE_ITER_NONE) {
if (member && Z_LVAL_P(member) > 0) {
@ -437,12 +437,12 @@ long_dim:
if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
mynode = node;
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
if (!member && node && node->parent &&
@ -688,7 +688,7 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt
attribs = 0;
elements = 1;
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
}
}
}
@ -696,11 +696,11 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt
if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
}
@ -740,7 +740,7 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt
if (elements) {
if (Z_TYPE_P(member) == IS_LONG) {
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
}
node = sxe_get_element_by_offset(sxe, Z_LVAL_P(member), node, NULL);
} else {
@ -810,7 +810,7 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements
attribs = 0;
elements = 1;
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
}
}
}
@ -818,11 +818,11 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements
if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
}
@ -859,7 +859,7 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements
if (elements) {
if (Z_TYPE_P(member) == IS_LONG) {
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
}
node = sxe_get_element_by_offset(sxe, Z_LVAL_P(member), node, NULL);
if (node) {
@ -992,7 +992,7 @@ static int sxe_prop_is_empty(zend_object *object) /* {{{ */
}
if (sxe->iter.type == SXE_ITER_ELEMENT) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
}
if (!node || node->type != XML_ENTITY_DECL) {
attr = node ? (xmlAttrPtr)node->properties : NULL;
@ -1006,7 +1006,7 @@ static int sxe_prop_is_empty(zend_object *object) /* {{{ */
}
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
is_empty = 1;
ZVAL_UNDEF(&iter_data);
if (node && sxe->iter.type != SXE_ITER_ATTRLIST) {
@ -1101,7 +1101,7 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */
}
if (is_debug || sxe->iter.type != SXE_ITER_CHILD) {
if (sxe->iter.type == SXE_ITER_ELEMENT) {
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
}
if (!node || node->type != XML_ENTITY_DECL) {
attr = node ? (xmlAttrPtr)node->properties : NULL;
@ -1123,7 +1123,7 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */
}
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (node && sxe->iter.type != SXE_ITER_ATTRLIST) {
if (node->type == XML_ATTRIBUTE_NODE) {
@ -1282,7 +1282,7 @@ PHP_METHOD(SimpleXMLElement, xpath)
}
GET_NODE(sxe, nodeptr);
nodeptr = php_sxe_get_first_node_non_destructive(sxe, nodeptr);
nodeptr = php_sxe_get_first_node(sxe, nodeptr);
if (!nodeptr) {
return;
}
@ -1391,7 +1391,7 @@ PHP_METHOD(SimpleXMLElement, asXML)
sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (!node) {
RETURN_FALSE;
@ -1514,7 +1514,7 @@ PHP_METHOD(SimpleXMLElement, getNamespaces)
sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (node) {
if (node->type == XML_ELEMENT_NODE) {
@ -1599,7 +1599,7 @@ PHP_METHOD(SimpleXMLElement, children)
}
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (!node) {
return;
}
@ -1623,7 +1623,7 @@ PHP_METHOD(SimpleXMLElement, getName)
sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (node) {
namelen = xmlStrlen(node->name);
RETURN_STRINGL((char*)node->name, namelen);
@ -1648,7 +1648,7 @@ PHP_METHOD(SimpleXMLElement, attributes)
sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (!node) {
return;
}
@ -1689,7 +1689,7 @@ PHP_METHOD(SimpleXMLElement, addChild)
return;
}
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (node == NULL) {
php_error_docref(NULL, E_WARNING, "Cannot add child. Parent is not a permanent member of the XML tree");
@ -1749,7 +1749,7 @@ PHP_METHOD(SimpleXMLElement, addAttribute)
sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
node = php_sxe_get_first_node(sxe, node);
if (node && node->type != XML_ELEMENT_NODE) {
node = node->parent;
@ -1842,7 +1842,7 @@ static int sxe_object_cast_ex(zend_object *readobj, zval *writeobj, int type)
sxe = php_sxe_fetch_object(readobj);
if (type == _IS_BOOL) {
node = php_sxe_get_first_node_non_destructive(sxe, NULL);
node = php_sxe_get_first_node(sxe, NULL);
if (node) {
ZVAL_TRUE(writeobj);
} else {
@ -1852,7 +1852,7 @@ static int sxe_object_cast_ex(zend_object *readobj, zval *writeobj, int type)
}
if (sxe->iter.type != SXE_ITER_NONE) {
node = php_sxe_get_first_node_non_destructive(sxe, NULL);
node = php_sxe_get_first_node(sxe, NULL);
if (node) {
contents = xmlNodeListGetString((xmlDocPtr) sxe->document->ptr, node->children, 1);
}
@ -2600,7 +2600,7 @@ void *simplexml_export_node(zval *object) /* {{{ */
sxe = Z_SXEOBJ_P(object);
GET_NODE(sxe, node);
return php_sxe_get_first_node_non_destructive(sxe, node);
return php_sxe_get_first_node(sxe, node);
}
/* }}} */

View File

@ -1,92 +0,0 @@
--TEST--
Bug #55098 (SimpleXML iteration produces infinite loop)
--EXTENSIONS--
simplexml
--FILE--
<?php
$xmlString = "<root><a><b>1</b><b>2</b><b>3</b></a></root>";
$xml = simplexml_load_string($xmlString);
$nodes = $xml->a->b;
function test($nodes, $name, $callable) {
echo "--- $name ---\n";
foreach ($nodes as $nodeData) {
echo "nodeData: " . $nodeData . "\n";
$callable($nodes);
}
}
test($nodes, "asXml", fn ($n) => $n->asXml());
test($nodes, "attributes", fn ($n) => $n->attributes());
test($nodes, "children", fn ($n) => $n->children());
test($nodes, "getNamespaces", fn ($n) => $n->getNamespaces());
test($nodes, "xpath", fn ($n) => $n->xpath("/root/a/b"));
test($nodes, "var_dump", fn ($n) => var_dump($n));
test($nodes, "manipulation combined with querying", function ($n) {
$n->addAttribute("attr", "value");
(bool) $n["attr"];
$n->addChild("child", "value");
$n->outer[]->inner = "foo";
(bool) $n->outer;
(bool) $n;
isset($n->outer);
isset($n["attr"]);
unset($n->outer);
unset($n["attr"]);
unset($n->child);
});
?>
--EXPECT--
--- asXml ---
nodeData: 1
nodeData: 2
nodeData: 3
--- attributes ---
nodeData: 1
nodeData: 2
nodeData: 3
--- children ---
nodeData: 1
nodeData: 2
nodeData: 3
--- getNamespaces ---
nodeData: 1
nodeData: 2
nodeData: 3
--- xpath ---
nodeData: 1
nodeData: 2
nodeData: 3
--- var_dump ---
nodeData: 1
object(SimpleXMLElement)#3 (3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
nodeData: 2
object(SimpleXMLElement)#3 (3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
nodeData: 3
object(SimpleXMLElement)#3 (3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
--- manipulation combined with querying ---
nodeData: 1
nodeData: 2
nodeData: 3

View File

@ -41,7 +41,7 @@ foreach ($a2->b->c->children() as $key => $value) {
var_dump($value);
}?>
--EXPECT--
object(A)#4 (2) {
object(A)#2 (2) {
["@attributes"]=>
array(1) {
["attr"]=>
@ -50,7 +50,7 @@ object(A)#4 (2) {
[0]=>
string(10) "Some Value"
}
object(A)#6 (2) {
object(A)#3 (2) {
["@attributes"]=>
array(1) {
["attr"]=>

View File

@ -1,37 +0,0 @@
--TEST--
GH-12192 (SimpleXML infinite loop when getName() is called within foreach)
--EXTENSIONS--
simplexml
--FILE--
<?php
$xml = "<root><a>1</a><a>2</a></root>";
$xml = simplexml_load_string($xml);
$a = $xml->a;
foreach ($a as $test) {
echo "Iteration\n";
var_dump($a->key());
var_dump($a->getName());
var_dump((string) $test);
}
var_dump($a);
?>
--EXPECT--
Iteration
string(1) "a"
string(1) "a"
string(1) "1"
Iteration
string(1) "a"
string(1) "a"
string(1) "2"
object(SimpleXMLElement)#2 (2) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
}

View File

@ -1,26 +0,0 @@
--TEST--
GH-12208 (SimpleXML infinite loop when a cast is used inside a foreach)
--EXTENSIONS--
simplexml
--FILE--
<?php
$xml = "<root><a>1</a><a>2</a></root>";
$xml = simplexml_load_string($xml);
$a = $xml->a;
foreach ($a as $test) {
var_dump((string) $a->current());
var_dump((string) $a);
var_dump((bool) $a);
}
?>
--EXPECT--
string(1) "1"
string(1) "1"
bool(true)
string(1) "2"
string(1) "1"
bool(true)