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

Arnaud Le Blanc <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Arnaud Le Blanc (arnaud-lb)
Date: 2026-08-11T19:01:51+02:00

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

Merge branch 'PHP-8.5'

* PHP-8.5:
  Add a stack limit check in php_count_recursive() (#23197)

Changed paths:
  A  ext/standard/tests/array/count_recursive_stack_limit.phpt
  M  ext/spl/spl_observer.c
  M  ext/standard/array.c
  M  ext/standard/php_array.h


Diff:

diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c
index adfc172af982..a980828cdcb2 100644
--- a/ext/spl/spl_observer.c
+++ b/ext/spl/spl_observer.c
@@ -747,7 +747,11 @@ PHP_METHOD(SplObjectStorage, count)
 	}
 
 	if (mode == PHP_COUNT_RECURSIVE) {
-		RETURN_LONG(php_count_recursive(&intern->storage));
+		zend_long count = php_count_recursive(&intern->storage);
+		if (UNEXPECTED(count < 0)) {
+			RETURN_THROWS();
+		}
+		RETURN_LONG(count);
 	}
 
 	RETURN_LONG(zend_hash_num_elements(&intern->storage));
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 91d3932516eb..648b48479099 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -560,10 +560,18 @@ PHPAPI zend_long php_count_recursive(HashTable *ht) /* {{{ */
 	zend_long cnt = 0;
 	zval *element;
 
+#ifdef ZEND_CHECK_STACK_LIMIT
+	if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+		zend_call_stack_size_error();
+		return -1;
+	}
+#endif
+
 	if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) {
 		if (GC_IS_RECURSIVE(ht)) {
 			php_error_docref(NULL, E_WARNING, "Recursion detected");
-			return 0;
+			/* A user error handler may have thrown. */
+			return EG(exception) ? -1 : 0;
 		}
 		GC_PROTECT_RECURSION(ht);
 	}
@@ -572,7 +580,12 @@ PHPAPI zend_long php_count_recursive(HashTable *ht) /* {{{ */
 	ZEND_HASH_FOREACH_VAL(ht, element) {
 		ZVAL_DEREF(element);
 		if (Z_TYPE_P(element) == IS_ARRAY) {
-			cnt += php_count_recursive(Z_ARRVAL_P(element));
+			zend_long sub_cnt = php_count_recursive(Z_ARRVAL_P(element));
+			if (UNEXPECTED(sub_cnt < 0)) {
+				cnt = -1;
+				break;
+			}
+			cnt += sub_cnt;
 		}
 	} ZEND_HASH_FOREACH_END();
 
@@ -617,6 +630,9 @@ PHP_FUNCTION(count)
 				cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
 			} else {
 				cnt = php_count_recursive(Z_ARRVAL_P(array));
+				if (UNEXPECTED(cnt < 0)) {
+					RETURN_THROWS();
+				}
 			}
 			RETURN_LONG(cnt);
 		case IS_OBJECT: {
diff --git a/ext/standard/php_array.h b/ext/standard/php_array.h
index ab6c8494dd4e..a7e8afdec34b 100644
--- a/ext/standard/php_array.h
+++ b/ext/standard/php_array.h
@@ -27,6 +27,7 @@ PHPAPI int php_array_merge(HashTable *dest, HashTable *src);
 PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src);
 PHPAPI int php_array_replace_recursive(HashTable *dest, HashTable *src);
 PHPAPI int php_multisort_compare(const void *a, const void *b);
+/* Returns -1 and throws if the array is nested too deeply. */
 PHPAPI zend_long php_count_recursive(HashTable *ht);
 
 PHPAPI bool php_array_data_shuffle(php_random_algo_with_state engine, zval *array);
diff --git a/ext/standard/tests/array/count_recursive_stack_limit.phpt b/ext/standard/tests/array/count_recursive_stack_limit.phpt
new file mode 100644
index 000000000000..a7f3918ba350
--- /dev/null
+++ b/ext/standard/tests/array/count_recursive_stack_limit.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Stack overflow in count() with COUNT_RECURSIVE and deeply nested arrays
+--SKIPIF--
+<?php
+if (ini_get('zend.max_allowed_stack_size') === false) {
+    die('skip No stack limit support');
+}
+if (getenv('SKIP_ASAN')) {
+    die('skip ASAN needs different stack limit setting due to more stack space usage');
+}
+?>
+--INI--
+zend.max_allowed_stack_size=256K
+--FILE--
+<?php
+/* Two elements per nesting level: the sibling must not be visited once the
+ * stack limit error has been thrown, so only one Error is thrown. */
+$a = [];
+for ($i = 0; $i < 30000; $i++) {
+    $a = [$a, []];
+}
+
+try {
+    count($a, COUNT_RECURSIVE);
+} catch (Throwable $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+    var_dump($e->getPrevious());
+}
+?>
+--EXPECTF--
+Error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
+NULL
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.