mruby-regexp: relocate the lookaround offsets with the code they name - #7213
Conversation
`enum re_opcode` gives four opcodes an absolute code index in `offset`: the
lookarounds hold the end of their sub-pattern there, the same way `RE_JMP`,
`RE_SPLIT` and `RE_SPLITNG` hold a jump target. Both relocators carried
their own list of which opcodes to fix, and both lists named the three jumps
and stopped: `insert_inst()` shifting code down to make room, and
`emit_atom_copy()` re-pointing a copied atom at itself.
A lookaround that went through either one kept an index into where its
sub-pattern used to be. The stale index lands on the sub-pattern's own
`RE_MATCH`, which ends the outer match at that point, so the pattern answers
about a match it never made. Three shapes reach a relocator: `*` or `?`
around a group, which inserts a `SPLIT` in front of it; `{n,m}`, which
copies it; and alternation, where `compile_alt()` inserts a `SPLIT` at the
first branch once every branch is compiled.
The answers flip in both directions, and a `MatchData` survives the failure
looking successful. Each line below is `begin(0)` and `m[0]`, against CRuby
4.0.6:
```ruby
/(?:(?=a)b)*x/.match("a") # CRuby: nil mruby: 0, nil
/(?:(?=a)a){2}/.match("aa") # CRuby: 0, "aa" mruby: nil
/(?=a)a|z/.match("ax") # CRuby: 0, "a" mruby: 0, nil
/(?:(?!b)b)*x/.match("ax") # CRuby: 1, "x" mruby: 0, nil
/(?:(?=a)ab)+/.match("ab") # CRuby: 0, "ab" mruby: 0, "ab"
```
The last one is the same group with nothing to relocate, and it has always
agreed.
Ask one function which opcodes hold a code index, rather than keeping the
question answered separately in each relocator: the two lists disagreeing
with the opcode set is the defect, and a list per relocator invites it back
the next time an opcode gains an index. `RE_LB_WIDTH` stays out, since it
carries a character count in `a`, as do `RE_SAVE` and `RE_BACKREF`, whose
`offset` is a slot number and a case-fold flag.
The ambiguity `insert_inst()` already resolves for a target equal to the
insertion point needs no new arm. A lookaround's sub-pattern ends ahead of
it, so it is always the forward-reference case, and leaving the end where it
is puts the inserted instruction after the sub-pattern rather than inside
it, which is what the quantifier wrapping the whole group is asking for.
|
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; 7 remain after this review. 📝 WalkthroughWalkthroughThe regexp compiler centralizes relocation of instruction indices for jumps, splits, and lookaround endpoints. Atom copying uses the same relocation rules. Regression tests cover relocated lookaround patterns and match boundaries. ChangesRegexp code-index relocation
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This localized change corrects lookaround behavior during regexp code relocation and adds targeted coverage; the reported checks pass, so no actionable merge-blocking risk remains. 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 |
enum re_opcodegives four opcodes an absolute code index inoffset: the lookarounds hold the end of their sub-pattern there, the same wayRE_JMP,RE_SPLITandRE_SPLITNGhold a jump target. Two places inre_compile.cmove compiled code and have to carry those indices along, and each carried its own list of which opcodes to fix. Both lists named the three jumps and stopped.The defect
A lookaround that goes through either relocator keeps an index into where its sub-pattern used to be. The stale index lands on the sub-pattern's own
RE_MATCH, which ends the outer match at that point, so the pattern answers about a match it never made.Three shapes reach a relocator:
*or?around a group, whereinsert_inst()puts aSPLITin front of it and shifts the rest down{n,m}, whereemit_atom_copy()copies the group and re-points the copy at itselfcompile_alt()inserts aSPLITat the first branch once every branch is compiledThe answers flip in both directions, and a
MatchDatasurvives the failure looking successful: the match reports a position whilem[0]isnil. Each line isbegin(0)andm[0], against CRuby 4.0.6:The last line is the same group with nothing to relocate, and it has always agreed.
The fix
Ask one function,
op_holds_code_index(), which opcodes hold a code index, rather than keeping the question answered separately in each relocator. The two lists disagreeing with the opcode set is the defect, and a list per relocator invites it back the next time an opcode gains an index.RE_LB_WIDTHstays out, since it carries a character count ina, as doRE_SAVEandRE_BACKREF, whoseoffsetis a slot number and a case-fold flag.The ambiguity
insert_inst()already resolves for a target equal to the insertion point needs no new arm. A lookaround's sub-pattern ends ahead of it, so it is always the forward-reference case, and leaving the end where it is puts the inserted instruction after the sub-pattern rather than inside it, which is what the quantifier wrapping the whole group is asking for.Tests
mrbgems/mruby-regexp/test/regexp_syntax.rbgains one assertion block covering the three relocation shapes, one case each, plus the group with nothing to relocate as the control.hostbuild_config/default.rbhostbintestbuild_config/default.rbfull-debugbuild_config/ci/gcc-clang.rbbintestbuild_config/ci/gcc-clang.rbbintestbintestbuild_config/ci/gcc-clang.rbcxx_abibuild_config/ci/gcc-clang.rbbyte-stringbuild_config/ci/gcc-clang.rbascii-casebuild_config/ci/gcc-clang.rbasanbuild_config/asan.rbasanbintestbuild_config/asan.rbThe
asanbuild reports noAddressSanitizeror UBSan diagnostic.Environment
Details
Compile lines for
mrbgems/mruby-regexp/src/re_compile.c, taken fromrake --verbosewith-MMD -c,-Iand-oremoved.full-debugandasanare-O0, sinceenable_debug()appends-g3 -O0after the toolchain default of-g -O3.Summary by CodeRabbit
Bug Fixes
Tests