Skip to content

fix(parse): prevent unintended Unicode case folding for character classes in alternations - #681

Open
reginaldalfret wants to merge 1 commit into
google:mainfrom
reginaldalfret:fix/alternation-charclass-casefold
Open

reginaldalfret wants to merge 1 commit into
google:mainfrom
reginaldalfret:fix/alternation-charclass-casefold

Conversation

@reginaldalfret

Copy link
Copy Markdown

Problem

In UTF-8 mode without the case-insensitive flag (FoldCase), a|[sS] matches \u017F (Latin small letter long s) and a|[kK] matches \u212A (Kelvin sign), while [sS] alone, [kK] alone, and a|s|S do not match.

RE2::FullMatch("\u017F", "a|[sS]"); // returned true (unexpected)
RE2::FullMatch("\u017F", "[sS]");   // returned false
RE2::FullMatch("\u017F", "a|s|S");  // returned false
RE2::FullMatch("\u212A", "a|[kK]"); // returned true (unexpected)
RE2::FullMatch("\u212A", "[kK]");   // returned false
RE2::FullMatch("\u212A", "a|k|K");  // returned false

Cause

In Regexp::ParseState::PushRegexp (re2/parse.cc), character classes of size 2 containing an uppercase/lowercase ASCII letter pair are rewritten as literals with flags_ | FoldCase.

However, in UTF-8 mode, Unicode case folding on s and k includes non-ASCII runes (\u017F and \u212A). When alternations like a|[sS] or a|[kK] are subsequently factored in FactorAlternationImpl::Round3, the literal's FoldCase flag causes AddRangeFlags to expand s and k into their full Unicode folding cycles, injecting the non-ASCII runes into the merged character class.

Solution

In PushRegexp, only rewrite two-character ASCII classes to literals with FoldCase if Latin1 mode is enabled or if the lower/upper runes form an exact 2-cycle in case folding (CycleFoldRune(lower) == upper). This preserves the optimization for ASCII letters that only fold between uppercase and lowercase ASCII, while preventing k and s from erroneously acquiring Unicode case-folding expansion when case-insensitivity was not enabled.

Added regression tests verifying that [sS], a|[sS], [kK], and a|[kK] do not match \u017F or \u212A.

Fixes #676.

When character classes like `[sS]` and `[kK]` are parsed in UTF-8 mode
without the case-insensitive flag (`FoldCase`), `PushRegexp` rewrote
them as literals with `flags_ | FoldCase` if their size was 2 and
contained matching uppercase and lowercase ASCII characters.

However, in UTF-8 mode, Unicode case folding on `s` and `k` includes
non-ASCII runes (U+017F / Latin small letter long s, and U+212A /
Kelvin sign). When an alternation like `a|[sS]` or `a|[kK]` was subsequently
factored in `FactorAlternationImpl::Round3`, the literal's `FoldCase` flag
caused `AddRangeFlags` to pull in those non-ASCII fold variants into the
combined character class, causing `a|[sS]` to match `\u017F` and `a|[kK]`
to match `\u212A`, while `[sS]` alone, `[kK]` alone, or `a|s|S` did not.

Only rewrite two-character ASCII classes to literals with `FoldCase` if
`Latin1` mode is enabled or if the lower/upper runes form an exact 2-cycle
in case folding (`CycleFoldRune(lower) == upper`), preventing `k` and `s`
from erroneously acquiring Unicode case-fold expansion when no fold flag
was requested.

Fixes google#676.

Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>

This branch has not been deployed

No deployments
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.

regex [sS] or [kK] matches U+017F and U+212A inconsistently

1 participant