Skip to content

Commit

Permalink
fix: empty alts inside nested repetitions cause infinite loop
Browse files Browse the repository at this point in the history
fixes #1200
  • Loading branch information
bd82 committed Sep 4, 2020
1 parent a81b06c commit 0b2bc9f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 8 deletions.
6 changes: 6 additions & 0 deletions packages/chevrotain/docs/changes/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

#### Bug Fixes

- [Empty Alternative inside Nested Repetitions causes infinite loops on Parser initilization.](https://github.com/SAP/chevrotain/issues/1200)

## 7.0.1 (4-22-2020)

#### Bug Fixes

- [Broken diagram generation in 7.0.0.](https://github.com/SAP/chevrotain/issues/1186)

## 7.0.0 (4-18-2020)
Expand Down
7 changes: 6 additions & 1 deletion packages/chevrotain/src/parse/grammar/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,12 @@ export function possiblePathsFrom(
result = getAlternativesForProd(newDef)
} else if (prod instanceof Alternation) {
forEach(prod.definition, (currAlt) => {
result = getAlternativesForProd(currAlt.definition)
// TODO: this is a limited check for empty alternatives
// It would prevent a common case of infinite loops during parser initialization.
// However **in-directly** empty alternatives may still cause issues.
if (isEmpty(currAlt.definition) === false) {
result = getAlternativesForProd(currAlt.definition)
}
})
return result
} else if (prod instanceof Terminal) {
Expand Down
56 changes: 49 additions & 7 deletions packages/chevrotain/test/parse/recognizer/infinite_loop_spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { EmbeddedActionsParser } from "../../../src/parse/parser/traits/parser_traits"
import {
EmbeddedActionsParser,
CstParser
} from "../../../src/parse/parser/traits/parser_traits"
import { createRegularToken } from "../../utils/matchers"
import { augmentTokenTypes } from "../../../src/scan/tokens"
import { IToken } from "../../../api"
import { createToken } from "../../../src/scan/tokens_public"
import { EMPTY_ALT } from "../../../src/parse/parser/parser"

describe("The Recognizer's capabilities for detecting infinite loops", () => {
class PlusTok {
static PATTERN = /\+/
}
augmentTokenTypes(<any>[PlusTok])

describe("The Recognizer's capabilities for detecting / handling infinite loops", () => {
it("Will gracefully 'escape' from an infinite loop in a repetition", () => {
class PlusTok {
static PATTERN = /\+/
}
augmentTokenTypes(<any>[PlusTok])

class InfiniteLoopParser extends EmbeddedActionsParser {
constructor(input: IToken[] = []) {
super([PlusTok])
Expand Down Expand Up @@ -97,4 +101,42 @@ describe("The Recognizer's capabilities for detecting infinite loops", () => {
expect(parser.errors[0].message).to.match(/[A, B]/)
expect(parser.errors[0].message).to.match(/[A, C]/)
})

it("Will enter an infinite loop during parser initialization when there is an empty alternative inside nested repetitionn", () => {
// ----------------- lexer -----------------
const Comma = createToken({ name: "Comma", pattern: /,/ })
const Comma2 = createToken({ name: "Comma", pattern: /,/ })

const allTokens = [Comma]

class NestedManyEmptyAltBugParser extends CstParser {
constructor() {
super(allTokens)
this.performSelfAnalysis()
}

public A = this.RULE("A", () => {
this.MANY(() => {
this.SUBRULE(this.B)
})
})

public B = this.RULE("B", () => {
this.MANY(() => {
this.SUBRULE(this.C)
})
})

public C = this.RULE("C", () => {
this.OR([
{ ALT: () => this.CONSUME(Comma) },
{
ALT: EMPTY_ALT()
}
])
})
}

expect(() => new NestedManyEmptyAltBugParser()).to.not.throw()
})
})

0 comments on commit 0b2bc9f

Please sign in to comment.