From 05832277089f9df9cde424d62033661620826592 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sun, 14 Aug 2022 14:53:04 +0200 Subject: [PATCH] fix(compiler): infinite loop in parser assignment expression with invalid left-hand expression (#47151) In #39004 some logic was introduced that tries to recover invalid expressions by treating the `=` token as a recovery point. It works by skipping ahead to the next recovery point inside the `skip` method which is called whenever an error is reported. This can lead to an infinite loop inside the `parseChain` method which assumes that reporting an error would've skipped over the token, but that won't happen since the `=` token is a recovery point. These changes resolve the infinite loop by breaking the loop if `error` didn't skip to a different token after the error was reported. Fixes #47131. PR Close #47151 --- packages/compiler/src/expression_parser/parser.ts | 10 +++++++++- .../compiler/test/expression_parser/parser_spec.ts | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/expression_parser/parser.ts b/packages/compiler/src/expression_parser/parser.ts index 3d24447079a13..5b4c29948fc94 100644 --- a/packages/compiler/src/expression_parser/parser.ts +++ b/packages/compiler/src/expression_parser/parser.ts @@ -586,10 +586,18 @@ export class _ParseAST { while (this.consumeOptionalCharacter(chars.$SEMICOLON)) { } // read all semicolons } else if (this.index < this.tokens.length) { + const errorIndex = this.index; this.error(`Unexpected token '${this.next}'`); + // The `error` call above will skip ahead to the next recovery point in an attempt to + // recover part of the expression, but that might be the token we started from which will + // lead to an infinite loop. If that's the case, break the loop assuming that we can't + // parse further. + if (this.index === errorIndex) { + break; + } } } - if (exprs.length == 0) { + if (exprs.length === 0) { // We have no expressions so create an empty expression that spans the entire input length const artificialStart = this.offset; const artificialEnd = this.offset + this.input.length; diff --git a/packages/compiler/test/expression_parser/parser_spec.ts b/packages/compiler/test/expression_parser/parser_spec.ts index e7e6c5fe79fd9..94556aec6cd66 100644 --- a/packages/compiler/test/expression_parser/parser_spec.ts +++ b/packages/compiler/test/expression_parser/parser_spec.ts @@ -321,6 +321,15 @@ describe('parser', () => { expect(ast.errors.length).toBe(1); expect(ast.errors[0].message).toContain('Unexpected token \'=\''); }); + + it('should recover on parenthesized empty rvalues', () => { + const ast = parseAction('(a[1] = b) = c = d'); + expect(unparse(ast)).toEqual('a[1] = b'); + validate(ast); + + expect(ast.errors.length).toBe(1); + expect(ast.errors[0].message).toContain('Unexpected token \'=\''); + }); }); });