Skip to content

Commit

Permalink
fix(compiler): infinite loop in parser assignment expression with inv…
Browse files Browse the repository at this point in the history
…alid 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
  • Loading branch information
crisbeto authored and Pawel Kozlowski committed Aug 17, 2022
1 parent 3d9455e commit 0583227
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 1 deletion.
10 changes: 9 additions & 1 deletion packages/compiler/src/expression_parser/parser.ts
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions packages/compiler/test/expression_parser/parser_spec.ts
Expand Up @@ -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 \'=\'');
});
});
});

Expand Down

0 comments on commit 0583227

Please sign in to comment.