mruby-regexp: close a class range at the first codepoint of a \u{...} list - #7263
Conversation
`compile_charclass` guarded the range emission with `cp <= hi` and had
no `else`, so a range written backwards was dropped without a word. A
positive class then silently lacked the span, and a negated one admitted
every character. CRuby raises `RegexpError` for either:
```ruby
Regexp.new("[b-a]") # CRuby: RegexpError (empty range in char class), mruby: compiles, matches nothing
Regexp.new("[^b-a]") =~ "x" # CRuby: RegexpError, mruby: 0
Regexp.new("[\\u{62}-\\u{61}]") # CRuby: RegexpError, mruby: compiles
Regexp.new("[\\u{62 63}-a]") # CRuby: RegexpError, mruby: compiles to `b`
```
The bare `[b-a]` is old behavior, but `\u` made such a range easy to
write without the letters showing it, and the loss is the same. Raise
"empty range in char class" with CRuby's wording once both ends are
read. The comparison is sound where it runs: the byte-versus-character
check just before it leaves no pair of a byte and a character above
ASCII, and ASCII sits below either kind, so the two numbers order the
ends.
…}` list
`read_class_atom` handed every `\u{...}` list over the same way: all but
the last codepoint joined the class as members and the last was
returned, so it could open a range. That is right before a `-`, where
`[\u{61 62}-z]` is `a` plus `b-z`, and wrong after one, where CRuby
closes the range with the codepoint next to the `-`, the first of the
list, and takes the rest as members. The class held a different set in
either direction:
```ruby
/[a-\u{63 7a}]/ =~ "m" # CRuby: nil, mruby: 0 (`a-z` plus `c` instead of `a-c` plus `z`)
/[a-\u{7a 41}]/ =~ "a" # CRuby: 0, mruby: nil (`a` down to `A`, dropped, and `z` alone)
Regexp.new("[b-\\u{61 63}]") # CRuby: RegexpError (empty range in char class), mruby: `a-c`
```
`read_class_atom` now takes whether the atom closes a range. For a list
that does, the first codepoint is returned as the end and the rest join
the class; a list that does not keeps the old order. `[a-\u{7a 41}]` is
`a-z` plus `A`, and `[b-\u{61 63}]` is reported as the empty range it
is. On a build reading a String by byte the end is still the last byte
of the codepoint's spelling, as it is for any `\u` in a class there.
|
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 (4)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 1 remains after this review. 📝 WalkthroughWalkthroughChangesRegexp character-class range handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This localized change corrects reversed character-class ranges and Unicode range bounds, with regression coverage and passing test suites; no actionable merge-blocking risk remains beyond normal checks and review. Sequence Diagram(s)sequenceDiagram
participant Pattern
participant RegexpCompiler
participant CharacterClass
Pattern->>RegexpCompiler: provide character-class pattern
RegexpCompiler->>CharacterClass: parse Unicode-list members and range endpoints
RegexpCompiler->>RegexpCompiler: validate range direction
RegexpCompiler-->>Pattern: compile class or raise RegexpError
Possibly related PRs
Suggested labels: 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 |
Two defects at the range step of
compile_charclassinmrbgems/mruby-regexp/src/re_compile.c, fixed together because the second is not fully observable without the first: with the range end read from the wrong codepoint,[b-\u{61 63}]compiles tob-cand no reversed range is ever seen there.A reversed range was dropped without a word. The emission was guarded by
cp <= hiwith noelse, so a positive class silently lacked the span and a negated one admitted every character. CRuby raises for either.A
\u{...}list closing a range was bound at its last codepoint.read_class_atomhanded every list over the same way, all but the last codepoint as members and the last returned, which is right before a-([\u{61 62}-z]isaplusb-z) and wrong after one, where CRuby closes the range with the codepoint next to the-and takes the rest as members. The class held a different set in either direction.Fix
cp > hiraisesRegexpErrorwith CRuby's wording,empty range in char class: /.../. The comparison is sound where it runs: that check leaves no pair of a byte and a character above ASCII, and ASCII sits below either kind.read_class_atomtakes acloses_rangeflag. For a\u{...}list that closes a range the first codepoint is returned as the end and the rest join the class as members; a list that does not keeps the old order (last codepoint returned). On a build reading a String by byte the end is still the last byte of that codepoint's spelling, as for any\uin a class there.\u{...}lists now states both sides of the-and that a reversed range raises.Both
[a-\u{61}]and[\u{61}-\u{61}](a range of one) still compile,[-a]and[a-]still take-as a member, and the byte-versus-character rule for\xends is untouched.Testing
regexp_syntax.rb: new blockreversed character class range([b-a],[^b-a]with the exact message,[xz-ay], and the non-cases[a-a],[-a],[a-]).regexp_utf8.rb: new blocksreversed character class range through \uanda \u list closing a character class range; the ASCII\urows run on every build, the rows with codepoints above ASCII ([\u{3044}-\u{3042}],[\u{100}-\u{FF}],[\u{3042}-\u{3044 3046}],[\u{3044}-\u{3046 3042}]) under__ENCODING__ == "UTF-8", since on a byte-read build the ends are the last bytes of the spellings and their order is not the codepoints' order. The two existing list blocks gain a HI-side row[a-\u{63 7a}].bin/mrubyandruby.Full suite green at every commit (
MRUBY_CONFIG=ci/gcc-clang rake -m testat both commits, defaultrake -m testat the tip afterrm -rf build/host):rake -m test)At the first commit the totals are one lower on each of the five builds (2352 / 2352 / 2352 / 2282 / 2349), KO 0, Crash 0.
Environment
Machine, toolchain, and the compile line of every build
Actual compile line of
src/string.cin eachbuild_config/ci/gcc-clang.rbbuild (-MMD -c,-I, and-odropped).full-debugis-O0becauseenable_debugappends-g3 -O0after the toolchain's-g -O3;cxx_abicompiles C as C++ withgcc -x c++ -std=gnu++03, g++ only links.Summary by CodeRabbit
Bug Fixes
\u{...}codepoint lists.RegexpErrorinstead of being silently ignored.Documentation