Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/Ramstack.Parsing/Parser.Repeat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
32 changes: 20 additions & 12 deletions src/Ramstack.Parsing/Parser.Until.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
26 changes: 26 additions & 0 deletions tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}