Skip to content

fix(regex): escape literal [ inside character classes (#3527) - #3777

Closed
proggeramlug wants to merge 1 commit into
mainfrom
fix/3527-regex-bracket-in-class
Closed

proggeramlug wants to merge 1 commit into
mainfrom
fix/3527-regex-bracket-in-class

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

A literal unescaped [ inside a [...] character class is valid in JS (a plain [), but both Rust engines — the regex crate and the fancy-regex fallback — reject it as "invalid character class". js_regex_to_rust passed it through unchanged.

Why this mattered (and why it's NOT an engine limitation)

Perry already has a fancy-regex fallback that supports backreferences, lookahead, and lookbehind. But the same translated pattern string feeds both engines: when the regex crate bails on an advanced construct and the code falls back to fancy-regex, an unescaped [ makes fancy-regex reject the pattern too — even though it fully supports the rest.

get-intrinsic's path-split pattern is exactly this shape:

[^%.[\]]+|\[(?:(-?\d+...)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])...)

It has backreferences (\2) and lookahead ((?=...)) — both fancy-regex-compatible — but the leading [^%.[\]] class with a literal [ made it throw SyntaxError: Invalid regular expression: …: invalid pattern at init while compiling Express (#3527). Escaping the [ → the whole pattern compiles under fancy-regex and matches correctly.

Fix

Track character-class depth in js_regex_to_rust and escape a literal [ found inside a class to \[ (semantics-preserving in both engines). Also gate the (?<name>...) named-group rewrite on being outside a class, where ( is literal.

Verification

  • The get-intrinsic pattern now compiles under fancy-regex and matches (%Array.prototype.push% → Array); the Express tree advances past it.
  • Unit test js_regex_to_rust_escapes_literal_bracket_in_class covers the escaping, the named-group interaction, and end-to-end compilation of the full pattern. cargo fmt --check clean.

Part of the #3527 chain (prior: #3537/#3543/#3546, #3551, #3733, #3751, #3764, #3771, #3776). No version bump / changelog — folded in at merge per the worktree-PR convention.

A literal unescaped `[` inside a `[...]` character class is valid in JS (it's
a plain `[`), but both Rust engines — the `regex` crate AND the `fancy-regex`
fallback — reject it as "invalid character class". `js_regex_to_rust` passed it
through unchanged.

This mattered more than a lone-bracket quirk: the *same* translated pattern
string feeds both engines, so when the `regex` crate bailed on an advanced
construct (backreference / lookaround) and the code fell back to `fancy-regex`,
the unescaped `[` made fancy-regex reject the pattern too — even though it fully
supports the backreferences and lookahead involved. get-intrinsic's path-split
pattern `[^%.[\]]+|...(?!\2)...\2...|(?=...)` is exactly this shape, so it threw
`SyntaxError: Invalid regular expression: …: invalid pattern` at init while
compiling Express (#3527), despite fancy-regex being able to handle it once the
`[` is escaped.

Fix: track character-class depth in `js_regex_to_rust` and escape a literal `[`
encountered inside a class to `\[` (semantics-preserving in both engines). Also
gate the `(?<name>...)` named-group rewrite on being outside a class, where `(`
is literal. Verified: the get-intrinsic pattern now compiles under fancy-regex
and matches correctly; the Express tree advances past it. Unit test covers the
class-bracket escaping and the full pattern.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant