Skip to content

Commit a10bce8

Browse files
LamentXU123devnexen
andcommitted
ext/intl: Fix grapheme_extract() next offset
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>
1 parent 09c3e73 commit a10bce8

3 files changed

Lines changed: 45 additions & 3 deletions

File tree

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ PHP NEWS
8484
haystacks. (Weilin Du)
8585
. Fixed grapheme_strrpos() and grapheme_strripos() skipping overlapping matches
8686
when using a negative offset. (Weilin Du)
87+
. Fixed grapheme_extract() returning an incorrect next offset when starting
88+
inside a multibyte UTF-8 character. (Weilin Du, David Carlier)
8789

8890
- Lexbor:
8991
. Merge patches lexbor/lexbor@8a14bc0 and lexbor/lexbor@f67ce4b, fixing a

‎ext/intl/grapheme/grapheme_string.c‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,8 @@ PHP_FUNCTION(grapheme_extract)
786786
}
787787
}
788788

789-
str_len -= (pstr - str);
789+
lstart = pstr - str;
790+
str_len -= lstart;
790791

791792
/* if the string is all ASCII up to size+1 - or str_len whichever is first - then we are done.
792793
(size + 1 because the size-th character might be the beginning of a grapheme cluster)
@@ -795,7 +796,7 @@ PHP_FUNCTION(grapheme_extract)
795796
if ( -1 != grapheme_ascii_check((unsigned char *)pstr, MIN(size + 1, str_len)) ) {
796797
size_t nsize = MIN(size, str_len);
797798
if ( NULL != next ) {
798-
ZEND_TRY_ASSIGN_REF_LONG(next, start + nsize);
799+
ZEND_TRY_ASSIGN_REF_LONG(next, lstart + nsize);
799800
}
800801
RETURN_STRINGL(pstr, nsize);
801802
}
@@ -829,7 +830,7 @@ PHP_FUNCTION(grapheme_extract)
829830
ubrk_close(bi);
830831

831832
if ( NULL != next ) {
832-
ZEND_TRY_ASSIGN_REF_LONG(next, start + ret_pos);
833+
ZEND_TRY_ASSIGN_REF_LONG(next, lstart + ret_pos);
833834
}
834835

835836
RETURN_STRINGL(((char *)pstr), ret_pos);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
grapheme_extract() includes skipped UTF-8 continuation bytes in the next offset
3+
--EXTENSIONS--
4+
intl
5+
--FILE--
6+
<?php
7+
8+
$cases = [
9+
["\u{00E9}x", 1, 1, GRAPHEME_EXTR_COUNT],
10+
["\u{1F600}x", 1, 1, GRAPHEME_EXTR_COUNT],
11+
["\u{1F600}x", 2, 1, GRAPHEME_EXTR_COUNT],
12+
["\u{1F600}x", 3, 1, GRAPHEME_EXTR_COUNT],
13+
["\u{00E9}\u{1F600}x", 1, 1, GRAPHEME_EXTR_COUNT],
14+
["\u{00E9}\u{1F600}x", 1, 4, GRAPHEME_EXTR_MAXBYTES],
15+
["\u{00E9}\u{1F600}x", 1, 1, GRAPHEME_EXTR_MAXCHARS],
16+
["\u{1F600}\u{00E9}x", -4, 1, GRAPHEME_EXTR_COUNT],
17+
["\u{00E9}x", -2, 1, GRAPHEME_EXTR_COUNT],
18+
["\u{1F600}x", 0, 1, GRAPHEME_EXTR_COUNT],
19+
["\u{00E9}x", 1, 0, GRAPHEME_EXTR_COUNT],
20+
];
21+
22+
foreach ($cases as [$string, $start, $size, $type]) {
23+
$result = grapheme_extract($string, $size, $type, $start, $next);
24+
echo bin2hex($result), ' ', $next, "\n";
25+
}
26+
27+
?>
28+
--EXPECT--
29+
78 3
30+
78 5
31+
78 5
32+
78 5
33+
f09f9880 6
34+
f09f9880 6
35+
f09f9880 6
36+
c3a9 6
37+
78 3
38+
f09f9880 4
39+
1

0 commit comments

Comments
 (0)