Skip to content

mruby-regexp: quote the pattern as written in RegexpError messages - #7031

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-error-original-pattern
Aug 9, 2026
Merged

mruby-regexp: quote the pattern as written in RegexpError messages#7031
matz merged 1 commit into
mruby:masterfrom
takumin:regexp-error-original-pattern

Conversation

@takumin

@takumin takumin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

compile_error() formats the pattern the parser is reading. In /x mode that
is not the pattern the user wrote: mrb_re_compile() points c->src at the
buffer strip_extended() returns, so the message quotes text with the
free-spacing and the comments taken out.

Regexp.new("a # c\n[", Regexp::EXTENDED)
# CRuby: RegexpError (premature end of char-class: /a # c\n[/x)
# mruby: RegexpError (unterminated character class: /a[/)

The comment that explained the pattern is gone from the message, and so is the
position the reader would use to find the error. Anything the pass removes goes
with it:

Regexp.new("a b(", Regexp::EXTENDED)
# CRuby: RegexpError (end pattern with unmatched parenthesis: /a b(/x)
# mruby: RegexpError (unmatched '(': /ab(/)

This is a diagnostics bug, not a behavioural one. The same patterns are accepted
and rejected either way; only the text of the exception differs.

Change

re_compiler gains orig and orig_end next to src and src_end.
mrb_re_compile() sets them from its arguments before the extended-mode block
replaces pattern and len, and compile_error() quotes them instead of
src and src_end. The parser keeps reading the preprocessed text; this only
changes what gets quoted. Ordinary patterns see no difference, since c->src
is then already the caller's buffer.

The new pointer outlives the compile. The single caller is regexp_init(),
which stores the same String in @source before passing RSTRING_PTR(), so
the bytes stay rooted across the allocating mrb_format(). It also never
points into c->stripped, so the message no longer depends on the free order
in compile_error(); the comment there is updated to say what is quoted and
why rather than to state a lifetime constraint that no longer applies.

Out of scope

CRuby appends the flags, /a # c\n[/x. mruby's format string carries no flags
at all, for /x or for anything else, which is a separate difference. The
message wording (unterminated character class against CRuby's
premature end of char-class) is another one; this gem's messages differ from
Onigmo's throughout.

Tests

Two assert_raise_with_message cases in assert("Regexp extended mode (x flag)")
pin the whole message for a comment-bearing pattern and a whitespace-bearing
one. No existing assertion depended on the old text: the RegexpError cases in
this file are all assert_raise without a message.

rake test passes (1940 OK, 0 KO).

Summary by CodeRabbit

  • Bug Fixes

    • Improved regular expression compilation errors in extended mode to display the original pattern, including whitespace and comments.
    • Preserved the correct pattern length in error messages for clearer diagnostics.
  • Tests

    • Added coverage for unterminated character classes and unmatched opening parentheses in extended-mode regular expressions.

`compile_error()` formatted `c->src`, the text the parser reads. In extended
mode `mrb_re_compile()` points `c->src` at the buffer `strip_extended()`
returns, so the message quoted a pattern with the free-spacing and the
comments already removed.

```ruby
Regexp.new("a # c\n[", Regexp::EXTENDED)
# CRuby: RegexpError (premature end of char-class: /a # c\n[/x)
# mruby: RegexpError (unterminated character class: /a[/)

Regexp.new("a b(", Regexp::EXTENDED)
# CRuby: RegexpError (end pattern with unmatched parenthesis: /a b(/x)
# mruby: RegexpError (unmatched '(': /ab(/)
```

Keep the caller's pointer and length in `orig` and `orig_end`, set before the
extended-mode block replaces them, and quote those instead. The parser still
reads the preprocessed text; only the message changes. Ordinary patterns are
unaffected, since `c->src` is then already the caller's buffer.

The new pointer is the caller's buffer and outlives the compile: the single
caller is `regexp_init()`, which stores the same String in `@source` before
passing `RSTRING_PTR()`, so the bytes stay rooted across the allocating
`mrb_format()`. `orig` never points into `c->stripped`, so the message no
longer depends on the free order in `compile_error()`.
@takumin
takumin requested a review from matz as a code owner August 9, 2026 12:23
@coderabbitai

coderabbitai Bot commented Aug 9, 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: cce8e6a7-10c1-454e-bcb4-7c9cec40eded

📥 Commits

Reviewing files that changed from the base of the PR and between 66d3f0a and ff8f9e7.

📒 Files selected for processing (2)
  • mrbgems/mruby-regexp/src/re_compile.c
  • mrbgems/mruby-regexp/test/regexp.rb

📝 Walkthrough

Walkthrough

The regexp compiler now retains the original pattern separately from its preprocessed source. Compilation errors use the original pattern and explicit length. Tests cover malformed extended-mode patterns with whitespace and comments.

Changes

Regexp error source preservation

Layer / File(s) Summary
Compiler original-source tracking
mrbgems/mruby-regexp/src/re_compile.c
The compiler stores original pattern pointers before extended-mode preprocessing and uses them when formatting compilation errors.
Extended-mode error regression tests
mrbgems/mruby-regexp/test/regexp.rb
Tests verify that malformed patterns include the original whitespace and comments in RegexpError messages.

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

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: preserving the original regexp pattern in RegexpError messages.
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.

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