Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change recursion checks to be rule based, better match the spec #443

Merged
merged 2 commits into from
Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 29 additions & 16 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func NewParser(opts ...Option) (*Parser, error) {
}
}
if p.maxRecursionDepth == 0 {
p.maxRecursionDepth = 250
p.maxRecursionDepth = 200
}
if p.maxRecursionDepth == -1 {
p.maxRecursionDepth = int((^uint(0)) >> 1)
Expand Down Expand Up @@ -158,29 +158,41 @@ func (re *recursionError) Error() string {
var _ error = &recursionError{}

type recursionListener struct {
maxDepth int
depth int
maxDepth int
ruleTypeDepth map[int]*int
}

func (rl *recursionListener) VisitTerminal(node antlr.TerminalNode) {}

func (rl *recursionListener) VisitErrorNode(node antlr.ErrorNode) {}

func (rl *recursionListener) EnterEveryRule(ctx antlr.ParserRuleContext) {
if ctx != nil && ctx.GetRuleIndex() == gen.CELParserRULE_expr {
if rl.depth >= rl.maxDepth {
rl.depth++
panic(&recursionError{
message: fmt.Sprintf("expression recursion limit exceeded: %d", rl.maxDepth),
})
}
rl.depth++
if ctx == nil {
return
}
ruleIndex := ctx.GetRuleIndex()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Speed-wise, it might make more sense to use map[int]*int. That way you do not need to do do both a lookup and insert into a map each time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is safe. I remember having to put the increment after the recursion check. But looking at ANTLR Go, it appears that if EnterEveryRule panics, ExitEveryRule should not be called. Might want to double-check, otherwise things could go negative.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added a defensive check since I'm using a pointer now as well.

depth, found := rl.ruleTypeDepth[ruleIndex]
if !found {
var counter = 1
rl.ruleTypeDepth[ruleIndex] = &counter
depth = &counter
} else {
*depth++
}
if *depth >= rl.maxDepth {
panic(&recursionError{
message: fmt.Sprintf("expression recursion limit exceeded: %d", rl.maxDepth),
})
}
}

func (rl *recursionListener) ExitEveryRule(ctx antlr.ParserRuleContext) {
if ctx != nil && ctx.GetRuleIndex() == gen.CELParserRULE_expr {
rl.depth--
if ctx == nil {
return
}
ruleIndex := ctx.GetRuleIndex()
if depth, found := rl.ruleTypeDepth[ruleIndex]; found && *depth > 0 {
*depth--
}
}

Expand Down Expand Up @@ -214,7 +226,7 @@ func (rl *recoveryLimitErrorStrategy) RecoverInline(recognizer antlr.Parser) ant
}

func (rl *recoveryLimitErrorStrategy) checkAttempts(recognizer antlr.Parser) {
if rl.attempts >= rl.maxAttempts {
if rl.attempts == rl.maxAttempts {
rl.attempts++
msg := fmt.Sprintf("error recovery attempt limit exceeded: %d", rl.maxAttempts)
recognizer.NotifyErrorListeners(msg, nil, nil)
Expand Down Expand Up @@ -263,7 +275,8 @@ func (p *parser) parse(expr runes.Buffer, desc string) *exprpb.Expr {
// Unfortunately ANTLR Go runtime is missing (*antlr.BaseParser).RemoveParseListeners, so this is
// good enough until that is exported.
prsrListener := &recursionListener{
maxDepth: p.maxRecursionDepth,
maxDepth: p.maxRecursionDepth,
ruleTypeDepth: map[int]*int{},
}

defer func() {
Expand Down Expand Up @@ -295,7 +308,7 @@ func (p *parser) parse(expr runes.Buffer, desc string) *exprpb.Expr {
case *recursionError:
p.errors.ReportError(common.NoLocation, "%s", err.message)
case *recoveryLimitError:
p.errors.ReportError(common.NoLocation, "%s", err.message)
// do nothing, listeners already notified and error reported.
default:
panic(val)
}
Expand Down
75 changes: 67 additions & 8 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1093,15 +1093,74 @@ ERROR: <input>:1:26: reserved identifier: var
| ?
| .^`,
},
{
I: `a ? b ((?))`,
E: `
ERROR: <input>:1:9: Syntax error: mismatched input '?' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| a ? b ((?))
| ........^
ERROR: <input>:1:10: Syntax error: mismatched input ')' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| a ? b ((?))
| .........^
ERROR: <input>:1:12: Syntax error: error recovery attempt limit exceeded: 4
| a ? b ((?))
| ...........^`,
},
{
I: `[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[['too many']]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]`,
E: "ERROR: <input>:-1:0: expression recursion limit exceeded: 250",
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[['too many']]]]]]]]]]]]]]]]]]]]]]]]]]]]
]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]`,
E: "ERROR: <input>:-1:0: expression recursion limit exceeded: 32",
},
{
I: `-[-1--1--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--3--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--3--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
--3-[-1--1--1--1---1-1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1-À1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0--1--1--1--1--0--3--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
--1--1---1--1--1--0--1--1--1--1--0--3--1--1--0--1--1--1
--1--0--1--1--1--3-[-1--1--1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1
--1--0--1--1--1--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1
--1--0--1--1--1--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1
--1--0--1--1--1--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1
--1--0--1--1--1--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--3--1--1--0--1--1--1
--1--0--1--1--1--3-[-1--1--1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1
--1--0--1--1--1--3-[-1--1--1--1---1--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--3--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1--1
--1---1--1--1--0--1--1--1--1--0--3--1--1--0--1`,
E: `
ERROR: <input>:-1:0: expression recursion limit exceeded: 32
ERROR: <input>:3:33: Syntax error: extraneous input '/' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| --3-[-1--1--1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
| ................................^
ERROR: <input>:8:33: Syntax error: extraneous input '/' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| --3-[-1--1--1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1
| ................................^
ERROR: <input>:11:17: Syntax error: token recognition error at: 'À'
| --1--1---1--1-À1--0--1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
| ................^
ERROR: <input>:14:23: Syntax error: extraneous input '/' expecting {'[', '{', '(', '.', '-', '!', 'true', 'false', 'null', NUM_FLOAT, NUM_INT, NUM_UINT, STRING, BYTES, IDENTIFIER}
| --1--1---1--1--1--0-/1--1--1--1--0--2--1--1--0--1--1--1--1--0--1--1--1--3-[-1--1
| ......................^`,
},
}

Expand Down Expand Up @@ -1181,7 +1240,7 @@ func (l *locationAdorner) GetMetadata(elem interface{}) string {
}

func TestParse(t *testing.T) {
p, err := NewParser(Macros(AllMacros...))
p, err := NewParser(Macros(AllMacros...), MaxRecursionDepth(32), ErrorRecoveryLimit(4))
if err != nil {
t.Fatal(err)
}
Expand Down