Skip to content
Open
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
13 changes: 13 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type contextFieldSet struct {
type parseContext struct {
lexer.PeekingLexer
depth int
parseDepth int
trace io.Writer
deepestError error
deepestErrorDepth int
Expand Down Expand Up @@ -124,6 +125,18 @@ func (p *parseContext) Stop(err error, branch *parseContext) bool {

func (p *parseContext) hasInfiniteLookahead() bool { return p.lookahead < 0 }

func (p *parseContext) enterStruct() error {
p.parseDepth++
if MaxParseDepth > 0 && p.parseDepth > MaxParseDepth {
p.parseDepth--
t := p.Peek()
return Errorf(t.Pos, "maximum parse depth of %d exceeded", MaxParseDepth)
}
return nil
}

func (p *parseContext) leaveStruct() { p.parseDepth-- }

func (p *parseContext) printTrace(n node) func() {
if p.trace != nil {
tok := p.PeekingLexer.Peek()
Expand Down
9 changes: 9 additions & 0 deletions nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ var (
// MaxIterations limits the number of elements capturable by {}.
MaxIterations = 1000000

// MaxParseDepth limits recursive production nesting. Deeply nested input
// otherwise overflows the goroutine stack with a fatal error that recover
// cannot catch. Zero disables the limit.
MaxParseDepth = 1024

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think this should be configurable through a parser option.


positionType = reflect.TypeFor[lexer.Position]()
tokenType = reflect.TypeFor[lexer.Token]()
tokensType = reflect.TypeFor[[]lexer.Token]()
Expand Down Expand Up @@ -150,6 +155,10 @@ func (s *strct) GoString() string { return s.typ.Name() }

func (s *strct) Parse(ctx *parseContext, _ reflect.Value) (out []reflect.Value, err error) {
defer ctx.printTrace(s)()
if err := ctx.enterStruct(); err != nil {
return nil, err
}
defer ctx.leaveStruct()
sv := reflect.New(s.typ).Elem()
start := ctx.RawCursor()
applyStart := len(ctx.apply)
Expand Down
23 changes: 23 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1938,3 +1938,26 @@ func TestIssue216(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, &grammar{B: 4}, out)
}

func TestMaxParseDepth(t *testing.T) {
type expr struct {
Ident string `@Ident`
Inner *expr `| "(" @@ ")"`
}
parser := mustTestParser[expr](t)

got, err := parser.ParseString("", "(((x)))")
assert.NoError(t, err)
assert.Equal(t, "x", got.Inner.Inner.Inner.Ident)

old := participle.MaxParseDepth
participle.MaxParseDepth = 8
t.Cleanup(func() { participle.MaxParseDepth = old })

input := strings.Repeat("(", 40) + "x" + strings.Repeat(")", 40)
_, err = parser.ParseString("", input)
assert.Error(t, err)
if err == nil || !strings.Contains(err.Error(), "maximum parse depth of 8 exceeded") {
t.Fatalf("expected depth error, got %v", err)
}
}