RegExp.prototype.source double-escapes an already escaped `/`
Describe the bug
RegExp.prototype.source escapes a / that the pattern has already escaped, so /a\/b/.source returns a\\/b instead of a\/b.
That is a different regex (an escaped backslash followed by /), so a regex built again from its own source no longer behaves like the original.
To Reproduce
cargo run --bin boa -- -e 'console.log(/a\/b/.source)'- Expected:
a\/b - Actual:
a\\/b
Building the regex again from its source stops matching:
cargo run --bin boa -- -e 'const re = /a\/b/; console.log(new RegExp(re.source).test("a/b"))'- Expected:
true - Actual:
false
The same happens with a backslash followed by a line terminator:
cargo run --bin boa -- -e 'console.log(new RegExp("\\\n").source)'- Expected:
\n - Actual:
\\n
Expected behavior
Per EscapeRegExpPattern, the returned string, evaluated again as a regex with the same flags, must behave identically to the original. / and line terminators that are already escaped should be left as they are. V8 returns a\/b.
Build environment (please complete the following information):
- OS: macOS
- Version: 26.6.2
- Target triple: aarch64-apple-darwin
- Rustc version: rustc 1.98.0 (88d9e12ae 2026-08-18)
Additional context
Reproduced on main at 3046147f. The cause is escape_pattern in core/engine/src/builtins/regexp/mod.rs, which adds a \ before every / without checking whether it is already preceded by an unescaped backslash.
This also makes two test262 tests fail: test/staging/sm/RegExp/source.js and test/staging/sm/RegExp/toString.js.
I have a fix ready and will open a PR for it.
Source: boa-dev/boa