mruby-regexp: correct regexp offset conversion for UTF-8 and binary strings#6915
Conversation
There was a problem hiding this comment.
Code Review
This pull request adds support for binary (ASCII-8BIT) strings in the mruby regexp engine by introducing a binary flag to the execution functions and bypassing UTF-8 decoding when processing binary strings. Feedback on the changes suggests capping byte_off to the string length in re_byte_to_char to prevent potential out-of-bounds memory reads, and adding a fast-path check in re_char_to_byte to avoid scanning the string when the character offset is clearly out of bounds.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| struct RString *s = RSTRING(str); | ||
| if (RSTR_SINGLE_BYTE_P(s) || RSTR_BINARY_P(s)) return byte_off; | ||
|
|
||
| const char *p = RSTR_PTR(s); |
There was a problem hiding this comment.
To prevent potential out-of-bounds memory reads, we should defensively cap byte_off to the actual string length len before iterating through the string bytes. If byte_off is larger than len, it could lead to reading past the allocated buffer of the string.
struct RString *s = RSTRING(str);
if (RSTR_SINGLE_BYTE_P(s) || RSTR_BINARY_P(s)) return byte_off;
mrb_int len = RSTR_LEN(s);
if (byte_off > len) byte_off = len;
const char *p = RSTR_PTR(s);| if (char_off < 0) { | ||
| mrb_int char_len = re_byte_to_char(mrb, str, len); | ||
| char_off += char_len; | ||
| if (char_off < 0) return -1; | ||
| } |
There was a problem hiding this comment.
For positive out-of-bounds offsets where char_off > len, we can immediately return -1 without scanning the entire string. Since a UTF-8 character is at least 1 byte, the character length of a string can never exceed its byte length. Adding this fast-path check avoids an O(N) scan of the string when the offset is clearly out of bounds.
if (char_off < 0) {
mrb_int char_len = re_byte_to_char(mrb, str, len);
char_off += char_len;
if (char_off < 0) return -1;
}
else if (char_off > len) {
return -1;
}There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a7c5ab620c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (captures[1] == captures[0]) { | ||
| if (captures[1] < slen) { | ||
| int clen = mrb_re_utf8_charlen(s + captures[1], s + slen); | ||
| int clen = mrb_re_charlen(s + captures[1], s + slen, binary); |
There was a problem hiding this comment.
Keep block gsub zero-width advancement byte-based for binary
This binary-aware zero-width advance only fixes the replacement-string C path. When the subject is forced ASCII-8BIT and the pattern can match empty (// or /(?=.)/), the block implementation still advances in mrbgems/mruby-regexp/mrblib/string_regexp.rb with rest[0], which returns the whole UTF-8 character even for the forced-binary string; e.g. bin.gsub(//) { '-' } skips the 0xA9 byte while bin.gsub(//, '-') now advances byte-by-byte. Please mirror this byte-vs-UTF-8 step in the block path so the two String#gsub forms stay consistent for binary strings.
Useful? React with 👍 / 👎.
|
Addressed three points:
|
Summary
This PR follows up on the review feedback from #6913 and fixes regexp offset conversion and binary string handling in
mruby-regexp.The main fixes are:
Regexp#=~whenMRB_UTF8_STRINGis enabledASCII-8BIT/BINARYstringsMRB_UTF8_STRINGbuildsDetails
Return character offsets from
Regexp#=~In
MRB_UTF8_STRINGbuilds,Regexp#=~now converts the internal byte offset to a public character offset before returning it.Without
MRB_UTF8_STRING, the existing byte-offset behavior is preserved.Keep
Regexp#match(str, pos)semantics unchangedRegexp#match(str, pos)andRegexp#match?(str, pos)keep the same public semantics. This PR fixes the internal position conversion so thatASCII-8BIT/BINARYstrings keep byte-indexedposvalues instead of being converted as UTF-8 character offsets.Normal non-binary strings in
MRB_UTF8_STRINGbuilds continue to use character offsets, and non-MRB_UTF8_STRINGbuilds continue to use byte offsets.Pass binary string state into the regexp engine
mrb_re_execnow receives whether the subject string is binary. This allows the regexp engine to choose the correct advancement behavior:ASCII-8BIT/BINARYstrings: advance by one byteThis applies to regexp execution paths including:
.matchinggsubSmall internal helpers were added to centralize the byte-vs-UTF-8 behavior:
mrb_re_charlenmrb_re_decode_charmrb_re_utf8_continuation_pAffected behavior
The table below summarizes the effective units used by the affected APIs and regexp behavior.
Bold entries indicate behavior fixed by this PR.
MRB_UTF8_STRINGMRB_UTF8_STRINGnormal non-binary stringsmruby-encoding+ASCII-8BIT/BINARYstringsRegexp#=~return valueRegexp#match(str, pos)posRegexp#match?(str, pos)posMatchData#begin(n)/#end(n)./ character class advancementString#scan(/./)String#sub(/./)/String#gsub(/./)Note that “UTF-8 regexp character” here means the unit used by the regexp engine while matching, such as how far
.advances. It is different from public character offsets returned by APIs such asRegexp#=~orMatchData#begin.For normal non-binary strings, the existing regexp engine behavior is preserved. For example,
\xC3\xA9continues to be treated as one regexp character by..For forced
ASCII-8BIT/BINARYstrings, regexp matching now advances byte-by-byte.CRuby compatibility
Compatibility was checked against CRuby
4.0.4.The checked cases include:
ASCII-8BIT/BINARYstringsposbehaviorIn the checked cases, the
mruby-encodingbuild now matches CRuby behavior.Representative examples:
Tests
Added tests covering three behavior groups:
MRB_UTF8_STRINGbuilds and existing byte-offset behaviorASCII-8BIT/BINARYstrings and byte-by-byte regexp advancementVerification
Successfully across three configurations:
MRB_UTF8_STRING, nomruby-encodingMRB_UTF8_STRINGenabled, nomruby-encodingMRB_UTF8_STRINGenabled withmruby-encoding