mruby-regexp: document what a pattern still decides in the String overrides - #7067
Conversation
|
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 (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe change expands documentation for Regexp validation, Ruby-level method dispatch, singleton overrides, CRuby differences, and ChangesRegexp Override Documentation
Estimated code review effort: 1 (Trivial) | ~5 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 |
d7406f4 to
eb65911
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In `@mrbgems/mruby-regexp/mrblib/string_regexp.rb`:
- Around line 1-4: Revise the introductory validation and reachability comments
to cover only the overrides that actually call Regexp.__check_pattern. Document
that String#=~ directly rejects String, while String#[], String#[]=, and
String#slice! first use a Regexp === guard; also note that __check_pattern
accepts String patterns, which may reach later dispatch after compilation or
quoting.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9ffd6ce1-3c17-49c9-8093-e7824365cf64
📒 Files selected for processing (1)
mrbgems/mruby-regexp/mrblib/string_regexp.rb
…rrides
The overrides in `mrbgems/mruby-regexp/mrblib/string_regexp.rb` take care not
to let a pattern argument lie about its type, and the comments say so:
`Regexp.__check_pattern` makes the accept-or-reject decision in C, and `[]`,
`[]=` and `slice!` read the real class with `Regexp ===` before taking the
regexp path at all. What the comments leave unsaid is that the searches those
overrides then perform are ordinary sends to the pattern and to the MatchData
it hands back, so a singleton method on the pattern changes the answer:
```ruby
r = Regexp.new("l+")
def r.match(*a); "PWNED"; end
"hello".sub(r) { |m| m.upcase } # CRuby: "heLLo", mruby: NoMethodError (pre_match)
"hello"[r] # CRuby: "ll", mruby: "P"
s = "hello"; s[r] = "X"; s # CRuby: "heXo", mruby: NoMethodError (begin)
"hello".slice!(r) # CRuby: "ll", mruby: NoMethodError (begin)
```
`gsub` and `split` reach the pattern through `__byte_match` rather than
`match`, so the same rewrite there gives them a `NoMethodError` on
`__byte_begin`; `scan`, `sub` and `gsub` with a replacement string go through
`__scan`, `__sub_str` and `__gsub_str`. The `__` prefix is a naming
convention, not a protection.
CRuby dispatches `match` from `String#match` on purpose and answers `"PWNED"`
there too, but `rb_str_sub_bang()`, `rb_str_subpat()`, `rb_str_subpat_set()`
and `rb_str_slice_bang()` call `rb_reg_search()` directly, and `String#match?`
and `String#=~` search a real Regexp without asking it anything.
Reaching any of this takes rewriting a method on a Regexp instance and then
handing that instance to a String method. An argument that is not a Regexp
never gets as far as the searches, because the type test in front of them
cannot be steered. The gem accepts the wider surface rather than giving every
override a C entry point that searches without dispatching, so record that
decision in a note at the top of the file, and stop the type-test comment in
`String#[]` from reading as though accepting the argument settled everything
the pattern can still influence.
eb65911 to
8be3a94
Compare
String#match? and String#=~ instead of asking the argument
takumin/mruby#54
The overrides in
mrbgems/mruby-regexp/mrblib/string_regexp.rbtake care not to let anargument lie about its type:
Regexp === patternreads the real class, andRegexp.__check_patternmakes the accept-or-reject decision in C so that no Ruby-sidehelper can be swapped out under it. Having established what the object is, every override
then calls a method on it, and those methods are ordinary Ruby-visible methods on
Regexp.sub's block form reaches the pattern throughmd.pre_match, wheremdis whateverpattern.match(self)returned; the redefinedmatch's"PWNED"string has nopre_match, so mruby raisesNoMethodErrorinstead of substituting garbage.sub!witha block goes the same way, through
sub.String#[]answers"P"rather than raising: it hands the result ofargs[0].match(self)to
MatchData#[], and for a String receiver that isString#[]again with an index of 0,so the answer is the first character of the string the redefined
matchreturned.String#sliceis a second entry for the same method.[]=andslice!fetch the matchthe same way and then ask it for
begin, which a String does not have, so those raise.gsubandsplitdo not consultmatchfor their result, so a redefinedmatchleavesthem alone. They reach the pattern through
__byte_matchinstead, which is redefinable inexactly the same way, and redefining that one gives them a
NoMethodErroron__byte_begin.gsub!callspattern.match(self)only for its truthiness, so aredefined
matchreturning any true value leaves it working;scanhands the whole jobto
pattern.__scan, and the replacement-string forms ofsubandgsubtopattern.__sub_strandpattern.__gsub_str.The surface is every method the overrides call on a pattern or on the MatchData that
pattern handed back:
match(frommatch,sub,sub!,gsub!,[],[]=andslice!),match?(frommatch?),=~(from=~),__byte_match(fromgsubandsplit),__sub_str,__gsub_strand__scan.[],pre_match,post_match,begin,end,size,length,__byte_begin,__byte_endand__set_globals.All of them are defined with
mrb_define_method(); the__prefix is a namingconvention, not a protection.
CRuby is open in one of these places and deliberately so:
rb_str_match_m()dispatchesmatchto the pattern on purpose, which is why"hello".match(r)answers"PWNED"thereas well. Everywhere else it is closed.
rb_str_sub_bang(),rb_str_subpat(),rb_str_subpat_set()andrb_str_slice_bang()callrb_reg_search()directly, andrb_str_match_m_p()andrb_str_match()search a real Regexp without asking it anything,so
String#match?andString#=~are closed there while the overrides here dispatch. ARegexpcreated withRegexp.newis not frozen in either implementation, so this needs nounusual setup.
Why a comment rather than a fix
It takes deliberate sabotage to observe. Redefining
Regexp#matchon an instance you thenpass to
String#suborString#[]is not something a program does by accident, andnothing here is reachable from ordinary input: an argument that is not a Regexp never
reaches these calls, because the type test in front of them cannot be steered. That
distinction is the point, and the existing comments do not draw it. They claim only that
the argument cannot pose as a Regexp, which is true, but placed in front of a search they
read as though accepting the argument settled everything the pattern can influence.
The alternative is to give the overrides a C entry point that takes a pattern and a string
and searches, without going through any Ruby-visible method on the pattern. That is a
larger change than it sounds:
sub,gsub,split,scan,[],[]=andslice!would each need one, and the block forms have to stay in mrblib because they call back
into Ruby. It also buys nothing against any input the gem actually receives.
So this PR writes the position down instead:
CRuby stands on each, and why the gem accepts it;
String#[], so its type-test comment stops reading as a claimabout the search that follows it.
CodeRabbit raised the
String#[]half of this on #7054 as a review comment, asking thatthe regexp branch call the built-in
Regexp#matchdirectly. That is the C entry pointoption applied to one method; it was left alone there on the grounds that the rest of the
file has the same shape and the question belongs to the whole gem. This is the answer to
that question.
Testing
Comment-only, so behaviour is unchanged.
rake testpasses (2008 tests, 0 failures, 0crashes). Every result above was reproduced on this branch and against CRuby 4.0.6.
Summary by CodeRabbit
String#[]documentation to distinguish implementation selection from subsequent pattern behavior.