diff --git a/java/com/google/re2j/MachineInput.java b/java/com/google/re2j/MachineInput.java index f69ba043..9808eb26 100644 --- a/java/com/google/re2j/MachineInput.java +++ b/java/com/google/re2j/MachineInput.java @@ -200,9 +200,18 @@ boolean canCheckPrefix() { @Override int index(RE2 re2, int pos) { - pos += start; - int i = indexOf(str, re2.prefix, pos); - return i < 0 ? i : i - pos; + int p = pos + start; + while (true) { + int i = indexOf(str, re2.prefix, p); + if (i < 0) { + return -1; + } + if (i > 0 && Character.isLowSurrogate(str.charAt(i)) && Character.isHighSurrogate(str.charAt(i - 1))) { + p = i + 1; + continue; + } + return i - (pos + start); + } } @Override diff --git a/javatests/com/google/re2j/MatcherTest.java b/javatests/com/google/re2j/MatcherTest.java index 4f24ae2e..5747150e 100644 --- a/javatests/com/google/re2j/MatcherTest.java +++ b/javatests/com/google/re2j/MatcherTest.java @@ -524,4 +524,23 @@ public void testPatternLongestMatch() { assertEquals("aaa bbb", text.substring(matcher.start(), matcher.end())); } } + + @Test + public void testSurrogatePairInteriorNoMatch() { + // A pattern whose literal prefix is a lone low surrogate should not match + // inside a valid surrogate pair (issue #207). + assertFalse(Pattern.compile("\uDC21").matcher("a\uD801\uDC21b").find()); + assertFalse(Pattern.compile("\uDC21|\uDC22").matcher("a\uD801\uDC21b").find()); + assertFalse(Pattern.compile("[\uD800-\uDFFF]").matcher("a\uD801\uDC21b").find()); + + // Search must skip the interior hit and resume to find an isolated low surrogate: + Matcher m = Pattern.compile("\uDC21").matcher("\uD801\uDC21\uDC21"); + assertTrue(m.find()); + assertEquals(2, m.start()); + assertEquals(3, m.end()); + + // StringBuilder CharSequence input should also behave identically: + Matcher mSb = Pattern.compile("\uDC21").matcher(new StringBuilder("a\uD801\uDC21b")); + assertFalse(mSb.find()); + } }