Skip to content

Skip literal prefix index hits inside surrogate pairs (fixes #207) - #209

Closed
jdymitarai wants to merge 1 commit into
google:masterfrom
jdymitarai:fix-surrogate-pair-prefix-index-207
Closed

jdymitarai wants to merge 1 commit into
google:masterfrom
jdymitarai:fix-surrogate-pair-prefix-index-207

Conversation

@jdymitarai

Copy link
Copy Markdown

Fixes #207

Problem

When a pattern contains a literal prefix whose first character is a lone low surrogate (e.g. \uDC21), MachineInput.UTF16Input.index() previously used indexOf(str, re2.prefix, pos) to jump directly to matches.
When str contains a well-formed surrogate pair (e.g. \uD801\uDC21), indexOf matches the low surrogate \uDC21 inside the surrogate pair.
When Machine.java advances pos to this hit, step(pos) decodes the low surrogate in isolation as rune 0xDC21 and matches. In contrast, patterns without a literal prefix (such as \uDC21|\uDC22 or [\uD800-\uDFFF]) step codepoint-by-codepoint, decoding the pair as the composite code point U+10421 and stepping past it without matching.

Solution

In MachineInput.UTF16Input.index(), verify whether an indexOf hit is a low surrogate preceded by a high surrogate (i > 0 && Character.isLowSurrogate(str.charAt(i)) && Character.isHighSurrogate(str.charAt(i - 1))). If so, the hit is inside a surrogate pair and is not a valid codepoint boundary; skip it and resume searching at i + 1.

Verification

  • Added testSurrogatePairInteriorNoMatch in javatests/com/google/re2j/MatcherTest.java verifying that lone low surrogate patterns do not match inside surrogate pairs, that search resume finds isolated low surrogates, and that StringBuilder behaves identically.
  • Verified all unit tests pass.

When a pattern's literal prefix begins with a lone low surrogate,
MachineInput.UTF16Input.index() previously jumped to raw indexOf hits.
If the hit is preceded by a high surrogate, it is inside a valid
surrogate pair rather than a codepoint boundary.

In step(), decoding starting from inside the pair caused it to treat
the low surrogate as an isolated rune and erroneously match. Patterns
without literal prefixes step codepoint by codepoint and correctly
skip such pairs.

This commit updates UTF16Input.index() to detect when an indexOf hit is
a low surrogate preceded by a high surrogate, skipping it and resuming
search at the next character.
@jdymitarai

Copy link
Copy Markdown
Author

Closing this PR to avoid review overhead on the team. The context remains in the thread for future reference if helpful. Thanks for your time.

@jdymitarai jdymitarai closed this Sep 12, 2026
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.

Pattern "\uDC21"` matches inside a surrogate pair; equivalent classes don't

1 participant