[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.
This commit is contained in:
Ilya Matveev
2018-11-12 16:20:14 +07:00
committed by Ilya Matveev
parent 70efeb1433
commit 54beb0c8e8
3 changed files with 58 additions and 31 deletions
@@ -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
*/
@@ -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
}
}
@@ -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
}
}