[php-src] PHP-8.4: ext/intl: Fix grapheme_extract() next offset (#24160)
| From: | Weilin Du via GitHub | Date: | Wed, 07 Oct 2026 16:00:46 +0000 |
| Subject: | [php-src] PHP-8.4: ext/intl: Fix grapheme_extract() next offset (#24160) | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142068@lists.php.net to get a copy of this message | ||
Author: Weilin Du (LamentXU123)
Committer: GitHub (web-flow)
Pusher: LamentXU123
Date: 2026-10-08T00:00:42+08:00
Commit: https://github.com/php/php-src/commit/6aff7e384f3c605457f25b74993a9d2d7d6174c1
Raw diff: https://github.com/php/php-src/commit/6aff7e384f3c605457f25b74993a9d2d7d6174c1.diff
ext/intl: Fix grapheme_extract() next offset (#24160)
When the starting position lies inside a multibyte UTF-8 character,
grapheme_extract() advances the cursor but reports a next offset based on
the original position. Derive the adjusted offset from the cursor for both
extraction paths.
Add regression coverage for extraction modes, ASCII and Unicode tails,
and negative starting offsets, and document the fix in NEWS.
Co-authored-by: David Carlier <devnexen@gmail.com>
Changed paths:
A ext/intl/tests/grapheme_extract_next_offset.phpt
M NEWS
M ext/intl/grapheme/grapheme_string.c
Diff:
diff --git a/NEWS b/NEWS
index 999d1f6fc5ae..64c94bbbebd2 100644
--- a/NEWS
+++ b/NEWS
@@ -108,6 +108,8 @@ PHP NEWS
haystacks. (Weilin Du)
. Fixed grapheme_strrpos() and grapheme_strripos() skipping overlapping matches
when using a negative offset. (Weilin Du)
+ . Fixed grapheme_extract() returning an incorrect next offset when starting
+ inside a multibyte UTF-8 character. (Weilin Du, David Carlier)
- Lexbor:
. Merge patches lexbor/lexbor@8a14bc0 and lexbor/lexbor@f67ce4b, fixing a
diff --git a/ext/intl/grapheme/grapheme_string.c b/ext/intl/grapheme/grapheme_string.c
index 6a9233b6b6a6..50e40cffc015 100644
--- a/ext/intl/grapheme/grapheme_string.c
+++ b/ext/intl/grapheme/grapheme_string.c
@@ -786,7 +786,8 @@ PHP_FUNCTION(grapheme_extract)
}
}
- str_len -= (pstr - str);
+ lstart = pstr - str;
+ str_len -= lstart;
/* if the string is all ASCII up to size+1 - or str_len whichever is first - then we are done.
(size + 1 because the size-th character might be the beginning of a grapheme cluster)
@@ -795,7 +796,7 @@ PHP_FUNCTION(grapheme_extract)
if ( -1 != grapheme_ascii_check((unsigned char *)pstr, MIN(size + 1, str_len)) ) {
size_t nsize = MIN(size, str_len);
if ( NULL != next ) {
- ZEND_TRY_ASSIGN_REF_LONG(next, start + nsize);
+ ZEND_TRY_ASSIGN_REF_LONG(next, lstart + nsize);
}
RETURN_STRINGL(pstr, nsize);
}
@@ -829,7 +830,7 @@ PHP_FUNCTION(grapheme_extract)
ubrk_close(bi);
if ( NULL != next ) {
- ZEND_TRY_ASSIGN_REF_LONG(next, start + ret_pos);
+ ZEND_TRY_ASSIGN_REF_LONG(next, lstart + ret_pos);
}
RETURN_STRINGL(((char *)pstr), ret_pos);
diff --git a/ext/intl/tests/grapheme_extract_next_offset.phpt
b/ext/intl/tests/grapheme_extract_next_offset.phpt
new file mode 100644
index 000000000000..07221d5b8950
--- /dev/null
+++ b/ext/intl/tests/grapheme_extract_next_offset.phpt
@@ -0,0 +1,39 @@
+--TEST--
+grapheme_extract() includes skipped UTF-8 continuation bytes in the next offset
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+
+$cases = [
+ ["\u{00E9}x", 1, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{1F600}x", 1, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{1F600}x", 2, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{1F600}x", 3, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{00E9}\u{1F600}x", 1, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{00E9}\u{1F600}x", 1, 4, GRAPHEME_EXTR_MAXBYTES],
+ ["\u{00E9}\u{1F600}x", 1, 1, GRAPHEME_EXTR_MAXCHARS],
+ ["\u{1F600}\u{00E9}x", -4, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{00E9}x", -2, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{1F600}x", 0, 1, GRAPHEME_EXTR_COUNT],
+ ["\u{00E9}x", 1, 0, GRAPHEME_EXTR_COUNT],
+];
+
+foreach ($cases as [$string, $start, $size, $type]) {
+ $result = grapheme_extract($string, $size, $type, $start, $next);
+ echo bin2hex($result), ' ', $next, "\n";
+}
+
+?>
+--EXPECT--
+78 3
+78 5
+78 5
+78 5
+f09f9880 6
+f09f9880 6
+f09f9880 6
+c3a9 6
+78 3
+f09f9880 4
+ 1