Skip to content

mruby-regexp: keep the pike VM's visited keys from wrapping - #7073

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-visited-key-wrap
Aug 10, 2026
Merged

mruby-regexp: keep the pike VM's visited keys from wrapping#7073
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-visited-key-wrap

Conversation

@takumin

@takumin takumin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #7071, which landed before this could be folded in.

That change has a step reserve one visited key per epsilon pass rather than a
single key, so s.gen climbs by up to 2 * pass_span per input byte where it
used to climb by up to 2. gen and key_max are uint32_t, so the counter
wraps near 430 MB of subject instead of near 2 GB.

Past a wrap, visited[] still holds the large keys earlier steps wrote.
add_thread() returns at s->visited[pc] >= key for every pc, the closure
adds nothing, and the match silently fails. A wrapped key can also land on
UINT32_MAX, which re_loop_back() returns as its RE_LOOP_STOP sentinel, so
an ordinary fork would read as a loop that has to stop.

Both advance sites move into advance_gen(), which clears the marks and starts
the keys over before the counter can reach either. visited[] only ever
describes the closure being built, so clearing it costs one memset() per wrap
and loses nothing; the guard also holds every live key below the sentinel.

Verification

rake test passes.

The wrap itself needs a subject too large to test directly, so the restart path
was exercised by lowering the threshold to fire on every step. With that build,
rake test stays green and both differential sweeps against CRuby 4.0.6 (the
12870-row structured sweep and the 56000-row random sweep used in #7071)
produce output identical to the build without the guard. The threshold was then
restored and the sweeps re-run to confirm the shipped build is unchanged.

Summary by CodeRabbit

  • Bug Fixes
    • Improved regular expression processing stability by safely handling internal state rollover during extended matching operations.
    • Prevented potential matching errors after prolonged or repeated regular expression evaluations.

A step reserves one visited key per epsilon pass rather than a single key, so
`s.gen` climbs by up to `2 * pass_span` per input byte where it used to climb
by up to 2. `gen` and `key_max` are `uint32_t`, so the counter now wraps near
430 MB of subject instead of near 2 GB.

Past a wrap, `visited[]` still holds the large keys earlier steps wrote.
`add_thread()` then returns at `s->visited[pc] >= key` for every `pc`, the
closure adds nothing, and the match silently fails. A wrapped key can also
land on `UINT32_MAX`, which `re_loop_back()` returns as its `RE_LOOP_STOP`
sentinel, so an ordinary fork would read as a loop that has to stop.

Move both advance sites into `advance_gen()`, which clears the marks and
starts the keys over before the counter can reach either. The marks only ever
describe the closure being built, so clearing them costs one `memset()` per
wrap and loses nothing, and the same guard holds every live key below the
sentinel.

Forcing the restart on every step, rather than once per wrap, leaves `rake
test` green and both differential sweeps against CRuby identical to the run
without it, so the restart path answers the same as the counter it replaces.
@takumin
takumin requested a review from matz as a code owner August 10, 2026 11:25
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: be33ea41-de0a-4634-8d4d-2f799f340dcd

📥 Commits

Reviewing files that changed from the base of the PR and between 0db635c and 2f33464.

📒 Files selected for processing (1)
  • mrbgems/mruby-regexp/src/re_exec.c

📝 Walkthrough

Walkthrough

The Pike VM now centralizes generation advancement and visited-state clearing. Initialization starts generation tracking at zero, and match setup plus per-character processing use the new helper.

Changes

Pike VM generation tracking

Layer / File(s) Summary
Generation rollover helper
mrbgems/mruby-regexp/src/re_exec.c
Adds advance_gen() to update generation bounds and clear visited marks before counter exhaustion. Updates the loop-key documentation and initial generation values.
Generation advancement integration
mrbgems/mruby-regexp/src/re_exec.c
Uses advance_gen() when seeding match attempts and before processing each input character.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • mruby/mruby#7071: Both changes update Pike VM generation and visited-state handling in re_exec.c.

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing Pike VM visited keys from wrapping.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matz
matz merged commit b7e292e into mruby:master Aug 10, 2026
20 of 21 checks passed
@takumin
takumin deleted the regexp-visited-key-wrap branch August 10, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants