[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:
committed by
Commit Queue
parent
a69ff1830b
commit
9c86b8ba99
@@ -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]
|
||||
Reference in New Issue
Block a user