Skip to content

Commit dc52cef

Browse files
crisbetoPawel Kozlowski
authored andcommitted
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
1 parent 0ca5eb3 commit dc52cef

File tree

2 files changed

+18
-1
lines changed

2 files changed

+18
-1
lines changed

packages/compiler/src/expression_parser/parser.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,10 +586,18 @@ export class _ParseAST {
586586
while (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
587587
} // read all semicolons
588588
} else if (this.index < this.tokens.length) {
589+
const errorIndex = this.index;
589590
this.error(`Unexpected token '${this.next}'`);
591+
// The `error` call above will skip ahead to the next recovery point in an attempt to
592+
// recover part of the expression, but that might be the token we started from which will
593+
// lead to an infinite loop. If that's the case, break the loop assuming that we can't
594+
// parse further.
595+
if (this.index === errorIndex) {
596+
break;
597+
}
590598
}
591599
}
592-
if (exprs.length == 0) {
600+
if (exprs.length === 0) {
593601
// We have no expressions so create an empty expression that spans the entire input length
594602
const artificialStart = this.offset;
595603
const artificialEnd = this.offset + this.input.length;

packages/compiler/test/expression_parser/parser_spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,15 @@ describe('parser', () => {
321321
expect(ast.errors.length).toBe(1);
322322
expect(ast.errors[0].message).toContain('Unexpected token \'=\'');
323323
});
324+
325+
it('should recover on parenthesized empty rvalues', () => {
326+
const ast = parseAction('(a[1] = b) = c = d');
327+
expect(unparse(ast)).toEqual('a[1] = b');
328+
validate(ast);
329+
330+
expect(ast.errors.length).toBe(1);
331+
expect(ast.errors[0].message).toContain('Unexpected token \'=\'');
332+
});
324333
});
325334
});
326335

0 commit comments

Comments
 (0)