Skip to content

Commit 45b5b6b

Browse files
authored
Merge pull request #8 from rameel/fix/repeat-parser
Fix for infinite loop prevention
2 parents 809c367 + f4cdb06 commit 45b5b6b

3 files changed

Lines changed: 68 additions & 18 deletions

File tree

src/Ramstack.Parsing/Parser.Repeat.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,14 +318,27 @@ public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out
318318

319319
do
320320
{
321-
if (!parser.TryParse(ref context, out var result))
322-
break;
321+
var last = context.Position;
323322

324-
// Prevent pointless loop with zero-width parser
325-
if (context.MatchedSegment.Length == 0 && list.Count >= _min)
323+
if (!parser.TryParse(ref context, out var result))
326324
break;
327325

328326
list.Add(result);
327+
328+
//
329+
// Prevent infinite loop
330+
//
331+
if (list.Count >= _min)
332+
{
333+
//
334+
// Parsing failed in this case because:
335+
// 1. The parser matched, but the position remained unchanged.
336+
// 2. Rechecking would yield the same result, making it redundant.
337+
// 3. If a parser matches but the position remains unchanged, it results in an infinite loop.
338+
//
339+
if (context.Position == last)
340+
break;
341+
}
329342
}
330343
while (list.Count < _max);
331344

@@ -380,11 +393,14 @@ public override bool TryParse(ref ParseContext context, out Unit value)
380393

381394
do
382395
{
396+
var last = context.Position;
383397
if (!parser.TryParse(ref context, out value))
384398
break;
385399

386-
// Prevent pointless loop with zero-width parser
387-
if (context.MatchedSegment.Length != 0)
400+
//
401+
// Prevent infinite loop
402+
//
403+
if (context.Position != last)
388404
continue;
389405

390406
count = _min;

src/Ramstack.Parsing/Parser.Until.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,28 @@ public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out
5858
return true;
5959
}
6060

61+
var position = context.Position;
62+
6163
if (!parser.TryParse(ref context, out var result))
6264
break;
6365

64-
// Prevent infinite loop with zero-width match
65-
if (context.MatchedSegment.Length == 0)
66+
list.Add(result);
67+
68+
//
69+
// Prevent infinite loop
70+
//
71+
if (context.Position == position)
6672
{
6773
//
6874
// Parsing failed in this case because:
69-
// 1. The main parser matched zero length, so the position remains unchanged.
75+
// 1. The main parser matched, but the position remained unchanged.
7076
// 2. The terminator didn't match before, and it won't match now.
7177
// 3. Rechecking would yield the same result, making it redundant.
72-
// 4. Without a terminator, parsing is considered unsuccessful, and it will never be matched now.
73-
// 5. With a zero-width match, it results in an infinite loop.
78+
// 4. Without a terminator, parsing is considered unsuccessful, and it will never match now.
79+
// 5. If a parser matches but the position remains unchanged, it results in an infinite loop.
7480
//
7581
break;
7682
}
77-
78-
list.Add(result);
7983
}
8084

8185
value = null;
@@ -115,19 +119,23 @@ public override bool TryParse(ref ParseContext context, out Unit value)
115119
return true;
116120
}
117121

122+
var position = context.Position;
123+
118124
if (!parser.TryParse(ref context, out value))
119125
break;
120126

121-
// Prevent infinite loop with zero-width match
122-
if (context.MatchedSegment.Length == 0)
127+
//
128+
// Prevent infinite loop
129+
//
130+
if (context.Position == position)
123131
{
124132
//
125133
// Parsing failed in this case because:
126-
// 1. The main parser matched zero length, so the position remains unchanged.
134+
// 1. The main parser matched, but the position remained unchanged.
127135
// 2. The terminator didn't match before, and it won't match now.
128136
// 3. Rechecking would yield the same result, making it redundant.
129-
// 4. Without a terminator, parsing is considered unsuccessful, and it will never be matched now.
130-
// 5. With a zero-width match, it results in an infinite loop.
137+
// 4. Without a terminator, parsing is considered unsuccessful, and it will never match now.
138+
// 5. If a parser matches but the position remains unchanged, it results in an infinite loop.
131139
//
132140
break;
133141
}

tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,30 @@ public void Repeat_5_10_Test()
5151

5252
Assert.That(parser.Parse("aaaa").Success, Is.False);
5353
}
54+
55+
[Test]
56+
[SuppressMessage("ReSharper", "InconsistentNaming")]
57+
public void Repeat_InfiniteLoopPrevention_ZeroLength()
58+
{
59+
var digit = Character.Digit;
60+
var digit_list = digit.Separated(L(','));
61+
var index_list = digit_list.Between(L('['), L(']')).Many();
62+
63+
Assert.That(index_list.Parse("[]").Length, Is.EqualTo(2));
64+
Assert.That(index_list.Parse("[][]").Length, Is.EqualTo(4));
65+
Assert.That(index_list.Parse("[][][]").Length, Is.EqualTo(6));
66+
67+
Assert.That(index_list.Parse("[1,2][1,2][2,6]").Length, Is.EqualTo(15));
68+
Assert.That(index_list.Parse("[][][1,2][][][1,2][][][2,6][][]").Length, Is.EqualTo(31));
69+
}
70+
71+
[Test]
72+
public void Repeat_InfiniteLoopPrevention_ZeroConsuming()
73+
{
74+
var parser = And(Character.Digit).AtLeast(2);
75+
76+
Assert.That(parser.Parse("1234567890").Success, Is.True);
77+
Assert.That(parser.Parse("1234567890").Length, Is.Zero);
78+
Assert.That(parser.Text().Parse("1234567890").Value, Is.Empty);
79+
}
5480
}

0 commit comments

Comments
 (0)