From f4cdb06917c45e44abe0d5c5f76dcc63a07090e7 Mon Sep 17 00:00:00 2001 From: rameel Date: Sun, 16 Feb 2025 00:44:10 +0500 Subject: [PATCH] Fix infinite loop prevention Fixed #7 --- src/Ramstack.Parsing/Parser.Repeat.cs | 28 ++++++++++++---- src/Ramstack.Parsing/Parser.Until.cs | 32 ++++++++++++------- .../ParsersTests.Repeat.cs | 26 +++++++++++++++ 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/Ramstack.Parsing/Parser.Repeat.cs b/src/Ramstack.Parsing/Parser.Repeat.cs index 9d483fd..3bd8e41 100644 --- a/src/Ramstack.Parsing/Parser.Repeat.cs +++ b/src/Ramstack.Parsing/Parser.Repeat.cs @@ -318,14 +318,27 @@ public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out do { - if (!parser.TryParse(ref context, out var result)) - break; + var last = context.Position; - // Prevent pointless loop with zero-width parser - if (context.MatchedSegment.Length == 0 && list.Count >= _min) + if (!parser.TryParse(ref context, out var result)) break; list.Add(result); + + // + // Prevent infinite loop + // + if (list.Count >= _min) + { + // + // Parsing failed in this case because: + // 1. The parser matched, but the position remained unchanged. + // 2. Rechecking would yield the same result, making it redundant. + // 3. If a parser matches but the position remains unchanged, it results in an infinite loop. + // + if (context.Position == last) + break; + } } while (list.Count < _max); @@ -380,11 +393,14 @@ public override bool TryParse(ref ParseContext context, out Unit value) do { + var last = context.Position; if (!parser.TryParse(ref context, out value)) break; - // Prevent pointless loop with zero-width parser - if (context.MatchedSegment.Length != 0) + // + // Prevent infinite loop + // + if (context.Position != last) continue; count = _min; diff --git a/src/Ramstack.Parsing/Parser.Until.cs b/src/Ramstack.Parsing/Parser.Until.cs index 135fcf9..df9ffe1 100644 --- a/src/Ramstack.Parsing/Parser.Until.cs +++ b/src/Ramstack.Parsing/Parser.Until.cs @@ -58,24 +58,28 @@ public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out return true; } + var position = context.Position; + if (!parser.TryParse(ref context, out var result)) break; - // Prevent infinite loop with zero-width match - if (context.MatchedSegment.Length == 0) + list.Add(result); + + // + // Prevent infinite loop + // + if (context.Position == position) { // // Parsing failed in this case because: - // 1. The main parser matched zero length, so the position remains unchanged. + // 1. The main parser matched, but the position remained unchanged. // 2. The terminator didn't match before, and it won't match now. // 3. Rechecking would yield the same result, making it redundant. - // 4. Without a terminator, parsing is considered unsuccessful, and it will never be matched now. - // 5. With a zero-width match, it results in an infinite loop. + // 4. Without a terminator, parsing is considered unsuccessful, and it will never match now. + // 5. If a parser matches but the position remains unchanged, it results in an infinite loop. // break; } - - list.Add(result); } value = null; @@ -115,19 +119,23 @@ public override bool TryParse(ref ParseContext context, out Unit value) return true; } + var position = context.Position; + if (!parser.TryParse(ref context, out value)) break; - // Prevent infinite loop with zero-width match - if (context.MatchedSegment.Length == 0) + // + // Prevent infinite loop + // + if (context.Position == position) { // // Parsing failed in this case because: - // 1. The main parser matched zero length, so the position remains unchanged. + // 1. The main parser matched, but the position remained unchanged. // 2. The terminator didn't match before, and it won't match now. // 3. Rechecking would yield the same result, making it redundant. - // 4. Without a terminator, parsing is considered unsuccessful, and it will never be matched now. - // 5. With a zero-width match, it results in an infinite loop. + // 4. Without a terminator, parsing is considered unsuccessful, and it will never match now. + // 5. If a parser matches but the position remains unchanged, it results in an infinite loop. // break; } diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs index 780f5f5..7a5dc0e 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs @@ -51,4 +51,30 @@ public void Repeat_5_10_Test() Assert.That(parser.Parse("aaaa").Success, Is.False); } + + [Test] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public void Repeat_InfiniteLoopPrevention_ZeroLength() + { + var digit = Character.Digit; + var digit_list = digit.Separated(L(',')); + var index_list = digit_list.Between(L('['), L(']')).Many(); + + Assert.That(index_list.Parse("[]").Length, Is.EqualTo(2)); + Assert.That(index_list.Parse("[][]").Length, Is.EqualTo(4)); + Assert.That(index_list.Parse("[][][]").Length, Is.EqualTo(6)); + + Assert.That(index_list.Parse("[1,2][1,2][2,6]").Length, Is.EqualTo(15)); + Assert.That(index_list.Parse("[][][1,2][][][1,2][][][2,6][][]").Length, Is.EqualTo(31)); + } + + [Test] + public void Repeat_InfiniteLoopPrevention_ZeroConsuming() + { + var parser = And(Character.Digit).AtLeast(2); + + Assert.That(parser.Parse("1234567890").Success, Is.True); + Assert.That(parser.Parse("1234567890").Length, Is.Zero); + Assert.That(parser.Text().Parse("1234567890").Value, Is.Empty); + } }