Skip to content

mruby-regexp: String#split steps by a byte through a binary subject - #7085

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-split-binary
Aug 10, 2026
Merged

mruby-regexp: String#split steps by a byte through a binary subject#7085
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-split-binary

Conversation

@takumin

@takumin takumin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

An empty match leaves split where it started, so the loop steps forward
before searching again. The step read the rest of the subject as UTF-8 and
cleared a whole character. A string marked by String#b has one position per
byte and __byte_match finds an empty match at every one of them, so the
fields came back joined.

"\u{1F600}".b.split(//)  # ["\xF0\x9F\x98\x80"]    <- CRuby: ["\xF0", "\x9F", "\x98", "\x80"]
"a\u{E9}b".b.split(//)   # ["a", "\xC3\xA9", "b"]  <- CRuby: ["a", "\xC3", "\xA9", "b"]
"\u{1F600}".b.split("")  # ["\xF0", "\x9F", "\x98", "\x80"]   the core split, all along

The same subject came apart two ways depending on which spelling of the same
request was used.

The change

split reads the flag off the subject with Regexp.__binary_string? and
steps by a byte, which is what gsub a few lines above already does at the
same point and for the same reason. Nothing else in the loop moves: a subject
read as UTF-8 still steps by a character, and the limit still counts fields.

Why the step cannot read it off the slice

The step asks byteslice for the rest of the subject and takes its first
element. byteslice builds that string with mrb_str_byte_subseq(), which
copies MRB_STR_SINGLE_BYTE and not MRB_STR_BINARY, so the slice arrives
unmarked and its first element is a whole character again however the subject
was marked. Reading the flag off the subject asks nothing of the slice.
Whether a slice of a binary string should itself be binary is a question about
src/string.c and is left alone here.

Testing

rake test is green on full-core with gcc on x86_64-linux, with and
without MRB_REGEXP_UNICODE_CASE: 2215 and 2214 asserts, no failures. The new
test fails without the change:

Fail: String#split - a byte-indexed subject is split by byte
  KO: 1   ->   KO: 0

Checked against CRuby 4.0.6, including the limit forms split(//, 2) and
split(//, -1), an empty subject, and an ASCII subject marked by String#b.

Summary by CodeRabbit

  • Bug Fixes

    • Improved String#split behavior with zero-length regular-expression matches.
    • Binary strings are now split byte by byte, while UTF-8 and other text strings continue to split by character.
    • Corrected handling at the end of strings and when a maximum field count is specified.
  • Tests

    • Added coverage for binary-string splitting, UTF-8 character splitting, and field-count limits.

An empty match leaves `split` where it started, so the loop steps forward
before searching again. The step read the rest of the subject as UTF-8 and
cleared a whole character. A string marked by `String#b` has one position per
byte and `__byte_match` finds an empty match at every one of them, so the
fields came back joined.

```ruby
"\u{1F600}".b.split(//)  # was ["\xF0\x9F\x98\x80"],   CRuby: ["\xF0", "\x9F", "\x98", "\x80"]
"a\u{E9}b".b.split(//)   # was ["a", "\xC3\xA9", "b"], CRuby: ["a", "\xC3", "\xA9", "b"]
```

The step asks `byteslice` for the rest of the subject and takes its first
element, and `byteslice` hands back a string that carries no `MRB_STR_BINARY`,
so that element is a whole character again however the subject was marked.

Read the flag off the subject with `Regexp.__binary_string?` and step by a
byte, which is what `gsub` already does at the same point and for the same
reason. A subject read as UTF-8 still steps by a character, and the limit
still counts fields.

`split("")` asks the same thing of the core implementation, which counted
bytes for such a string all along, so the two spellings agree again.
@takumin
takumin requested a review from matz as a code owner August 10, 2026 16:56
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4263ef7b-74f1-4c68-8c85-b5ae8ec54286

📥 Commits

Reviewing files that changed from the base of the PR and between 7184392 and 2f5b5ef.

📒 Files selected for processing (2)
  • mrbgems/mruby-regexp/mrblib/string_regexp.rb
  • mrbgems/mruby-regexp/test/regexp.rb

📝 Walkthrough

Walkthrough

String#split now handles zero-length regexp matches according to the subject encoding. Binary strings advance by one byte, while non-binary strings retain character-aware advancement. Tests cover UTF-8, binary, and limited splits.

Changes

Encoding-aware split advancement

Layer / File(s) Summary
Encoding-aware split advancement
mrbgems/mruby-regexp/mrblib/string_regexp.rb, mrbgems/mruby-regexp/test/regexp.rb
String#split detects binary subjects and advances zero-length matches by one byte. Non-binary subjects use character-aware advancement with end-of-string handling. Tests cover byte-wise binary splitting, character-wise UTF-8 splitting, and positive limit field counts.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change to byte-wise stepping for binary subjects in String#split.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matz
matz merged commit 11c39d9 into mruby:master Aug 10, 2026
21 checks passed
@takumin
takumin deleted the regexp-split-binary branch August 10, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants