[K/N] Fix stack overflow in regex when a quantifier is matched many times

Motivation:

Users often expect simple patterns, like `[a]+` or `[^a]+`, to work fast
and without any problems, even with long strings.
Char class from the first pattern matches only 'a' and gets wrapped into
LeafQuantifierSet, and works fine with long strings indeed.
Char class from the second pattern, however, matches any character
except 'a', including supplementary code points. So, the number of chars
it consumes is not known beforehand. There is no optimization for such
char classes, and if they are matched multiple times, the stack memory
gets exhausted.

Modification:

Introduce FixedLengthQuantifierSet node.
The node represents quantifier over constructs that consume a fixed
amount of characters for a given string and index. Such constructs don't
need backtracking to find a different match. Thus, it is possible for
the node to avoid recursion when matching multiple times.

Result:

Fixes KT-46211, KT-35508 and probably KT-39789. Reproducer for the
latter issue is no longer available, but error stacktrace resembles
those of the other issues.
This commit is contained in:
Abduqodiri Qurbonzoda
2022-11-17 00:04:03 +02:00
committed by Space Team
parent 6e50bbee3b
commit fb31a29c39
16 changed files with 440 additions and 116 deletions
@@ -452,23 +452,27 @@ class MatchResultTest {
* 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"))
fun testMatches(pattern: String, input: String) {
assertTrue(Regex(pattern).matches(input), "\"$pattern\" should match \"$input\"")
}
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"))
testMatches("^(:[0-5]?[0-9])+$", input = ":20:30")
testMatches("(.{1,}){2}", input = "aa")
assertTrue(Regex("([0]?[0]?)+").matches("0000"))
assertTrue(Regex("([0]?[0]?b)+").matches("00b00b"))
assertTrue(Regex("((b{2}){3})+").matches("bbbbbbbbbbbb"))
testMatches("(.+b)+", input = "0b0b")
testMatches("(.+?b)+", input = "0b0b")
testMatches("(.?b)+", input = "0b0b")
testMatches("(.??b)+", input = "0b0b")
testMatches("(.*b)+", input = "0b0b")
testMatches("(.*?b)+", input = "0b0b")
testMatches("(.{1,2}b)+", input = "0b00b")
testMatches("(.{1,2}?b)+", input = "0b00b")
assertTrue(Regex("[^a]").matches("b"))
testMatches("([0]?[0]?)+", input = "0000")
testMatches("([0]?[0]?b)+", input = "00b00b")
testMatches("((b{2}){3})+", input = "bbbbbbbbbbbb")
testMatches("[^a]", input = "b")
}
@Test fun kt28158() {