mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Fix GH-13863: Removal during NodeList iteration breaks loop
The list is live, so upon cache invalidation we should rewalk the tree to sync the index again with the node list. We keep the legacy behaviour for the old DOM classes. Closes GH-13934.
This commit is contained in:
@@ -188,8 +188,19 @@ static void php_dom_iterator_move_forward(zend_object_iterator *iter) /* {{{ */
|
||||
} else {
|
||||
if (objmap->nodetype == XML_ATTRIBUTE_NODE ||
|
||||
objmap->nodetype == XML_ELEMENT_NODE) {
|
||||
curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node;
|
||||
curnode = curnode->next;
|
||||
|
||||
/* Note: keep legacy behaviour for non-spec mode. */
|
||||
if (php_dom_follow_spec_intern(intern) && php_dom_is_cache_tag_stale_from_doc_ptr(&iterator->cache_tag, intern->document)) {
|
||||
php_dom_mark_cache_tag_up_to_date_from_doc_ref(&iterator->cache_tag, intern->document);
|
||||
curnode = dom_fetch_first_iteration_item(objmap);
|
||||
zend_ulong index = 0;
|
||||
while (curnode != NULL && index++ < iter->index) {
|
||||
curnode = curnode->next;
|
||||
}
|
||||
} else {
|
||||
curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node;
|
||||
curnode = curnode->next;
|
||||
}
|
||||
} else {
|
||||
/* The collection is live, we nav the tree from the base object if we cannot
|
||||
* use the cache to restart from the last point. */
|
||||
|
||||
@@ -262,6 +262,13 @@ static zend_always_inline void php_dom_mark_cache_tag_up_to_date_from_node(php_l
|
||||
}
|
||||
}
|
||||
|
||||
static zend_always_inline void php_dom_mark_cache_tag_up_to_date_from_doc_ref(php_libxml_cache_tag *cache_tag, php_libxml_ref_obj *document)
|
||||
{
|
||||
ZEND_ASSERT(cache_tag != NULL);
|
||||
ZEND_ASSERT(document != NULL);
|
||||
cache_tag->modification_nr = document->cache_tag.modification_nr;
|
||||
}
|
||||
|
||||
static zend_always_inline bool php_dom_follow_spec_node(const xmlNode *node)
|
||||
{
|
||||
ZEND_ASSERT(node != NULL);
|
||||
|
||||
164
ext/dom/tests/gh13863.phpt
Normal file
164
ext/dom/tests/gh13863.phpt
Normal file
@@ -0,0 +1,164 @@
|
||||
--TEST--
|
||||
GH-13863 (Removal during NodeList iteration breaks loop)
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
enum DomType {
|
||||
case Legacy;
|
||||
case Modern;
|
||||
}
|
||||
|
||||
function createTestDoc(DomType $type) {
|
||||
$xml = <<<EOXML
|
||||
<x>
|
||||
<item1 />
|
||||
<item2 />
|
||||
<item3 />
|
||||
<item4 />
|
||||
<item5 />
|
||||
</x>
|
||||
EOXML;
|
||||
|
||||
if ($type === DomType::Legacy) {
|
||||
$doc = new DOMDocument();
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->loadXML($xml);
|
||||
} else {
|
||||
$doc = DOM\XMLDocument::createFromString($xml, LIBXML_NOBLANKS);
|
||||
}
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
foreach ([DomType::Legacy, DomType::Modern] as $case) {
|
||||
$name = match ($case) {
|
||||
DomType::Legacy => 'Legacy',
|
||||
DomType::Modern => 'Modern',
|
||||
};
|
||||
|
||||
echo "--- $name test remove index 2 at index 2 ---\n";
|
||||
|
||||
$doc = createTestDoc($case);
|
||||
|
||||
foreach ($doc->documentElement->childNodes as $i => $node) {
|
||||
var_dump($doc->documentElement->childNodes->item($i) === $node);
|
||||
var_dump($i, $node->localName);
|
||||
if ($i === 2) {
|
||||
$node->remove();
|
||||
}
|
||||
}
|
||||
|
||||
echo $doc->saveXML(), "\n";
|
||||
|
||||
echo "--- $name test remove index 1 at index 2 ---\n";
|
||||
|
||||
$doc = createTestDoc($case);
|
||||
|
||||
foreach ($doc->documentElement->childNodes as $i => $node) {
|
||||
var_dump($doc->documentElement->childNodes->item($i) === $node);
|
||||
var_dump($i, $node->localName);
|
||||
if ($i === 2) {
|
||||
$doc->documentElement->childNodes[1]->remove();
|
||||
}
|
||||
}
|
||||
|
||||
echo $doc->saveXML(), "\n";
|
||||
|
||||
echo "--- $name test remove all ---\n";
|
||||
|
||||
$doc = createTestDoc($case);
|
||||
|
||||
foreach ($doc->documentElement->childNodes as $i => $node) {
|
||||
var_dump($doc->documentElement->childNodes->item($i) === $node);
|
||||
var_dump($i, $node->localName);
|
||||
$node->remove();
|
||||
}
|
||||
|
||||
echo $doc->saveXML(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
--- Legacy test remove index 2 at index 2 ---
|
||||
bool(true)
|
||||
int(0)
|
||||
string(5) "item1"
|
||||
bool(true)
|
||||
int(1)
|
||||
string(5) "item2"
|
||||
bool(true)
|
||||
int(2)
|
||||
string(5) "item3"
|
||||
<?xml version="1.0"?>
|
||||
<x><item1/><item2/><item4/><item5/></x>
|
||||
|
||||
--- Legacy test remove index 1 at index 2 ---
|
||||
bool(true)
|
||||
int(0)
|
||||
string(5) "item1"
|
||||
bool(true)
|
||||
int(1)
|
||||
string(5) "item2"
|
||||
bool(true)
|
||||
int(2)
|
||||
string(5) "item3"
|
||||
bool(false)
|
||||
int(3)
|
||||
string(5) "item4"
|
||||
bool(false)
|
||||
int(4)
|
||||
string(5) "item5"
|
||||
<?xml version="1.0"?>
|
||||
<x><item1/><item3/><item4/><item5/></x>
|
||||
|
||||
--- Legacy test remove all ---
|
||||
bool(true)
|
||||
int(0)
|
||||
string(5) "item1"
|
||||
<?xml version="1.0"?>
|
||||
<x><item2/><item3/><item4/><item5/></x>
|
||||
|
||||
--- Modern test remove index 2 at index 2 ---
|
||||
bool(true)
|
||||
int(0)
|
||||
string(5) "item1"
|
||||
bool(true)
|
||||
int(1)
|
||||
string(5) "item2"
|
||||
bool(true)
|
||||
int(2)
|
||||
string(5) "item3"
|
||||
bool(true)
|
||||
int(3)
|
||||
string(5) "item5"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<x><item1/><item2/><item4/><item5/></x>
|
||||
--- Modern test remove index 1 at index 2 ---
|
||||
bool(true)
|
||||
int(0)
|
||||
string(5) "item1"
|
||||
bool(true)
|
||||
int(1)
|
||||
string(5) "item2"
|
||||
bool(true)
|
||||
int(2)
|
||||
string(5) "item3"
|
||||
bool(true)
|
||||
int(3)
|
||||
string(5) "item5"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<x><item1/><item3/><item4/><item5/></x>
|
||||
--- Modern test remove all ---
|
||||
bool(true)
|
||||
int(0)
|
||||
string(5) "item1"
|
||||
bool(true)
|
||||
int(1)
|
||||
string(5) "item3"
|
||||
bool(true)
|
||||
int(2)
|
||||
string(5) "item5"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<x><item2/><item4/></x>
|
||||
Reference in New Issue
Block a user