From ed658ee04630d35caf26ad4c512f8fbf4e44b0a0 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 11 Jun 2022 10:49:40 +0200 Subject: [PATCH 1/2] Fix loop in parser I noted a case in Metals where the compiler would keep running at 100+ % until the process was killed. Using jstack I tracked it down to an infinite `skip` caused by a `syntaxError` in `pattern3`. In fact, the syntaxError should not skip at this point since the offending expression was already fully parsed. I fixed this in this commit. The parser was invoked from the `signatureHelp` method. It seems it parsed something that was not syntactically correct (specifically, a postfix `*` appeared in a pattern where none was allowed). ` --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index bdf19ac7d013..30de3885e383 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2745,14 +2745,13 @@ object Parsers { def pattern3(): Tree = val p = infixPattern() if followingIsVararg() then - atSpan(in.skipToken()) { - p match - case p @ Ident(name) if name.isVarPattern => - Typed(p, Ident(tpnme.WILDCARD_STAR)) - case _ => - syntaxError(em"`*` must follow pattern variable") - p - } + val start = in.skipToken() + p match + case p @ Ident(name) if name.isVarPattern => + Typed(p, atSpan(start) { Ident(tpnme.WILDCARD_STAR) }) + case _ => + syntaxError(em"`*` must follow pattern variable", start) + p else p /** Pattern2 ::= [id `@'] Pattern3 From 59556fd4510d7b22b0e3c3dbeca648fa63b6d6c6 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 11 Jun 2022 16:14:03 +0200 Subject: [PATCH 2/2] Defensive code to make sure there's no infinite loop in `skip`. --- compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 93ce23cbc103..f987ecd595fe 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -313,8 +313,13 @@ object Scanners { // when skipping and therefore might erroneously end up syncing on a nested OUTDENT. if debugTokenStream then println(s"\nSTART SKIP AT ${sourcePos().line + 1}, $this in $currentRegion") - while !atStop do + var noProgress = 0 + // Defensive measure to ensure we always get out of the following while loop + // even if source file is weirly formatted (i.e. we never reach EOF + while !atStop && noProgress < 3 do + val prevOffset = offset nextToken() + if offset == prevOffset then noProgress += 1 else noProgress = 0 if debugTokenStream then println(s"\nSTOP SKIP AT ${sourcePos().line + 1}, $this in $currentRegion") if token == OUTDENT then dropUntil(_.isInstanceOf[Indented])