Summary
Perry's . (without the s flag) excludes only \n. ECMAScript's . matches any character except a LineTerminator, which is \n, \r, U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) — §22.2.2.7.
"\t\r\n".match(/.{2}/g)
// node: null
// perry: [ "\t\r" ]
Impact
Silent over-matching, in the direction that matters: a .-based pattern intended to stay within one line will run across a \r, and on CRLF input that is every line. Line-oriented parsers, log scanners and anything splitting on \r\n can consume a boundary they were written to stop at. No error, no crash — just a longer match than the author specified.
How it was found
A corpus differential of 3,402 regex patterns against 12 probe subjects, run against node --experimental-strip-types on current main. 64 of 3,402 records (1.9%) diverge, split between this and #9217.
Surfaced while working on a regex performance change and independent of it — the differential runs on unmodified main.
Fix direction
In crates/perry-runtime/src/regex/grammar.rs, a bare . should translate to a class excluding all four LineTerminators rather than relying on the Rust engine's \n-only default. Under the s (dotAll) flag . matches everything including all four, which perry already handles by mapping s onto the Rust engine's dot-matches-newline mode — so only the non-s case needs to change.
Note the interaction with #9216, which translates JS's [^] to (?s:.): that rewrite is the dotAll form and is unaffected by this fix, but whoever fixes this should confirm both together, since they touch the same translation site.
The corpus differential is a ready-made regression instrument: it should go from 64 diverging records to the count attributable to #9217 alone.
Summary
Perry's
.(without thesflag) excludes only\n. ECMAScript's.matches any character except a LineTerminator, which is\n,\r, U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) — §22.2.2.7.Impact
Silent over-matching, in the direction that matters: a
.-based pattern intended to stay within one line will run across a\r, and on CRLF input that is every line. Line-oriented parsers, log scanners and anything splitting on\r\ncan consume a boundary they were written to stop at. No error, no crash — just a longer match than the author specified.How it was found
A corpus differential of 3,402 regex patterns against 12 probe subjects, run against
node --experimental-strip-typeson currentmain. 64 of 3,402 records (1.9%) diverge, split between this and #9217.Surfaced while working on a regex performance change and independent of it — the differential runs on unmodified
main.Fix direction
In
crates/perry-runtime/src/regex/grammar.rs, a bare.should translate to a class excluding all four LineTerminators rather than relying on the Rust engine's\n-only default. Under thes(dotAll) flag.matches everything including all four, which perry already handles by mappingsonto the Rust engine's dot-matches-newline mode — so only the non-scase needs to change.Note the interaction with #9216, which translates JS's
[^]to(?s:.): that rewrite is the dotAll form and is unaffected by this fix, but whoever fixes this should confirm both together, since they touch the same translation site.The corpus differential is a ready-made regression instrument: it should go from 64 diverging records to the count attributable to #9217 alone.