[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)) 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 * 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. // We call innerSet.matches here, if it succeeds it call next.matches where next is this QuantifierSet.
// So we have a recursive searching procedure. // So we have a recursive searching procedure.
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { 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)) { if (!innerSet.hasConsumed(matchResult)) {
return next.matches(startIndex, testString, matchResult) return matchNext()
} }
// Fast case: '*' or {0, } - no need to count occurrences. // Fast case: '*' or {0, } - no need to count occurrences.
if (min == 0 && max == Quantifier.INF) { if (min == 0 && max == Quantifier.INF) {
val nextIndex = innerSet.matches(startIndex, testString, matchResult) val nextIndex = innerSet.matches(startIndex, testString, matchResult)
return if (nextIndex < 0) { return if (nextIndex < 0) {
next.matches(startIndex, testString, matchResult) matchNext()
} else { } else {
nextIndex nextIndex
} }
} }
val enterCount = matchResult.enterCounters[groupQuantifierIndex]
// can't go inner set; // can't go inner set;
if (max != Quantifier.INF && enterCount >= max) { if (max != Quantifier.INF && enterCount >= max) {
return next.matches(startIndex, testString, matchResult) return matchNext()
} }
// go inner set; // go inner set;
matchResult.enterCounters[groupQuantifierIndex]++ matchResult.enterCounters[groupQuantifierIndex] = ++enterCount
val nextIndex = innerSet.matches(startIndex, testString, matchResult) val nextIndex = innerSet.matches(startIndex, testString, matchResult)
if (nextIndex < 0) { return if (nextIndex < 0) {
matchResult.enterCounters[groupQuantifierIndex]-- matchResult.enterCounters[groupQuantifierIndex] = --enterCount
if (enterCount >= min) { if (enterCount >= min) {
return next.matches(startIndex, testString, matchResult) matchNext()
} else { } else {
matchResult.enterCounters[groupQuantifierIndex] = 0 -1
return -1
} }
} else { } else {
matchResult.enterCounters[groupQuantifierIndex] = 0 nextIndex
return nextIndex
} }
} }
@@ -34,9 +34,17 @@ internal class ReluctantGroupQuantifierSet(
) : GroupQuantifierSet(quantifier, innerSet, next, type, setCounter) { ) : GroupQuantifierSet(quantifier, innerSet, next, type, setCounter) {
override fun matches(startIndex: Int, testString: CharSequence, matchResult: MatchResultImpl): Int { 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)) { if (!innerSet.hasConsumed(matchResult)) {
return next.matches(startIndex, testString, matchResult) return matchNext()
} }
// Fast case: '*' or {0, } - no need to count occurrences. // 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; // can't go inner set;
if (enterCounter >= max) { if (max != Quantifier.INF && enterCount >= max) {
matchResult.enterCounters[groupQuantifierIndex] = 0 return matchNext()
return next.matches(startIndex, testString, matchResult)
} }
var nextIndex: Int return if (enterCount >= min) {
if (enterCounter >= min) { val nextIndex = matchNext()
nextIndex = next.matches(startIndex, testString, matchResult)
if (nextIndex < 0) { if (nextIndex < 0) {
matchResult.enterCounters[groupQuantifierIndex]++ matchResult.enterCounters[groupQuantifierIndex] = ++enterCount
nextIndex = innerSet.matches(startIndex, testString, matchResult) innerSet.matches(startIndex, testString, matchResult)
} else { } else {
matchResult.enterCounters[groupQuantifierIndex] = 0 nextIndex
return nextIndex
} }
} else { } else {
matchResult.enterCounters[groupQuantifierIndex]++ matchResult.enterCounters[groupQuantifierIndex] = ++enterCount
nextIndex = innerSet.matches(startIndex, testString, matchResult) innerSet.matches(startIndex, testString, matchResult)
} }
return nextIndex
} }
} }