Skip to content

[YARR] Reject \ + non-ASCII as an identity escape in Unicode mode - #577

Open
robobun wants to merge 4 commits into
mainfrom
robobun/regexp-astral-escape
Open

robobun wants to merge 4 commits into
mainfrom
robobun/regexp-astral-escape

Conversation

@robobun

@robobun robobun commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

With the u or v flag, Yarr accepts \ followed by any non-ASCII character as an identity escape. isIdentityEscapeAnError (YarrParser.h) reports an error only for isASCII(ch) && !strchr(syntaxChars, ch). The spec (IdentityEscape[+UnicodeMode]) allows only SyntaxCharacter and /, plus - in a class and ClassSetReservedPunctuator in a v-mode class set. V8 throws "Invalid escape" for every one of these.

  • /\é/u and /\中/u compile and match the character (V8, spec: SyntaxError).
  • A supplementary character is worse. The accepting arm of parseEscape does atomPatternCharacter(consume()), which takes one code unit. So /^\😀$/u becomes a lone lead surrogate atom followed by a lone trail surrogate atom and matches no input at all: none of the 1.1M single-code-point strings match it. /[\😀]/u matches "\ud83d" and "\ude00" but not "\u{1F600}".
new RegExp("\\é", "u").test("é")                      // true   (spec, V8: SyntaxError)
new RegExp("^\\\u{1F600}$", "u").test("\u{1F600}")    // false  (spec, V8: SyntaxError)
["\ud83d", "\ude00", "\u{1F600}"].map(s => new RegExp("^[\\\u{1F600}]$", "u").test(s))  // [true, true, false]

Fix

In Unicode mode isIdentityEscapeAnError now returns true for any code unit that is NUL, non-ASCII, or ASCII outside the allowed set. Non-Unicode patterns keep the Annex B behaviour. Every other caller of the function already passes an ASCII character, so only the default: arm of parseEscape changes behaviour.

Scope

This PR first also fixed RegExp.escape narrowing a supplementary code point to 16 bits before classifying it. Upstream has since landed that half as 320682@main (https://bugs.webkit.org/show_bug.cgi?id=323642, with JSTests/stress/regexp-escape-supplementary.js), and it reached this fork's main through the upstream merge in #614. The branch now takes main's RegExpConstructor.cpp and regexp-escape.js unchanged. Its diff against main is the Yarr change and its test. Upstream main still has the Yarr defect.

Verification

  • JSTests/stress/regexp-unicode-identity-escape-non-ascii.js: \ + BMP, supplementary, lone surrogate, U+2028 and U+FEFF throw SyntaxError with u and v in an atom, a class, a group, a \q{} and a set operation. The allowed escapes still parse, and non-Unicode patterns still accept the same inputs.
  • Ran that file, regexp-escape-supplementary.js, regexp-escape.js, regexp-v-flag-escaped-hyphen-after-set-operand.js, regexp-vflag-property-of-strings.js and regexp-unicode-mix-escaped-and-literal-surrogates.js under a bun debug ASAN build (Linux x64) linked against the preview build of this head (autobuild-preview-pr-577-86df2756). All pass. The same build passes bun's URLPattern suite (v-flag patterns, 408 tests).
  • On a build of oven-sh/WebKit main without this change, the new stress file fails at its first assertion and bun's test for it fails 7 of 18 cases (RegExp: reject \ + non-ASCII identity escapes with the u and v flags (WebKit bump for oven-sh/WebKit#577) bun#41767).

… Yarr accepts \ + non-ASCII as an identity escape in Unicode mode

RegExp.escape classified each code point with StringView::contains(char16_t)
and isStrWhiteSpace(char16_t). A supplementary code point was truncated to
its low 16 bits before the check. U+2002A (low bits 0x2A, '*') came out as
"\<U+2002A>" and U+20009 (low bits 0x09, tab) came out as "\ud840\udc09".
896 of the 1,048,576 supplementary code points were affected. With the u or
v flag the escaped result then never matched the input. The punctuator
checks now run only for ASCII code points and the whitespace and surrogate
checks only for BMP code points. Supplementary code points pass through
unchanged, as the spec requires.

Yarr's isIdentityEscapeAnError only reported an error for an ASCII character
outside the SyntaxCharacter set. In UnicodeMode the spec allows only
SyntaxCharacter and '/' (plus '-' in a class and ClassSetReservedPunctuator
in a v-mode class set). "\é" and "\中" were accepted with the u flag, and a
supplementary character after the backslash was consumed as a single code
unit, so /^\u{1F600}$/u matched nothing and [\u{1F600}] matched the lone
surrogates. Any non-ASCII character after a backslash is now a SyntaxError
in UnicodeMode, which matches V8 and the spec. Non-Unicode patterns keep
the Annex B behaviour.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment thread JSTests/stress/regexp-escape.js Outdated
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 372f8eda-ee69-4784-9bd2-4200274228e3

📥 Commits

Reviewing files that changed from the base of the PR and between 1de631c and e11053b.

📒 Files selected for processing (1)
  • JSTests/stress/regexp-escape.js

Included review availability: Your plan provides up to 5 included reviews per hour; 1 remains after this review.


Walkthrough

Changes

The patch corrects supplementary-code-point handling in RegExp.escape and rejects non-ASCII identity escapes in Unicode regular expressions. Stress tests cover escaping, matching, syntax errors, permitted escapes, and non-Unicode behavior.

RegExp Unicode behavior

Layer / File(s) Summary
Supplementary code point escaping
Source/JavaScriptCore/runtime/RegExpConstructor.cpp, JSTests/stress/regexp-escape.js
RegExp.escape applies syntax and punctuation checks to ASCII characters, restricts whitespace and surrogate checks to BMP characters, and preserves supplementary code points. Tests cover targeted cases and all supplementary code points under empty, u, and v flags.
Unicode identity-escape validation
Source/JavaScriptCore/yarr/YarrParser.h, JSTests/stress/regexp-unicode-identity-escape-non-ascii.js
Unicode and UnicodeSets modes reject escaped non-ASCII characters. Tests also verify permitted syntax-character escapes and Annex B identity escapes without Unicode flags.

Merge Risk: ⚪ Minimal · up to 95c1e

The updated supplementary-plane coverage is bounded and introduces no identified merge-readiness risk.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description gives a detailed problem statement, fix summary, scope, and verification results. However, it does not follow the required template because it omits the Bugzilla URL, reviewer line, an… Add the associated Bugzilla URL, include the required “Reviewed by NOBODY (OOPS!).” line or reviewer information, and list the changed files with relevant functions. Keep the existing problem, fix, scope, and verification details if desired…
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly identifies the primary Yarr change: rejecting non-ASCII identity escapes in Unicode mode. It is concise and specific.
Full details: Description check

Explanation

The description gives a detailed problem statement, fix summary, scope, and verification results. However, it does not follow the required template because it omits the Bugzilla URL, reviewer line, and changed-file/function list.

Resolution

Add the associated Bugzilla URL, include the required “Reviewed by NOBODY (OOPS!).” line or reviewer information, and list the changed files with relevant functions. Keep the existing problem, fix, scope, and verification details if desired.

  • Fix all pre-merge checks with AI

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


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

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
86df2756 autobuild-preview-pr-577-86df2756 2026-09-11 10:30:18 UTC
95c1e904 autobuild-preview-pr-577-95c1e904 2026-09-10 07:57:44 UTC
e11053bf autobuild-preview-pr-577-e11053bf 2026-09-07 05:13:45 UTC
1de631c3 autobuild-preview-pr-577-1de631c3 2026-09-06 14:47:55 UTC

…stress test stays fast in every configuration

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bounding the supplementary-plane sweep — ~60 code units × 16 planes is comfortably under the stress-test time limit now. I re-reviewed and didn't find any bugs; since this changes what Yarr accepts in Unicode mode (previously-tolerated \ + non-ASCII now throws SyntaxError), a JSC reviewer's look is still worthwhile.

What was reviewed

  • regExpConstructorEscape: the isASCII/U_IS_BMP guards cover every narrowing call site, and the removed surrogate-pair \u branch is genuinely unreachable under the new condition.
  • isIdentityEscapeAnError: non-Unicode mode is untouched (outer isEitherUnicodeCompilation() guard), and strchr is still only reached with an ASCII argument.
  • Stress tests: throw-based assertions, no printing, bounded iteration; the added self-check confirms every listed BMP code unit is actually rewritten before the plane sweep asserts its supplementary alias is not.
Extended reasoning...

Overview

Two narrowly-scoped spec-compliance fixes plus tests. In RegExpConstructor.cpp, regExpConstructorEscape was passing a char32_t code point into StringView::contains(char16_t), isStrWhiteSpace(char16_t), and U16_IS_SURROGATE, silently truncating supplementary code points and mis-escaping any whose low 16 bits collided with an escaped BMP value; the fix gates those checks behind isASCII()/U_IS_BMP() with explicit static_cast<char16_t> and drops the now-dead supplementary \uXXXX\uXXXX branch. In YarrParser.h, isIdentityEscapeAnError is reordered so that in u/v mode any non-ASCII escape is an InvalidIdentityEscape (matching the spec's IdentityEscape[+UnicodeMode] production), where before it silently fell through as valid. JSTests/stress/regexp-escape.js gains targeted supplementary-code-point cases and a bounded plane × aliasing-code-unit sweep, and a new regexp-unicode-identity-escape-non-ascii.js covers the Yarr change across atoms, classes, groups, \q{}, and v-mode set operations.

Security risks

None identified. This tightens input validation (more patterns rejected in Unicode mode) and removes an implicit integer narrowing in a host built-in. No memory management, allocation sizing, or bounds arithmetic changed; strchr is now only reached when isASCII(ch) holds, so no wide-value truncation into it either.

Level of scrutiny

Moderate. The C++ delta is small and mechanical, but the Yarr change is a user-visible behavior change: new RegExp("\\é", "u") used to compile in JSC and will now throw. That's the spec-mandated behavior and matches V8, but it's the kind of compat surface a JSC reviewer should sign off on. Both changed source directories are listed under @ WebKit/jsc-reviewers in .github/CODEOWNERS.

Other factors

The one concern from the prior review — the exhaustive 1,048,576-iteration sweep in regexp-escape.js exceeding the JSTests 200ms budget — was addressed in commit e11053b by replacing it with a targeted sweep of the ~60 BMP code units RegExp.escape actually rewrites, crossed with all 16 supplementary planes (≈960 iterations), plus a self-check that each listed BMP code unit is in fact escaped. Both test files use throw-only assertions with no printing, per JSTests/README.md conventions. No candidate issues were raised or ruled out by the bug hunt beyond that.

robobun added a commit to oven-sh/bun that referenced this pull request Sep 7, 2026
robobun added a commit to oven-sh/bun that referenced this pull request Sep 8, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

main now carries the RegExp.escape half of this branch: upstream landed it as
320682@main (https://bugs.webkit.org/show_bug.cgi?id=323642) with
JSTests/stress/regexp-escape-supplementary.js. Take main's
RegExpConstructor.cpp and regexp-escape.js, so the branch differs from main
only by the Yarr identity-escape fix and its test.
@robobun robobun changed the title [JSC] RegExp.escape narrows supplementary code points to 16 bits, and Yarr accepts \ + non-ASCII as an identity escape in Unicode mode [YARR] Reject \ + non-ASCII as an identity escape in Unicode mode Sep 11, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review found no issues

No high-confidence issues detected in this change.

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