[php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6
| From: | Weilin Du | Date: | Thu, 01 Oct 2026 17:21:02 +0000 |
| Subject: | [php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6 | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-141844@lists.php.net to get a copy of this message | ||
Author: Weilin Du (LamentXU123)
Date: 2026-10-02T01:19:44+08:00
Commit: https://github.com/php/php-src/commit/724599cee57ec84a757eba8b21ac53f3772b93b5
Raw diff: https://github.com/php/php-src/commit/724599cee57ec84a757eba8b21ac53f3772b93b5.diff
Merge branch 'PHP-8.5' into PHP-8.6
* PHP-8.5:
Merge branch 'PHP-8.4' into PHP-8.5
* codex/pr24029-php85:
ext/intl: Fix grapheme_strstr() and grapheme_strpos() missing canonically equivalent matches
(#24029)
Changed paths:
A ext/intl/tests/grapheme_search_empty_haystack.phpt
A ext/intl/tests/grapheme_strpos_ascii_haystack.phpt
A ext/intl/tests/grapheme_strstr_canonical_match.phpt
M ext/intl/grapheme/grapheme_string.cpp
M ext/intl/grapheme/grapheme_util.cpp
Diff:
diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp
index e2a6280bcaa6..acb7c50cb9eb 100644
--- a/ext/intl/grapheme/grapheme_string.cpp
+++ b/ext/intl/grapheme/grapheme_string.cpp
@@ -125,7 +125,9 @@ U_CFUNC PHP_FUNCTION(grapheme_strpos)
if (found) {
RETURN_LONG(found - haystack);
}
- RETURN_FALSE;
+ if (grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0) {
+ RETURN_FALSE;
+ }
}
/* do utf16 part of the strpos */
@@ -563,7 +565,9 @@ static void strstr_common_handler(INTERNAL_FUNCTION_PARAMETERS, int f_ignore_cas
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);
@@ -573,15 +577,12 @@ static void strstr_common_handler(INTERNAL_FUNCTION_PARAMETERS, int
f_ignore_cas
RETURN_FALSE;
}
- /* if it is there, and if the haystack is ascii, we are all done */
- if ( grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0 ) {
- size_t found_offset = found - haystack;
+ size_t found_offset = found - haystack;
- if (part) {
- RETURN_STRINGL(haystack, found_offset);
- } else {
- RETURN_STRINGL(found, haystack_len - found_offset);
- }
+ if (part) {
+ RETURN_STRINGL(haystack, found_offset);
+ } else {
+ RETURN_STRINGL(found, haystack_len - found_offset);
}
}
diff --git a/ext/intl/grapheme/grapheme_util.cpp b/ext/intl/grapheme/grapheme_util.cpp
index e58510e011ca..9644df7f3591 100644
--- a/ext/intl/grapheme/grapheme_util.cpp
+++ b/ext/intl/grapheme/grapheme_util.cpp
@@ -114,6 +114,9 @@ U_CFUNC int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char
if(puchar_pos) {
*puchar_pos = -1;
}
+ if (haystack_len == 0 && needle_len > 0) {
+ return -1;
+ }
/* convert the strings to UTF-16. */
status = U_ZERO_ERROR;
diff --git a/ext/intl/tests/grapheme_search_empty_haystack.phpt
b/ext/intl/tests/grapheme_search_empty_haystack.phpt
new file mode 100644
index 000000000000..6f3df10649c6
--- /dev/null
+++ b/ext/intl/tests/grapheme_search_empty_haystack.phpt
@@ -0,0 +1,53 @@
+--TEST--
+Grapheme searches handle an empty haystack without ICU errors
+--EXTENSIONS--
+intl
+--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 --git a/ext/intl/tests/grapheme_strpos_ascii_haystack.phpt
b/ext/intl/tests/grapheme_strpos_ascii_haystack.phpt
new file mode 100644
index 000000000000..43d6fa6b24b4
--- /dev/null
+++ b/ext/intl/tests/grapheme_strpos_ascii_haystack.phpt
@@ -0,0 +1,55 @@
+--TEST--
+grapheme_strpos() uses Unicode matching for non-ASCII needles in ASCII haystacks
+--EXTENSIONS--
+intl
+--INI--
+intl.use_exceptions=1
+--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)
diff --git a/ext/intl/tests/grapheme_strstr_canonical_match.phpt
b/ext/intl/tests/grapheme_strstr_canonical_match.phpt
new file mode 100644
index 000000000000..96d8f6334b1a
--- /dev/null
+++ b/ext/intl/tests/grapheme_strstr_canonical_match.phpt
@@ -0,0 +1,27 @@
+--TEST--
+grapheme_strstr() finds canonically equivalent strings with different byte representations
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+foreach ([
+ ["e\u{0301}x", "\u{00E9}"],
+ ["\u{00E9}x", "e\u{0301}"],
+ ["pe\u{0301}x", "\u{00E9}"],
+ ["pe\u{0301}x\u{00E9}", "\u{00E9}"],
+ ['abc', 'b'],
+ ['abc', 'z'],
+] as [$haystack, $needle]) {
+ echo json_encode([
+ grapheme_strstr($haystack, $needle),
+ grapheme_strstr($haystack, $needle, true),
+ ]), "\n";
+}
+?>
+--EXPECT--
+["e\u0301x",""]
+["\u00e9x",""]
+["e\u0301x","p"]
+["e\u0301x\u00e9","p"]
+["bc","a"]
+[false,false]