From 54beb0c8e823a947282bca19b96db316b5feab3f Mon Sep 17 00:00:00 2001 From: Ilya Matveev Date: Mon, 12 Nov 2018 16:20:14 +0700 Subject: [PATCH] [regex] Fix quantifier processing Consider the following regular expression: ((.){0,1}b)+ The original Harmony implementation fails to match a string '0b1b' with it. The root-cause is the following. After matching the inner group first time (it matches '0' in the input string) we don't reset the counter of occurrences for it and proceed to the next elements for the regex. When we should match it the second time (we should match '1' at this point) we obtain this obsolete value of the counter and come to a conclusion that the group has more occurrences than specified in the quantifier. The fix resets this counter each time we move to next elements of a regex from a QuantifierSet. --- .../tests/harmony_regex/MatchResultTest.kt | 23 ++++++++++++ .../text/regex/sets/GroupQuantifierSet.kt | 31 ++++++++-------- .../regex/sets/ReluctantGroupQuantifierSet.kt | 35 ++++++++++--------- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/backend.native/tests/harmony_regex/MatchResultTest.kt b/backend.native/tests/harmony_regex/MatchResultTest.kt index 7c3bf53cde0..01af9e16946 100644 --- a/backend.native/tests/harmony_regex/MatchResultTest.kt +++ b/backend.native/tests/harmony_regex/MatchResultTest.kt @@ -435,6 +435,29 @@ class MatchResultTest { assertFalse(regex.containsMatchIn(str)) } + /** + * Regression test for https://github.com/JetBrains/kotlin-native/issues/2297 + */ + @Test fun test2297() { + assertTrue(Regex("^(:[0-5]?[0-9])+$").matches(":20:30")) + assertTrue(Regex("(.{1,}){2}").matches("aa")) + + assertTrue(Regex("(.+b)+").matches("0b0b")) + assertTrue(Regex("(.+?b)+").matches("0b0b")) + assertTrue(Regex("(.?b)+").matches("0b0b")) + assertTrue(Regex("(.??b)+").matches("0b0b")) + assertTrue(Regex("(.*b)+").matches("0b0b")) + assertTrue(Regex("(.*?b)+").matches("0b0b")) + assertTrue(Regex("(.{1,2}b)+").matches("0b00b")) + assertTrue(Regex("(.{1,2}?b)+").matches("0b00b")) + + assertTrue(Regex("([0]?[0]?)+").matches("0000")) + assertTrue(Regex("([0]?[0]?b)+").matches("00b00b")) + assertTrue(Regex("((b{2}){3})+").matches("bbbbbbbbbbbb")) + + assertTrue(Regex("[^a]").matches("b")) + } + /** * Regression test for HARMONY-3360 */ diff --git a/runtime/src/main/kotlin/kotlin/text/regex/sets/GroupQuantifierSet.kt b/runtime/src/main/kotlin/kotlin/text/regex/sets/GroupQuantifierSet.kt index 7d7b2f37a81..95fecf1149c 100644 --- a/runtime/src/main/kotlin/kotlin/text/regex/sets/GroupQuantifierSet.kt +++ b/runtime/src/main/kotlin/kotlin/text/regex/sets/GroupQuantifierSet.kt @@ -42,44 +42,47 @@ open internal class GroupQuantifierSet( // We call innerSet.matches here, if it succeeds it call next.matches where next is this QuantifierSet. // So we have a recursive searching procedure. override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { + var enterCount = matchResult.enterCounters[groupQuantifierIndex] + + fun matchNext(): Int { + matchResult.enterCounters[groupQuantifierIndex] = 0 + val result = next.matches(startIndex, testString, matchResult) + matchResult.enterCounters[groupQuantifierIndex] = enterCount + return result + } if (!innerSet.hasConsumed(matchResult)) { - return next.matches(startIndex, testString, matchResult) + return matchNext() } // Fast case: '*' or {0, } - no need to count occurrences. if (min == 0 && max == Quantifier.INF) { val nextIndex = innerSet.matches(startIndex, testString, matchResult) - return if (nextIndex < 0) { - next.matches(startIndex, testString, matchResult) + matchNext() } else { nextIndex } } - val enterCount = matchResult.enterCounters[groupQuantifierIndex] - // can't go inner set; if (max != Quantifier.INF && enterCount >= max) { - return next.matches(startIndex, testString, matchResult) + return matchNext() } // go inner set; - matchResult.enterCounters[groupQuantifierIndex]++ + matchResult.enterCounters[groupQuantifierIndex] = ++enterCount val nextIndex = innerSet.matches(startIndex, testString, matchResult) - if (nextIndex < 0) { - matchResult.enterCounters[groupQuantifierIndex]-- + return if (nextIndex < 0) { + matchResult.enterCounters[groupQuantifierIndex] = --enterCount if (enterCount >= min) { - return next.matches(startIndex, testString, matchResult) + matchNext() } else { - matchResult.enterCounters[groupQuantifierIndex] = 0 - return -1 + -1 } } else { - matchResult.enterCounters[groupQuantifierIndex] = 0 - return nextIndex + nextIndex } } diff --git a/runtime/src/main/kotlin/kotlin/text/regex/sets/ReluctantGroupQuantifierSet.kt b/runtime/src/main/kotlin/kotlin/text/regex/sets/ReluctantGroupQuantifierSet.kt index e492d04d681..a6da1602cd2 100644 --- a/runtime/src/main/kotlin/kotlin/text/regex/sets/ReluctantGroupQuantifierSet.kt +++ b/runtime/src/main/kotlin/kotlin/text/regex/sets/ReluctantGroupQuantifierSet.kt @@ -34,9 +34,17 @@ internal class ReluctantGroupQuantifierSet( ) : GroupQuantifierSet(quantifier, innerSet, next, type, setCounter) { override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { + var enterCount = matchResult.enterCounters[groupQuantifierIndex] + + fun matchNext(): Int { + matchResult.enterCounters[groupQuantifierIndex] = 0 + val result = next.matches(startIndex, testString, matchResult) + matchResult.enterCounters[groupQuantifierIndex] = enterCount + return result + } if (!innerSet.hasConsumed(matchResult)) { - return next.matches(startIndex, testString, matchResult) + return matchNext() } // Fast case: '*' or {0, } - no need to count occurrences. @@ -49,29 +57,22 @@ internal class ReluctantGroupQuantifierSet( } } - val enterCounter = matchResult.enterCounters[groupQuantifierIndex] - // can't go inner set; - if (enterCounter >= max) { - matchResult.enterCounters[groupQuantifierIndex] = 0 - return next.matches(startIndex, testString, matchResult) + if (max != Quantifier.INF && enterCount >= max) { + return matchNext() } - var nextIndex: Int - if (enterCounter >= min) { - nextIndex = next.matches(startIndex, testString, matchResult) + return if (enterCount >= min) { + val nextIndex = matchNext() if (nextIndex < 0) { - matchResult.enterCounters[groupQuantifierIndex]++ - nextIndex = innerSet.matches(startIndex, testString, matchResult) + matchResult.enterCounters[groupQuantifierIndex] = ++enterCount + innerSet.matches(startIndex, testString, matchResult) } else { - matchResult.enterCounters[groupQuantifierIndex] = 0 - return nextIndex + nextIndex } } else { - matchResult.enterCounters[groupQuantifierIndex]++ - nextIndex = innerSet.matches(startIndex, testString, matchResult) + matchResult.enterCounters[groupQuantifierIndex] = ++enterCount + innerSet.matches(startIndex, testString, matchResult) } - - return nextIndex } }