Commit 1adadf07e52 for php.net
commit 1adadf07e5223b43612afe25e082fe33dd456830
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Mon Aug 24 11:12:35 2026 -0400
[intl] Fix empty-needle grapheme_strpos offsets
grapheme_strpos_utf16() returned raw UTF-16 code-unit positions for an
empty needle instead of grapheme counts, so multi-code-unit graphemes
made strpos() and strrpos() over-report the offset. Convert the boundary
position like the non-empty search path; the ASCII fast paths coincide
and grapheme_strstr() consumes the separate raw UTF-16 out-parameter,
which stays correct.
Closes GH-23519
diff --git a/NEWS b/NEWS
index 927ebe121e8..ee0cf1b9c1d 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,8 @@ PHP NEWS
. Fixed bug GH-19320 (FPM UID and GID overflow). (Pratik Bhujel)
- Intl:
+ . Fixed grapheme_strpos() and grapheme_strrpos() with an empty needle
+ returning UTF-16 offsets instead of grapheme offsets. (Ilia Alshanetsky)
. Fixed a memory leak when dumping IntlCalendar instances. (Ilia Alshanetsky)
. Fixed a memory leak when iterating IntlBreakIterator::getPartsIterator()
results. (iliaal)
diff --git a/ext/intl/grapheme/grapheme_util.c b/ext/intl/grapheme/grapheme_util.c
index 501b9dfb221..f0c79785483 100644
--- a/ext/intl/grapheme/grapheme_util.c
+++ b/ext/intl/grapheme/grapheme_util.c
@@ -131,7 +131,11 @@ int32_t grapheme_strpos_utf16(char *haystack, size_t haystack_len, char *needle,
ret_pos = -1;
goto finish;
}
- ret_pos = last && offset >= 0 ? uhaystack_len : offset_pos;
+ if (last && offset >= 0) {
+ ret_pos = grapheme_count_graphemes(bi, uhaystack, uhaystack_len);
+ } else {
+ ret_pos = grapheme_count_graphemes(bi, uhaystack, offset_pos);
+ }
goto finish;
}
diff --git a/ext/intl/tests/grapheme_empty_offset_multibyte.phpt b/ext/intl/tests/grapheme_empty_offset_multibyte.phpt
new file mode 100644
index 00000000000..caa545c882c
--- /dev/null
+++ b/ext/intl/tests/grapheme_empty_offset_multibyte.phpt
@@ -0,0 +1,36 @@
+--TEST--
+grapheme_strpos() family with empty needle and offset on multi-code-unit graphemes
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+
+ini_set("intl.error_level", E_WARNING);
+
+var_dump(grapheme_strpos("😀x", ""));
+var_dump(grapheme_strpos("😀x", "", 0));
+var_dump(grapheme_strpos("😀x", "", 1));
+var_dump(grapheme_stripos("😀x", "", 1));
+var_dump(grapheme_strpos("😀x", "", -1));
+var_dump(grapheme_strrpos("😀x", ""));
+var_dump(grapheme_strrpos("😀x", "", 1));
+var_dump(grapheme_strripos("😀x", "", 1));
+var_dump(grapheme_strrpos("😀x", "", -1));
+try {
+ var_dump(grapheme_strpos("😀x", "", 5));
+} catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+int(0)
+int(0)
+int(1)
+int(1)
+int(1)
+int(2)
+int(2)
+int(2)
+int(1)
+ValueError: grapheme_strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack)