Commit 091cb333619 for php.net

commit 091cb333619644379ac606c38d373c45e7c2c0de
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sun Aug 30 11:14:46 2026 -0400

    [intl] Size sortWithSortKeys buffers based on array size (#23504)

    collator_sort_with_sort_keys() ecalloc'd sortKeyBuf and sortKeyIndxBuf
    at DEF_SORT_KEYS_BUF_SIZE (1MiB) each on every call regardless of array
    size. sortKeyBuf now starts from zend_hash_num_elements() * 32 bytes,
    clamped to a 4KiB minimum and the previous 1MiB cap, and grows
    geometrically up to DEF_SORT_KEYS_BUF_INCREMENT. sortKeyIndxBuf is
    allocated exactly for the element count, dropping the index-buffer
    growth path. Sibling audit: DEF_SORT_KEYS* constants have no other
    users and collator_sort()/asort()/get_sort_key() already scale
    allocations.

diff --git a/NEWS b/NEWS
index bd17cf9bf8e..ae8edaf97c9 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ PHP                                                                        NEWS
     100-continue flow control). (Sjoerd Langkemper)

 - Intl:
+  . Fixed Collator::sortWithSortKeys() allocating fixed 2MiB buffers
+    regardless of array size. (Ilia Alshanetsky)
   . Fixed a memory leak when iterating IntlBreakIterator::getPartsIterator()
     results. (iliaal)
   . Fixed a leak in Locale::getKeywords() when a keyword value cannot be
diff --git a/ext/intl/collator/collator_sort.cpp b/ext/intl/collator/collator_sort.cpp
index cb1f2aefc35..f2674b9c8ff 100644
--- a/ext/intl/collator/collator_sort.cpp
+++ b/ext/intl/collator/collator_sort.cpp
@@ -44,9 +44,8 @@ ZEND_EXTERN_MODULE_GLOBALS( intl )

 static const size_t DEF_SORT_KEYS_BUF_SIZE = 1048576;
 static const size_t DEF_SORT_KEYS_BUF_INCREMENT = 1048576;
-
-static const size_t DEF_SORT_KEYS_INDX_BUF_SIZE = 1048576;
-static const size_t DEF_SORT_KEYS_INDX_BUF_INCREMENT = 1048576;
+static const size_t MIN_SORT_KEYS_BUF_SIZE = 4096;
+static const size_t SORT_KEY_LENGTH_ESTIMATE = 32;

 static const size_t DEF_UTF16_BUF_SIZE = 1024;

@@ -427,17 +426,17 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
 	zval*       hashData             = nullptr;                     /* currently processed item of input hash */

 	char*       sortKeyBuf           = nullptr;                     /* buffer to store sort keys */
-	uint32_t    sortKeyBufSize       = DEF_SORT_KEYS_BUF_SIZE;   /* buffer size */
+	uint32_t    sortKeyBufSize       = 0;                        /* buffer size */
 	ptrdiff_t   sortKeyBufOffset     = 0;                        /* pos in buffer to store sort key */
 	uint32_t    sortKeyLen           = 0;                        /* the length of currently processing key */
 	uint32_t    bufLeft              = 0;
 	uint32_t    bufIncrement         = 0;

 	collator_sort_key_index_t* sortKeyIndxBuf = nullptr;            /* buffer to store 'indexes' which will be passed to 'qsort' */
-	uint32_t    sortKeyIndxBufSize   = DEF_SORT_KEYS_INDX_BUF_SIZE;
 	uint32_t    sortKeyIndxSize      = sizeof( collator_sort_key_index_t );

 	uint32_t    sortKeyCount         = 0;
+	uint32_t    numElements          = 0;
 	uint32_t    j                    = 0;

 	UChar*      utf16_buf            = nullptr;                     /* tmp buffer to hold current processing string in utf-16 */
@@ -472,9 +471,20 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
 	if( !hash || zend_hash_num_elements( hash ) == 0 )
 		RETURN_TRUE;

+	numElements = zend_hash_num_elements( hash );
+
+	if( numElements > DEF_SORT_KEYS_BUF_SIZE / SORT_KEY_LENGTH_ESTIMATE ) {
+		sortKeyBufSize = DEF_SORT_KEYS_BUF_SIZE;
+	} else {
+		sortKeyBufSize = numElements * SORT_KEY_LENGTH_ESTIMATE;
+	}
+	if( sortKeyBufSize < MIN_SORT_KEYS_BUF_SIZE ) {
+		sortKeyBufSize = MIN_SORT_KEYS_BUF_SIZE;
+	}
+
 	/* Create buffers */
-	sortKeyBuf     = reinterpret_cast<char *>(ecalloc( sortKeyBufSize,     sizeof( char    ) ));
-	sortKeyIndxBuf = reinterpret_cast<collator_sort_key_index_t *>(ecalloc( sortKeyIndxBufSize, sizeof( uint8_t ) ));
+	sortKeyBuf     = reinterpret_cast<char *>(ecalloc( sortKeyBufSize, sizeof( char ) ));
+	sortKeyIndxBuf = reinterpret_cast<collator_sort_key_index_t *>(ecalloc( numElements, sortKeyIndxSize ));
 	utf16_buf      = eumalloc( utf16_buf_size );

 	/* Iterate through input hash and create a sort key for each value. */
@@ -524,7 +534,15 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
 		/* check for sortKeyBuf overflow, increasing its size of the buffer if needed */
 		if( sortKeyLen > bufLeft )
 		{
-			bufIncrement = ( sortKeyLen > DEF_SORT_KEYS_BUF_INCREMENT ) ? sortKeyLen : DEF_SORT_KEYS_BUF_INCREMENT;
+			bufIncrement = sortKeyBufSize;
+
+			if( bufIncrement > DEF_SORT_KEYS_BUF_INCREMENT ) {
+				bufIncrement = DEF_SORT_KEYS_BUF_INCREMENT;
+			}
+
+			if( bufIncrement < sortKeyLen ) {
+				bufIncrement = sortKeyLen;
+			}

 			sortKeyBufSize += bufIncrement;
 			bufLeft += bufIncrement;
@@ -534,16 +552,6 @@ U_CFUNC PHP_FUNCTION( collator_sort_with_sort_keys )
 			sortKeyLen = ucol_getSortKey( co->ucoll, utf16_buf, utf16_len, (uint8_t*)sortKeyBuf + sortKeyBufOffset, bufLeft );
 		}

-		/*  check sortKeyIndxBuf overflow, increasing its size of the buffer if needed */
-		if( ( sortKeyCount + 1 ) * sortKeyIndxSize > sortKeyIndxBufSize )
-		{
-			bufIncrement = ( sortKeyIndxSize > DEF_SORT_KEYS_INDX_BUF_INCREMENT ) ? sortKeyIndxSize : DEF_SORT_KEYS_INDX_BUF_INCREMENT;
-
-			sortKeyIndxBufSize += bufIncrement;
-
-			sortKeyIndxBuf = reinterpret_cast<collator_sort_key_index_t *>(erealloc( sortKeyIndxBuf, sortKeyIndxBufSize ));
-		}
-
 		sortKeyIndxBuf[sortKeyCount].key = (char*)sortKeyBufOffset;    /* remember just offset, cause address */
 		                                                               /* of 'sortKeyBuf' may be changed due to realloc. */
 		sortKeyIndxBuf[sortKeyCount].zstr = hashData;
diff --git a/ext/intl/tests/collator_sort_with_sort_keys_buffer_size.phpt b/ext/intl/tests/collator_sort_with_sort_keys_buffer_size.phpt
new file mode 100644
index 00000000000..ef1d68851e3
--- /dev/null
+++ b/ext/intl/tests/collator_sort_with_sort_keys_buffer_size.phpt
@@ -0,0 +1,57 @@
+--TEST--
+Collator::sortWithSortKeys() buffer allocation scales with array size
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+$c = new Collator('en_US');
+$a = ['bb', 'aa', 'dd', 'cc'];
+$c->sort($a);
+
+$before = memory_get_peak_usage();
+$b = ['bb', 'aa', 'cc', 'ab', 'ca', 'bc', 'ac', 'ba'];
+$c->sortWithSortKeys($b);
+$peakDelta = memory_get_peak_usage() - $before;
+
+var_dump($a);
+var_dump($b);
+var_dump($peakDelta < 100000);
+
+$long = str_repeat('a', 10000);
+$d = [$long . 'b', $long . 'a'];
+$c->sortWithSortKeys($d);
+echo $d[0] === $long . 'a' ? "long-a\n" : "fail-a\n";
+echo $d[1] === $long . 'b' ? "long-b\n" : "fail-b\n";
+?>
+--EXPECT--
+array(4) {
+  [0]=>
+  string(2) "aa"
+  [1]=>
+  string(2) "bb"
+  [2]=>
+  string(2) "cc"
+  [3]=>
+  string(2) "dd"
+}
+array(8) {
+  [0]=>
+  string(2) "aa"
+  [1]=>
+  string(2) "ab"
+  [2]=>
+  string(2) "ac"
+  [3]=>
+  string(2) "ba"
+  [4]=>
+  string(2) "bb"
+  [5]=>
+  string(2) "bc"
+  [6]=>
+  string(2) "ca"
+  [7]=>
+  string(2) "cc"
+}
+bool(true)
+long-a
+long-b