Throw, if `BlockSyntax.parseLines` loops indefinitely (dart-lang/markdown#533)
We throw an `AssertionError` if `BlockSyntax.parseLines` enters an
infinite loop. This should have no effect, if there are no bugs.
But if there is a bug, it's easier to discover it in production if the
failure mode isn't an infinite loop eating up CPU, but just an unhandled
exception.
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index fc0c1ac..efe2352 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -164,7 +164,9 @@
// number of cells, which makes a table like structure not be recognized.
BlockSyntax? neverMatch;
+ var iterationsWithoutProgress = 0;
while (!isDone) {
+ final positionBefore = _pos;
for (final syntax in blockSyntaxes) {
if (neverMatch == syntax) {
continue;
@@ -173,7 +175,6 @@
if (syntax.canParse(this)) {
_previousSyntax = _currentSyntax;
_currentSyntax = syntax;
- final positionBefore = _pos;
final block = syntax.parse(this);
if (block != null) {
blocks.add(block);
@@ -189,6 +190,25 @@
break;
}
}
+ // Count the number of iterations without progress.
+ // This ensures that we don't have an infinite loop. And if we have an
+ // infinite loop, it's easier to gracefully recover from an error, than
+ // it is to discover an kill an isolate that's stuck in an infinite loop.
+ // Technically, it should be perfectly safe to remove this check
+ // But as it's possible to inject custom BlockSyntax implementations and
+ // combine existing ones, it is hard to promise that no combination can't
+ // trigger an infinite loop
+ if (positionBefore == _pos) {
+ iterationsWithoutProgress++;
+ if (iterationsWithoutProgress > 2) {
+ // If this happens we throw an error to avoid having the parser
+ // running in an infinite loop. An error is easier to handle.
+ // If you see this error in production please file a bug!
+ throw AssertionError('BlockParser.parseLines is not advancing');
+ }
+ } else {
+ iterationsWithoutProgress = 0;
+ }
}
return blocks;