mruby-regexp: reject an empty group name - #7021
Merged
Merged
Conversation
`compile_atom()` accepted `(?<>` as a named capture whose name is zero
bytes long. mruby registered it and the group became reachable through
every named capture API under the name `""`.
```ruby
re = Regexp.new("(?<>x)") # CRuby: RegexpError (group name is empty)
re.named_captures # mruby: {"" => 1}
re.match("x")[""] # mruby: "x"
```
The backreference form followed once such a group existed:
```ruby
Regexp.new("(?<>x)\k<>").match("xx") # mruby: matches CRuby: RegexpError
```
`\k<>` on its own already raised, but as `undefined group name reference`
rather than for the empty name, and it stopped raising as soon as the
pattern had an empty named group to resolve to.
Reject the empty name where each is parsed, with CRuby's message.
This also closes a latent pointer problem below it. `mrb_re_compile()`
copies capture names into an arena the regexp owns, guarded on the total
length of all names. For a pattern whose named groups were all empty
named that total was 0, the arena was never allocated, and every
`named_captures[i].name` kept pointing into `c.stripped`, which the
function frees before returning:
```ruby
Regexp.new("(?<>x) ", Regexp::EXTENDED).named_captures # mruby: {"" => 1}
```
Nothing dereferenced it, since every reader passed the matching
`name_len` of 0, so it was a latent condition rather than a fault. With
no empty name reaching registration, the guard is zero only when there
are no named captures at all, which is what its comment says.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughRegexp compilation now raises ChangesRegexp validation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
This was referenced Aug 9, 2026
This was referenced Aug 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
compile_atom()accepts(?<>as a named capture whose name is zero bytes long.CRuby rejects an empty group name at compile time; mruby registers it, and the
group is then reachable through every named capture API under the name
"".The backreference form goes the same way once such a group exists:
\k<>on its own raises in mruby, but for the wrong reason: it resolves to nogroup and falls out of the range check as
undefined group name reference. Itstops raising as soon as the pattern has an empty named group to resolve to,
which is the case above.
Cause
The scan in the
(?<name>branch stops immediately when the next byte is>,cap_name_lenis 0, and nothing tests it.cap_nameis non-NULL, so theregistration block stores the entry like any other. The
\k<name>branch scansthe same way.
The arena guard leaves a dangling pointer
mrb_re_compile()copies capture names into an arena the regexp owns, becauseuntil that point they point into the pattern source, which in
/xmode is abuffer the function frees before returning. The copy is guarded on the total
length of all names, so for a pattern whose named groups are all empty named,
totalis 0, the arena is never allocated, and everypat->named_captures[i].namekeeps pointing atc.stripped:Nothing dereferences it today, because every reader passes the matching
name_lenof 0 tomrb_str_new()ormemcmp(), so this is a latent conditionrather than a reproducible fault. Rejecting the empty name in the parser closes
it:
totalis now zero only when there are no named captures at all, which iswhat the guard's comment says.
Change
Two checks, one in each branch, using CRuby's
group name is emptymessage:(?<=...)and(?<!...)are taken by the lookbehind branch, which testsc->p[2], so neither reaches the new check.Testing
rake testpasses: 1943 tests, 0 failures, 0 crashes. Behaviour checked againstCRuby 4.0.6.
Summary by CodeRabbit
Bug Fixes
RegexpError.Tests