Commit d319e4a214b for php

commit d319e4a214bc1ed5949a08c401038b7b24b21598
Merge: 2460f3d2988 16a62e36773
Author: Weilin Du <weilindu@php.net>
Date:   Fri Oct 2 01:16:14 2026 +0800

    Merge branch 'PHP-8.4' into PHP-8.5

    * PHP-8.4:
      ext/intl: Fix grapheme_strstr() and grapheme_strpos() missing canonically equivalent matches (#24029)

diff --cc ext/intl/grapheme/grapheme_string.c
index f0fa069c7d5,6a9233b6b6a..64aa62d7458
--- a/ext/intl/grapheme/grapheme_string.c
+++ b/ext/intl/grapheme/grapheme_string.c
@@@ -554,10 -550,11 +556,12 @@@ static void strstr_common_handler(INTER
  		Z_PARAM_STRING(needle, needle_len)
  		Z_PARAM_OPTIONAL
  		Z_PARAM_BOOL(part)
 +		Z_PARAM_PATH(locale, locale_len)
  	ZEND_PARSE_PARAMETERS_END();

- 	if ( !f_ignore_case ) {
+ 	if ( !f_ignore_case
+ 			&& grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0
+ 			&& grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0 ) {

  		/* ASCII optimization: quick check to see if the string might be there */
  		found = php_memnstr(haystack, needle, needle_len, haystack + haystack_len);
diff --cc ext/intl/tests/grapheme_search_empty_haystack.phpt
index 00000000000,b8b36295fae..6f3df10649c
mode 000000,100644..100644
--- a/ext/intl/tests/grapheme_search_empty_haystack.phpt
+++ b/ext/intl/tests/grapheme_search_empty_haystack.phpt
@@@ -1,0 -1,55 +1,53 @@@
+ --TEST--
+ Grapheme searches handle an empty haystack without ICU errors
+ --EXTENSIONS--
+ intl
 ---INI--
 -intl.error_level=2
+ --FILE--
+ <?php
+ foreach ([0, 1] as $useExceptions) {
+     ini_set('intl.use_exceptions', (string) $useExceptions);
+     foreach (['grapheme_strpos', 'grapheme_stripos', 'grapheme_strrpos',
+               'grapheme_strripos', 'grapheme_strstr', 'grapheme_stristr'] as $function) {
+         $results = [];
+         foreach (['a', "\u{00E9}", "e\u{0301}", "\r\n", ''] as $needle) {
+             if ($function === 'grapheme_strstr' || $function === 'grapheme_stristr') {
+                 $results[] = [$function('', $needle), $function('', $needle, true)];
+             } else {
+                 $results[] = $function('', $needle);
+             }
+         }
+         echo $function, ': ', json_encode($results), ', error: ', intl_get_error_code(), "\n";
+     }
+ }
+
+ foreach (['grapheme_strpos', 'grapheme_stripos', 'grapheme_strrpos', 'grapheme_strripos'] as $function) {
+     foreach ([-1, 1] as $offset) {
+         try {
+             $function('', "\u{00E9}", $offset);
+         } catch (ValueError $e) {
+             echo $function, '(', $offset, '): ', $e::class, "\n";
+         }
+     }
+ }
+ ?>
+ --EXPECT--
+ grapheme_strpos: [false,false,false,false,0], error: 0
+ grapheme_stripos: [false,false,false,false,0], error: 0
+ grapheme_strrpos: [false,false,false,false,0], error: 0
+ grapheme_strripos: [false,false,false,false,0], error: 0
+ grapheme_strstr: [[false,false],[false,false],[false,false],[false,false],["",""]], error: 0
+ grapheme_stristr: [[false,false],[false,false],[false,false],[false,false],["",""]], error: 0
+ grapheme_strpos: [false,false,false,false,0], error: 0
+ grapheme_stripos: [false,false,false,false,0], error: 0
+ grapheme_strrpos: [false,false,false,false,0], error: 0
+ grapheme_strripos: [false,false,false,false,0], error: 0
+ grapheme_strstr: [[false,false],[false,false],[false,false],[false,false],["",""]], error: 0
+ grapheme_stristr: [[false,false],[false,false],[false,false],[false,false],["",""]], error: 0
+ grapheme_strpos(-1): ValueError
+ grapheme_strpos(1): ValueError
+ grapheme_stripos(-1): ValueError
+ grapheme_stripos(1): ValueError
+ grapheme_strrpos(-1): ValueError
+ grapheme_strrpos(1): ValueError
+ grapheme_strripos(-1): ValueError
+ grapheme_strripos(1): ValueError
diff --cc ext/intl/tests/grapheme_strpos_ascii_haystack.phpt
index 00000000000,f54c369e2db..43d6fa6b24b
mode 000000,100644..100644
--- a/ext/intl/tests/grapheme_strpos_ascii_haystack.phpt
+++ b/ext/intl/tests/grapheme_strpos_ascii_haystack.phpt
@@@ -1,0 -1,56 +1,55 @@@
+ --TEST--
+ grapheme_strpos() uses Unicode matching for non-ASCII needles in ASCII haystacks
+ --EXTENSIONS--
+ intl
+ --INI--
+ intl.use_exceptions=1
 -intl.error_level=2
+ --FILE--
+ <?php
+ foreach ([
+     ['K', "\u{212A}", 0],
+     ['aKbK', "\u{212A}", 0],
+     ['aKbK', "\u{212A}", 1],
+     ['aKbK', "\u{212A}", 2],
+     ['aKbK', "\u{212A}", 3],
+     ['aKbK', "\u{212A}", 4],
+     ['aKbK', "\u{212A}", -4],
+     ['aKbK', "\u{212A}", -2],
+     ['abc', "b\u{00AD}", 0],
+     ['abc', "\u{00E9}", 0],
+     ['abc', 'b', 1],
+     ['abc', 'b', 2],
+     ['', "\u{00E9}", 0],
+     ['', "e\u{0301}", 0],
+     ['', "\r\n", 0],
+     ['', '', 0],
+     ['abc', '', 3],
+ ] as [$haystack, $needle, $offset]) {
+     var_dump(grapheme_strpos($haystack, $needle, $offset));
+ }
+
+ try {
+     grapheme_strpos('', "\u{212A}", 1);
+ } catch (ValueError $e) {
+     echo $e->getMessage(), "\n";
+ }
+ ?>
+ --EXPECT--
+ int(0)
+ int(1)
+ int(1)
+ int(3)
+ int(3)
+ bool(false)
+ int(1)
+ int(3)
+ int(1)
+ bool(false)
+ int(1)
+ bool(false)
+ bool(false)
+ bool(false)
+ bool(false)
+ int(0)
+ int(3)
+ grapheme_strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)