mruby-regexp: fold a byte-indexed subject by ASCII under a /i backreference - #7264
Conversation
…eference A backreference under `/i` compares the captured span and the text at the cursor through `memcmp_ci`, which folds both sides with `mrb_re_case_fold`. For a byte-indexed subject (`String#b`) `mrb_re_decode_char` hands out the raw byte, and on a build with the Unicode table that byte was folded as the codepoint of the same value: 0xC0 became 0xE0 the way U+00C0 folds to U+00E0, so two bytes that spell no letter in common compared equal. ```ruby "\xC0\xE0".b =~ /(.)\1/i # CRuby: nil, mruby: 0 "\xC0a\xE0A".b =~ /(..)\1/i # CRuby: nil, mruby: 0 ``` A byte is not the character of the same value, and the letters a byte can spell are the ASCII ones. `memcmp_ci` now folds through `subject_fold`, which returns a byte above 127 unchanged when the subject is byte-indexed and folds everything else as before, so `"aA".b` and `"\xC0\xC0".b` still match `/(.)\1/i` at 0 and the two bytes above do not. A subject read as characters is untouched: `"sſ" =~ /(.)\1/i` still folds U+017F to `s`. The class side of the same report (`"\xE5".b =~ /[\xC5]/i`) was already answered: `class_match` keeps a byte member apart from the codepoint of the same number, so a byte-indexed subject reaches only the byte half of a class. The backreference was the one path that folded a subject at match time.
|
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)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 0 remain after this review. 📝 WalkthroughWalkthroughChangesBinary Subject Case Folding
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to This localized change prevents incorrect case-insensitive backreference matches for byte-indexed strings while preserving existing ASCII and character-string behavior. No actionable merge-blocking risk remains after normal checks and review. 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 |
A backreference under
/ifolded a byte-indexed subject (String#b) as if each byte were the codepoint of the same value. On a build with the Unicode case table 0xC0 folded to 0xE0 the way U+00C0 folds to U+00E0, so two bytes that spell no letter in common compared equal:memcmp_ciinre_exec.ccompares the captured span and the text at the cursor by folding both sides withmrb_re_case_fold.mrb_re_decode_charhands out the raw byte for a byte-indexed subject, and the fold took that byte for a codepoint. The class side of the same report ("\xE5".b =~ /[\xC5]/i) was already answered byclass_match, which keeps a byte member apart from the codepoint of the same number; the backreference was the one path that still folded a subject at match time.Fix
memcmp_cifolds through a newsubject_fold(c, binary), which returns a byte above 127 unchanged when the subject is byte-indexed and folds everything else as before. The letters a byte can spell are the ASCII ones, and those still fold; a subject read as characters is untouched. Builds without the Unicode table were never affected (their fold leaves 0xC0 alone), so the fix changes nothing there.Testing
New test
Regexp - a backreference under /i folds a byte-indexed subject by ASCIIinmrbgems/mruby-regexp/test/regexp_utf8.rb. The byte-indexed lines hold on every build and run unconditionally; the contrast with a subject read as characters ("sſ" =~ /(.)\1/ifolds U+017F tos,"sſ".bdoes not) sits in anif __ENCODING__ == "UTF-8"branch rather than behind askip, so a failure in the lines before it is not swallowed on the byte build. The test fails onmaster(Expected 0 to be nil) and passes with the fix.Full suite green at every commit (one commit).
MRUBY_CONFIG=ci/gcc-clang)rake -m test, noMRUBY_CONFIG)One divergence from CRuby is left alone, since it is not about byte-indexed subjects: CRuby answers
"ſs" =~ /(.)\1/iwithnilwhile"sſ" =~ /(.)\1/iis0, because Onigmo requires the remaining subject to hold at least the captured span's byte length before it folds. mruby answers0for both orders.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
Tests