[php-src] PHP-8.4: ext/intl: Fix grapheme_extract() next offset (#24160)

From: 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

« previous php.cvs (#142068) next »