[php-src] master: Merge branch 'PHP-8.5'

David Carlier <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: David Carlier (devnexen)
Date: 2026-07-12T23:33:13+01:00

Commit: https://github.com/php/php-src/commit/0a5edc51c04f53701a5a0432b7c6e54dcf4b57e9
Raw diff: https://github.com/php/php-src/commit/0a5edc51c04f53701a5a0432b7c6e54dcf4b57e9.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  ext/dom: getElementsByClassName() item() returns wrong element on random access.

Changed paths:
  A  ext/dom/tests/modern/common/Element_getElementsByClassName_item_random_access.phpt
  M  ext/dom/obj_map.c


Diff:

diff --git a/ext/dom/obj_map.c b/ext/dom/obj_map.c
index aa852c800473..4d6479e003f9 100644
--- a/ext/dom/obj_map.c
+++ b/ext/dom/obj_map.c
@@ -345,22 +345,20 @@ static void dom_map_get_by_class_name_item(dom_nnodemap_object *map, zend_long i
 	if (nodep && index >= 0) {
 		dom_node_idx_pair start_point = dom_obj_map_get_start_point(map, nodep, index);
 		if (start_point.node) {
-			if (start_point.index > 0) {
-				/* Only start iteration at next point if we actually have an index to seek to. */
-				itemnode = php_dom_next_in_tree_order(start_point.node, nodep);
-			} else {
-				itemnode = start_point.node;
-			}
+			itemnode = start_point.node;
 		} else {
 			itemnode = php_dom_first_child_of_container_node(nodep);
+			while (itemnode != NULL && !dom_matches_class_name(map, itemnode)) {
+				itemnode = php_dom_next_in_tree_order(itemnode, nodep);
+			}
 		}
 
-		do {
-			--start_point.index;
+		for (; start_point.index > 0 && itemnode != NULL; --start_point.index) {
+			itemnode = php_dom_next_in_tree_order(itemnode, nodep);
 			while (itemnode != NULL && !dom_matches_class_name(map, itemnode)) {
 				itemnode = php_dom_next_in_tree_order(itemnode, nodep);
 			}
-		} while (start_point.index > 0 && itemnode);
+		}
 	}
 	dom_ret_node_to_zobj(map, itemnode, return_value);
 	if (itemnode) {
diff --git a/ext/dom/tests/modern/common/Element_getElementsByClassName_item_random_access.phpt b/ext/dom/tests/modern/common/Element_getElementsByClassName_item_random_access.phpt
new file mode 100644
index 000000000000..005ad45d3c25
--- /dev/null
+++ b/ext/dom/tests/modern/common/Element_getElementsByClassName_item_random_access.phpt
@@ -0,0 +1,84 @@
+--TEST--
+Dom\Element::getElementsByClassName() item() random access (cold and backwards)
+--EXTENSIONS--
+dom
+--FILE--
+<?php
+
+/* Regression: the item() lookup must return the n-th match for random access,
+ * not only for a strictly ascending / foreach walk. A fresh collection whose
+ * first access is item(n) exercises the uncached path, and a decreasing index
+ * on the same collection exercises the cache-discard path. Non-matching and
+ * nested elements verify that iteration advances to the next matching element
+ * in tree order, not merely to the next sibling. */
+
+$dom = Dom\HTMLDocument::createFromString(<<<HTML
+<!DOCTYPE html>
+<body>
+    <span class="x" id="E0"></span>
+    <div class="y" id="skip1"><span class="x" id="E1"></span></div>
+    <span class="x" id="E2"></span>
+    <p class="z"><b class="x" id="E3"></b></p>
+    <span class="x" id="E4"></span>
+</body>
+HTML);
+
+$body = $dom->getElementsByTagName('body')->item(0);
+
+function id(?Dom\Element $e): string {
+    return $e === null ? 'NULL' : $e->id;
+}
+
+echo "-- cold random access (fresh collection per call) --\n";
+foreach ([0, 1, 2, 3, 4, 5] as $i) {
+    $collection = $body->getElementsByClassName('x');
+    echo "item($i) = ", id($collection->item($i)), "\n";
+}
+
+echo "-- backwards seek on one collection --\n";
+$collection = $body->getElementsByClassName('x');
+foreach ([4, 2, 0, 3, 1] as $i) {
+    echo "item($i) = ", id($collection->item($i)), "\n";
+}
+
+echo "-- item() seed then foreach --\n";
+$collection = $body->getElementsByClassName('x');
+$collection->item(3);
+$ids = [];
+foreach ($collection as $node) {
+    $ids[] = $node->id;
+}
+echo implode(" ", $ids), "\n";
+
+echo "-- last-element idiom --\n";
+$collection = $body->getElementsByClassName('x');
+echo "length = ", $collection->length, ", last = ", id($collection->item($collection->length - 1)), "\n";
+
+echo "-- live collection after mutation --\n";
+$collection = $body->getElementsByClassName('x');
+echo "item(1) = ", id($collection->item(1)), "\n";
+$dom->getElementById('E1')->remove();
+echo "item(1) = ", id($collection->item(1)), ", length = ", $collection->length, "\n";
+
+?>
+--EXPECT--
+-- cold random access (fresh collection per call) --
+item(0) = E0
+item(1) = E1
+item(2) = E2
+item(3) = E3
+item(4) = E4
+item(5) = NULL
+-- backwards seek on one collection --
+item(4) = E4
+item(2) = E2
+item(0) = E0
+item(3) = E3
+item(1) = E1
+-- item() seed then foreach --
+E0 E1 E2 E3 E4
+-- last-element idiom --
+length = 5, last = E4
+-- live collection after mutation --
+item(1) = E1
+item(1) = E2, length = 4
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.