Skip to content

mruby-regexp: convert a Bigint String#split limit - #7045

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:split-bigint-limit
Aug 9, 2026
Merged

mruby-regexp: convert a Bigint String#split limit#7045
matz merged 1 commit into
mruby:masterfrom
takumin:split-bigint-limit

Conversation

@takumin

@takumin takumin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

String#split in mruby-regexp converts its limit in mrblib
(mrbgems/mruby-regexp/mrblib/string_regexp.rb:144-146),
and the conversion is guarded so that it only runs for a limit that is not already an Integer:

if limit_given && !(Integer === limit)
  limit = Integer.__ensure(limit)
end

A Bigint is a real Integer, so it passes the guard and is never converted. The regexp path
then runs the split loop with it. The nil and String patterns do not: they delegate to the core
__split, which takes an mrb_int and raises.

big = 2 ** 70

"a,b,c".split(/,/, big)   # mruby: ["a", "b", "c"]                     CRuby: RangeError
"a,b,c".split(",",  big)  # mruby: RangeError: integer out of range    CRuby: RangeError

Two calls to one method with one argument, two answers, and the regexp one is not CRuby's.

mruby-bigint ships in math.gembox, which default.gembox includes, and mruby-regexp
comes in through stdlib.gembox, so a plain build reproduces this:

$ rake
$ ./build/host/bin/mruby -e 'big = 2**70; p "a,b,c".split(/,/, big); p "a,b,c".split(",", big)'
["a", "b", "c"]
trace (most recent call last):
	[1] -e:1
-e:1:in __split: integer out of range (RangeError)

This has the same shape as the is_a? divergence #7004 closed, on the same argument, but the
guard admits a Bigint under every spelling it has had: limit.is_a?(Integer) and
Integer === limit both answer true for one.

A Float limit is already right

Everything that is not an Integer goes through Integer.__ensure, so an out-of-range Float
never reaches the loop:

"a,b,".split(/,/, 2.0 ** 70)   # mruby: RangeError: integer out of range
"a,b,".split(",",  2.0 ** 70)  # mruby: RangeError: integer out of range
                               # CRuby: RangeError: float 1.180591621e+21 out of range of integer

Both mruby paths agree with CRuby on the class here and differ only in wording. The Bigint is
the one case the guard lets through.

The fix

-    if limit_given && !(Integer === limit)
-      limit = Integer.__ensure(limit)
-    end
+    limit = Integer.__ensure(limit) if limit_given

Integer.__ensure is mrb_ensure_int_type()
(src/numeric.c:2331-2337,
src/object.c:675-685),
which returns an Integer that fits mrb_int unchanged and narrows a Bigint through
mrb_bint_as_int(), raising RangeError when it does not fit
(mrbgems/mruby-bigint/core/bigint.c:5506-5517).
Both paths then raise, as CRuby does.

With the patch, "a,b,c".split(/,/, 2 ** 70) and "a,b,c".split(",", 2 ** 70) both raise
RangeError: integer out of range, and a negative Bigint limit raises as well.

The guard existed so that an ordinary Integer limit skipped the conversion, and it read the
type with Module#=== rather than the redefinable is_a? so that a limit could not claim to
be an Integer and skip it too. Converting unconditionally covers that case as well, so removing
the guard does not reopen #7004. The cost is one conversion call for an Integer limit that used
to skip it, and mrb_ensure_int_type() returns such a limit unchanged.

Tests

rake test covered none of this: 1967 tests, 1949 OK, 0 failures both with and without the
patch. The String#split with a Bigint limit assertions added to
mrbgems/mruby-regexp/test/regexp.rb fail on master and pass with the fix, and the suite is
then 1968 tests, 1950 OK, 0 failures. They skip when the build has no mruby-bigint, and the
exponent is a variable because a constant power out of mrb_int range fails the build rather
than raising.

Measured on 9360b3fd0 against CRuby 4.0.6 on x86_64 Linux, where mrb_int is 64 bits.

Not addressed here

A limit that fits mrb_int but not a C int is accepted by both mruby paths, where CRuby
raises:

"a,b,".split(/,/, 2 ** 40)   # mruby: ["a", "b", ""]   CRuby: RangeError: integer 1099511627776 too big to convert to 'int'
"a,b,".split(",",  2 ** 40)  # mruby: ["a", "b", ""]   CRuby: RangeError

The two paths agree with each other there, and accepting a wider limit follows from mrb_int
being the integer type of this implementation. Narrowing it to int is not proposed here.

Summary by CodeRabbit

  • Bug Fixes

    • Improved String#split handling for explicitly provided limits, including large integer values.
    • Values outside the supported range now consistently raise RangeError instead of being processed incorrectly.
  • Tests

    • Added coverage for positive and negative large-integer split limits when Bigint support is available.

`String#split` converted its `limit` only when the limit was not already an
Integer. A Bigint is an Integer, so it passed that check unconverted and the
regexp path ran the split loop with a limit that does not fit `mrb_int`. The
nil and String patterns delegate to `__split`, which takes an `mrb_int` and
raises. One argument, two answers, and the regexp one is neither CRuby's:

```ruby
big = 2 ** 70
"a,b,c".split(/,/, big)   # mruby: ["a", "b", "c"]                     CRuby: RangeError
"a,b,c".split(",",  big)  # mruby: RangeError: integer out of range    CRuby: RangeError
```

Convert every given limit through `Integer.__ensure`, which is
`mrb_ensure_int_type()`: it returns an Integer that fits `mrb_int` unchanged
and narrows a Bigint with `mrb_bint_as_int()`, raising `RangeError` when it
does not fit. Both paths now raise, as CRuby does.

The check existed so that an ordinary Integer limit skipped the conversion,
and it read the type with `Module#===` rather than the redefinable `is_a?` so
that a limit could not claim to be an Integer and skip it as well. Converting
unconditionally covers that case too, at the cost of one call for an Integer
limit that used to skip it.

A limit between `INT_MAX` and `MRB_INT_MAX` is still accepted, where CRuby
raises. That follows from `mrb_int` being the integer type of this
implementation and is left alone.
@takumin
takumin requested a review from matz as a code owner August 9, 2026 14:12
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

String#split now validates every explicit limit with Integer.__ensure. Tests cover out-of-range positive and negative Bigint limits for regexp and string patterns.

Changes

String split limit validation

Layer / File(s) Summary
Normalize and test split limits
mrbgems/mruby-regexp/mrblib/string_regexp.rb, mrbgems/mruby-regexp/test/regexp.rb
String#split normalizes all explicit limits. Tests verify RangeError for out-of-range positive and negative Bigint limits.

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

Possibly related PRs

  • mruby/mruby#7001: Both changes update String#split and its tests, but address different validation concerns.
  • mruby/mruby#7004: Both changes modify integer limit handling in String#split.
  • mruby/mruby#7006: Both changes modify String#split, but this change focuses on limit coercion.

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 identifies the main change: converting a Bigint limit 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
mrbgems/mruby-regexp/test/regexp.rb (1)

1169-1171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the negative String-pattern assertion.

The test covers limit on both pattern paths, but it covers -limit only for the regexp path. Add the symmetric assertion to protect the String-pattern behavior.

Proposed test
   assert_raise(RangeError) { "a,b,c".split(/,/, -limit) }
+  assert_raise(RangeError) { "a,b,c".split(",", -limit) }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mrbgems/mruby-regexp/test/regexp.rb` around lines 1169 - 1171, Add a
RangeError assertion beside the existing split limit tests in regexp.rb,
covering `"a,b,c".split(",", -limit)` to mirror the negative-limit regexp
assertion and verify String-pattern behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@mrbgems/mruby-regexp/test/regexp.rb`:
- Around line 1169-1171: Add a RangeError assertion beside the existing split
limit tests in regexp.rb, covering `"a,b,c".split(",", -limit)` to mirror the
negative-limit regexp assertion and verify String-pattern behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8383ebd0-2ea2-4259-802f-8ec46a185d2e

📥 Commits

Reviewing files that changed from the base of the PR and between 9360b3f and 257c657.

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

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