diff --git a/context.go b/context.go index 7fa12288..217c8cff 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,7 @@ type contextFieldSet struct { type parseContext struct { lexer.PeekingLexer depth int + parseDepth int trace io.Writer deepestError error deepestErrorDepth int @@ -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() diff --git a/nodes.go b/nodes.go index 2c6f393e..5437343d 100644 --- a/nodes.go +++ b/nodes.go @@ -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 + positionType = reflect.TypeFor[lexer.Position]() tokenType = reflect.TypeFor[lexer.Token]() tokensType = reflect.TypeFor[[]lexer.Token]() @@ -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) diff --git a/parser_test.go b/parser_test.go index 4a48237f..6ba888f1 100644 --- a/parser_test.go +++ b/parser_test.go @@ -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) + } +}