Skip to content

[Grapheme] Reject out-of-range offsets in the grapheme_str[ri]?pos() family - #641

Merged
nicolas-grekas merged 1 commit into
1.xfrom
grapheme-position-offset-range
Jul 28, 2026
Merged

[Grapheme] Reject out-of-range offsets in the grapheme_str[ri]?pos() family#641
nicolas-grekas merged 1 commit into
1.xfrom
grapheme-position-offset-range

Conversation

@nicolas-grekas

Copy link
Copy Markdown
Member
Q A
Branch? 1.x
Bug fix? yes
New feature? no
Deprecations? no
Issues -
License MIT

ext-intl requires the offset of grapheme_strpos(), grapheme_stripos(), grapheme_strrpos() and grapheme_strripos() to be contained in the haystack, and throws a ValueError on PHP >= 8 otherwise:

grapheme_strpos('abc', 'b', 4);   // ValueError: Argument #3 ($offset) must be contained in argument #1 ($haystack)
grapheme_strpos('abc', 'b', -4);  // ValueError, same message

The polyfill clamped the offset while cutting the haystack instead, so out-of-range values silently returned either false or, for negative offsets far enough from the end, a match found in the whole haystack:

Grapheme::grapheme_strpos('abc', 'b', 4);    // false
Grapheme::grapheme_strpos('abc', 'b', -10);  // 1

The valid range is -$len to $len inclusive, $len being the length of the haystack in grapheme units. Out of it, this PR throws the same ValueError as ext-intl on PHP >= 8, and returns false on PHP 7 like ext-intl did there. In-range behaviour is untouched.

This is what made symfony/string return a position where ext-intl makes UnicodeString::indexOf() return null: symfony/symfony#64992 relies on catching that ValueError, so its new test cases fail on the x86 / minimal-exts / lowest-php job, where intl is missing.

The added test covers the four functions on both implementations. Note the calls have to be literal, since a variable function call bypasses the namespaced functions TestListenerTrait installs and would only ever exercise ext-intl.

…family

ext-intl requires the offset to be contained in the haystack: it throws a
ValueError on PHP >= 8 when the offset is greater than the length of the
haystack in grapheme units, or lower than its negation. The polyfill instead
clamped the offset while cutting the haystack, so out-of-range values were
either reported as "not found" or, for negative offsets far enough from the
end, searched the whole haystack and returned a match.

This is how symfony/string ended up returning a position where ext-intl makes
UnicodeString::indexOf() return null.
@nicolas-grekas
nicolas-grekas force-pushed the grapheme-position-offset-range branch from 915a498 to ed3813b Compare July 28, 2026 08:26
@nicolas-grekas
nicolas-grekas merged commit d660c89 into 1.x Jul 28, 2026
38 of 40 checks passed
@nicolas-grekas
nicolas-grekas deleted the grapheme-position-offset-range branch July 28, 2026 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants