[scanner] Fix infinite loop in error recovery

Fixes https://github.com/dart-lang/sdk/issues/60785

Change-Id: Ida0750115e7582ca44b2a49419ed7ab56295caf6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/433701
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
This commit is contained in:
Jens Johansen
2025-06-11 08:02:46 -07:00
committed by Commit Queue
parent a69ff1830b
commit 9c86b8ba99
6 changed files with 406 additions and 1 deletions
@@ -439,7 +439,12 @@ abstract class AbstractScanner implements Scanner {
if (previous.isA(TokenType.CLOSE_PAREN)) {
Token closeParen = token.previous!;
Token? candidate = closeParen.previous;
while (candidate != null && candidate.endGroup != closeParen) {
while (candidate != null) {
if (candidate.endGroup == closeParen) break;
if (candidate.isEof) break;
if (candidate.endGroup != null) {
if (candidate.endGroup!.offset > closeParen.offset) break;
}
candidate = candidate.previous;
}
if (candidate?.endGroup == closeParen && candidate!.previous != null) {
@@ -0,0 +1,8 @@
void foo(String s) {
switch (s) {
case 'foo') {
print('foo');
} else {
print('bar');
}
}
@@ -0,0 +1,114 @@
Problems reported:
parser/general/issue_60785.crash:3:15: Expected ':' before this.
case 'foo') {
^
parser/general/issue_60785.crash:3:15: Expected an identifier, but got ')'.
case 'foo') {
^
parser/general/issue_60785.crash:3:10: Expected ';' after this.
case 'foo') {
^^^^^
parser/general/issue_60785.crash:3:15: Unexpected token ')'.
case 'foo') {
^
parser/general/issue_60785.crash:5:5: Expected an identifier, but got 'else'.
} else {
^^^^
parser/general/issue_60785.crash:5:3: Expected ';' after this.
} else {
^
parser/general/issue_60785.crash:5:5: Unexpected token 'else'.
} else {
^^^^
parser/general/issue_60785.crash:2:14: Can't find '}' to match '{'.
switch (s) {
^
beginCompilationUnit(void)
beginMetadataStar(void)
endMetadataStar(0)
beginTopLevelMember(void)
beginTopLevelMethod(UnmatchedToken({), null, null)
handleVoidKeyword(void)
handleIdentifier(foo, topLevelFunctionDeclaration)
handleNoTypeVariables(()
beginFormalParameters((, MemberKind.TopLevelMethod)
beginMetadataStar(String)
endMetadataStar(0)
beginFormalParameter(String, MemberKind.TopLevelMethod, null, null, null)
handleIdentifier(String, typeReference)
handleNoTypeArguments(s)
handleType(String, null)
handleIdentifier(s, formalParameterDeclaration)
handleFormalParameterWithoutValue())
endFormalParameter(null, null, null, s, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
endFormalParameters(1, (, ), MemberKind.TopLevelMethod)
handleAsyncModifier(null, null)
beginBlockFunctionBody({)
beginSwitchStatement(switch)
handleIdentifier(s, expression)
handleNoTypeArguments())
handleNoArguments())
handleSend(s, s)
handleParenthesizedCondition((, null, null)
beginSwitchBlock({)
beginCaseExpression(case)
beginLiteralString('foo')
endLiteralString(0, ))
handleSwitchCaseNoWhenClause('foo')
handleRecoverableError(Message[ExpectedButGot, Expected ':' before this., null, {string: :}], ), ))
endCaseExpression(case, null, :)
beginSwitchCase(0, 1, case)
handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got ')'., Try inserting an identifier before ')'., {lexeme: )}], ), ))
handleIdentifier(, expression)
handleNoTypeArguments())
handleNoArguments())
handleSend(, )
handleRecoverableError(Message[ExpectedAfterButGot, Expected ';' after this., null, {string: ;}], 'foo', 'foo')
handleExpressionStatement(), ;)
handleRecoverableError(Message[UnexpectedToken, Unexpected token ')'., null, {lexeme: )}], ), ))
beginBlock({, BlockKind(statement))
handleIdentifier(print, expression)
handleNoTypeArguments(()
beginArguments(()
beginLiteralString('foo')
endLiteralString(0, ))
endArguments(1, (, ))
handleSend(print, ))
handleExpressionStatement(print, ;)
endBlock(1, {, }, BlockKind(statement))
handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got 'else'., Try inserting an identifier before 'else'., {lexeme: else}], else, else)
handleIdentifier(, expression)
handleNoTypeArguments(else)
handleNoArguments(else)
handleSend(, )
handleRecoverableError(Message[ExpectedAfterButGot, Expected ';' after this., null, {string: ;}], }, })
handleExpressionStatement(else, ;)
handleRecoverableError(Message[UnexpectedToken, Unexpected token 'else'., null, {lexeme: else}], else, else)
beginBlock({, BlockKind(statement))
handleIdentifier(print, expression)
handleNoTypeArguments(()
beginArguments(()
beginLiteralString('bar')
endLiteralString(0, ))
endArguments(1, (, ))
handleSend(print, ))
handleExpressionStatement(print, ;)
endBlock(1, {, }, BlockKind(statement))
endSwitchCase(0, 1, null, null, 4, case, })
endSwitchBlock(1, {, })
endSwitchStatement(switch, })
endBlockFunctionBody(1, {, })
endTopLevelMethod(void, null, })
endTopLevelDeclaration(})
handleErrorToken(UnmatchedToken({))
handleRecoverableError(Message[UnmatchedToken, Can't find '}' to match '{'., null, {string: }, lexeme: {}], UnmatchedToken({), UnmatchedToken({))
endCompilationUnit(1, )
@@ -0,0 +1,238 @@
parseUnit(UnmatchedToken({))
skipErrorTokens(UnmatchedToken({))
listener: beginCompilationUnit(void)
syntheticPreviousToken(void)
parseTopLevelDeclarationImpl(UnmatchedToken({), DirectiveContext(DirectiveState.Unknown))
parseMetadataStar(UnmatchedToken({))
listener: beginMetadataStar(void)
listener: endMetadataStar(0)
parseTopLevelMemberImpl(UnmatchedToken({))
listener: beginTopLevelMember(void)
parseTopLevelMethod(UnmatchedToken({), null, null, UnmatchedToken({), VoidType(), null, foo, false)
listener: beginTopLevelMethod(UnmatchedToken({), null, null)
listener: handleVoidKeyword(void)
ensureIdentifierPotentiallyRecovered(void, topLevelFunctionDeclaration, false)
listener: handleIdentifier(foo, topLevelFunctionDeclaration)
parseMethodTypeVar(foo)
listener: handleNoTypeVariables(()
parseGetterOrFormalParameters(foo, foo, false, MemberKind.TopLevelMethod)
parseFormalParameters(foo, MemberKind.TopLevelMethod)
parseFormalParametersRest((, MemberKind.TopLevelMethod)
listener: beginFormalParameters((, MemberKind.TopLevelMethod)
parseFormalParameter((, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
parseMetadataStar(()
listener: beginMetadataStar(String)
listener: endMetadataStar(0)
listener: beginFormalParameter(String, MemberKind.TopLevelMethod, null, null, null)
listener: handleIdentifier(String, typeReference)
listener: handleNoTypeArguments(s)
listener: handleType(String, null)
ensureIdentifier(String, formalParameterDeclaration)
listener: handleIdentifier(s, formalParameterDeclaration)
listener: handleFormalParameterWithoutValue())
listener: endFormalParameter(null, null, null, s, null, null, FormalParameterKind.requiredPositional, MemberKind.TopLevelMethod)
listener: endFormalParameters(1, (, ), MemberKind.TopLevelMethod)
parseAsyncModifierOpt())
listener: handleAsyncModifier(null, null)
inPlainSync()
parseFunctionBody(), false, false)
listener: beginBlockFunctionBody({)
notEofOrValue(}, switch)
parseStatement({)
parseStatementX({)
parseSwitchStatement({)
listener: beginSwitchStatement(switch)
ensureParenthesizedCondition(switch, allowCase: false)
parseExpressionInParenthesisRest((, allowCase: false)
parseExpression(()
parsePrecedenceExpression((, 1, true, ConstantPatternContext.none)
parseUnaryExpression((, true, ConstantPatternContext.none)
parsePrimary((, expression, ConstantPatternContext.none)
parseSendOrFunctionLiteral((, expression, ConstantPatternContext.none)
parseSend((, expression, ConstantPatternContext.none)
isNextIdentifier(()
ensureIdentifier((, expression)
listener: handleIdentifier(s, expression)
listener: handleNoTypeArguments())
parseArgumentsOpt(s)
listener: handleNoArguments())
listener: handleSend(s, s)
ensureCloseParen(s, ()
listener: handleParenthesizedCondition((, null, null)
parseSwitchBlock())
ensureBlock(), BlockKind(switch statement))
listener: beginSwitchBlock({)
notEofOrValue(}, case)
peekPastLabels(case)
listener: beginCaseExpression(case)
parseExpression(case)
parsePrecedenceExpression(case, 1, true, ConstantPatternContext.none)
parseUnaryExpression(case, true, ConstantPatternContext.none)
parsePrimary(case, expression, ConstantPatternContext.none)
parseLiteralString(case)
parseSingleLiteralString(case)
listener: beginLiteralString('foo')
listener: endLiteralString(0, ))
listener: handleSwitchCaseNoWhenClause('foo')
ensureColon('foo')
rewriteAndRecover('foo', Message[ExpectedButGot, Expected ':' before this., null, {string: :}], :)
reportRecoverableError(), Message[ExpectedButGot, Expected ':' before this., null, {string: :}])
listener: handleRecoverableError(Message[ExpectedButGot, Expected ':' before this., null, {string: :}], ), ))
rewriter()
listener: endCaseExpression(case, null, :)
peekPastLabels())
parseStatementsInSwitchCase(:, ), case, 0, 1, null, null)
listener: beginSwitchCase(0, 1, case)
parseStatement(:)
parseStatementX(:)
parseExpressionStatementOrDeclaration(:, null)
parseExpressionStatementOrDeclarationAfterModifiers(:, :, null, null, null, null)
looksLikeLocalFunction())
parseExpressionStatement(:)
parseExpression(:)
parsePrecedenceExpression(:, 1, true, ConstantPatternContext.none)
parseUnaryExpression(:, true, ConstantPatternContext.none)
parsePrimary(:, expression, ConstantPatternContext.none)
parseSend(:, expression, ConstantPatternContext.none)
isNextIdentifier(:)
ensureIdentifier(:, expression)
reportRecoverableErrorWithToken(), Template(ExpectedIdentifier))
listener: handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got ')'., Try inserting an identifier before ')'., {lexeme: )}], ), ))
rewriter()
listener: handleIdentifier(, expression)
listener: handleNoTypeArguments())
parseArgumentsOpt()
listener: handleNoArguments())
listener: handleSend(, )
ensureSemicolon()
reportRecoverableError('foo', Message[ExpectedAfterButGot, Expected ';' after this., null, {string: ;}])
listener: handleRecoverableError(Message[ExpectedAfterButGot, Expected ';' after this., null, {string: ;}], 'foo', 'foo')
rewriter()
listener: handleExpressionStatement(), ;)
reportRecoverableError(), Message[UnexpectedToken, Unexpected token ')'., null, {lexeme: )}])
listener: handleRecoverableError(Message[UnexpectedToken, Unexpected token ')'., null, {lexeme: )}], ), ))
peekPastLabels({)
parseStatement())
parseStatementX())
parseBlock(), BlockKind(statement))
ensureBlock(), BlockKind(statement))
listener: beginBlock({, BlockKind(statement))
notEofOrValue(}, print)
parseStatement({)
parseStatementX({)
parseExpressionStatementOrDeclarationAfterModifiers({, {, null, null, null, null)
looksLikeLocalFunction(print)
parseExpressionStatement({)
parseExpression({)
parsePrecedenceExpression({, 1, true, ConstantPatternContext.none)
parseUnaryExpression({, true, ConstantPatternContext.none)
parsePrimary({, expression, ConstantPatternContext.none)
parseSendOrFunctionLiteral({, expression, ConstantPatternContext.none)
looksLikeFunctionBody(;)
parseSend({, expression, ConstantPatternContext.none)
isNextIdentifier({)
ensureIdentifier({, expression)
listener: handleIdentifier(print, expression)
listener: handleNoTypeArguments(()
parseArgumentsOpt(print)
parseArguments(print)
parseArgumentsRest(()
listener: beginArguments(()
parseExpression(()
parsePrecedenceExpression((, 1, true, ConstantPatternContext.none)
parseUnaryExpression((, true, ConstantPatternContext.none)
parsePrimary((, expression, ConstantPatternContext.none)
parseLiteralString(()
parseSingleLiteralString(()
listener: beginLiteralString('foo')
listener: endLiteralString(0, ))
listener: endArguments(1, (, ))
listener: handleSend(print, ))
ensureSemicolon())
listener: handleExpressionStatement(print, ;)
notEofOrValue(}, })
listener: endBlock(1, {, }, BlockKind(statement))
peekPastLabels(else)
parseStatement(})
parseStatementX(})
parseExpressionStatementOrDeclaration(}, null)
parseExpressionStatementOrDeclarationAfterModifiers(}, }, null, null, null, null)
looksLikeLocalFunction(else)
parseExpressionStatement(})
parseExpression(})
parsePrecedenceExpression(}, 1, true, ConstantPatternContext.none)
parseUnaryExpression(}, true, ConstantPatternContext.none)
parsePrimary(}, expression, ConstantPatternContext.none)
inPlainSync()
parseSend(}, expression, ConstantPatternContext.none)
isNextIdentifier(})
ensureIdentifier(}, expression)
reportRecoverableErrorWithToken(else, Template(ExpectedIdentifier))
listener: handleRecoverableError(Message[ExpectedIdentifier, Expected an identifier, but got 'else'., Try inserting an identifier before 'else'., {lexeme: else}], else, else)
rewriter()
listener: handleIdentifier(, expression)
listener: handleNoTypeArguments(else)
parseArgumentsOpt()
listener: handleNoArguments(else)
listener: handleSend(, )
ensureSemicolon()
reportRecoverableError(}, Message[ExpectedAfterButGot, Expected ';' after this., null, {string: ;}])
listener: handleRecoverableError(Message[ExpectedAfterButGot, Expected ';' after this., null, {string: ;}], }, })
rewriter()
listener: handleExpressionStatement(else, ;)
reportRecoverableError(else, Message[UnexpectedToken, Unexpected token 'else'., null, {lexeme: else}])
listener: handleRecoverableError(Message[UnexpectedToken, Unexpected token 'else'., null, {lexeme: else}], else, else)
peekPastLabels({)
parseStatement(else)
parseStatementX(else)
parseBlock(else, BlockKind(statement))
ensureBlock(else, BlockKind(statement))
listener: beginBlock({, BlockKind(statement))
notEofOrValue(}, print)
parseStatement({)
parseStatementX({)
parseExpressionStatementOrDeclarationAfterModifiers({, {, null, null, null, null)
looksLikeLocalFunction(print)
parseExpressionStatement({)
parseExpression({)
parsePrecedenceExpression({, 1, true, ConstantPatternContext.none)
parseUnaryExpression({, true, ConstantPatternContext.none)
parsePrimary({, expression, ConstantPatternContext.none)
parseSendOrFunctionLiteral({, expression, ConstantPatternContext.none)
looksLikeFunctionBody(;)
parseSend({, expression, ConstantPatternContext.none)
isNextIdentifier({)
ensureIdentifier({, expression)
listener: handleIdentifier(print, expression)
listener: handleNoTypeArguments(()
parseArgumentsOpt(print)
parseArguments(print)
parseArgumentsRest(()
listener: beginArguments(()
parseExpression(()
parsePrecedenceExpression((, 1, true, ConstantPatternContext.none)
parseUnaryExpression((, true, ConstantPatternContext.none)
parsePrimary((, expression, ConstantPatternContext.none)
parseLiteralString(()
parseSingleLiteralString(()
listener: beginLiteralString('bar')
listener: endLiteralString(0, ))
listener: endArguments(1, (, ))
listener: handleSend(print, ))
ensureSemicolon())
listener: handleExpressionStatement(print, ;)
notEofOrValue(}, })
listener: endBlock(1, {, }, BlockKind(statement))
peekPastLabels(})
listener: endSwitchCase(0, 1, null, null, 4, case, })
notEofOrValue(}, })
listener: endSwitchBlock(1, {, })
listener: endSwitchStatement(switch, })
notEofOrValue(}, })
listener: endBlockFunctionBody(1, {, })
listener: endTopLevelMethod(void, null, })
listener: endTopLevelDeclaration(})
reportAllErrorTokens(UnmatchedToken({))
listener: handleErrorToken(UnmatchedToken({))
listener: handleRecoverableError(Message[UnmatchedToken, Can't find '}' to match '{'., null, {string: }, lexeme: {}], UnmatchedToken({), UnmatchedToken({))
listener: endCompilationUnit(1, )
@@ -0,0 +1,21 @@
NOTICE: Stream was rewritten by parser!
void foo(String s) {
switch (s) {
case 'foo':*synthetic*;) {
print('foo');
} *synthetic*;else {
print('bar');
}
}}
[UnmatchedToken]void[KeywordToken] foo[StringToken]([BeginToken]String[StringToken] s[StringToken])[SimpleToken] {[BeginToken]
switch[KeywordToken] ([BeginToken]s[StringToken])[SimpleToken] {[BeginToken]
case[KeywordToken] 'foo'[StringToken]:[SyntheticToken][SyntheticStringToken];[SyntheticToken])[SimpleToken] {[BeginToken]
print[StringToken]([BeginToken]'foo'[StringToken])[SimpleToken];[SimpleToken]
}[SimpleToken] [SyntheticStringToken];[SyntheticToken]else[KeywordToken] {[BeginToken]
print[StringToken]([BeginToken]'bar'[StringToken])[SimpleToken];[SimpleToken]
}[SimpleToken]
}[SyntheticToken]}[SimpleToken]
[SimpleToken]
@@ -0,0 +1,19 @@
void foo(String s) {
switch (s) {
case 'foo') {
print('foo');
} else {
print('bar');
}
}}
[UnmatchedToken]void[KeywordToken] foo[StringToken]([BeginToken]String[StringToken] s[StringToken])[SimpleToken] {[BeginToken]
switch[KeywordToken] ([BeginToken]s[StringToken])[SimpleToken] {[BeginToken]
case[KeywordToken] 'foo'[StringToken])[SimpleToken] {[BeginToken]
print[StringToken]([BeginToken]'foo'[StringToken])[SimpleToken];[SimpleToken]
}[SimpleToken] else[KeywordToken] {[BeginToken]
print[StringToken]([BeginToken]'bar'[StringToken])[SimpleToken];[SimpleToken]
}[SimpleToken]
}[SyntheticToken]}[SimpleToken]
[SimpleToken]