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)