From d768691f462c2e614482621224426a638d0fc6b4 Mon Sep 17 00:00:00 2001 From: dplotnikov Date: Fri, 18 Sep 2026 16:34:38 -0700 Subject: [PATCH 1/2] Enforce recursion depth limits for ternary expressions in the Pratt parser. --- parser/parser_test.go | 34 ++++++++++++++++++++++++++++++++++ parser/pratt_parser.go | 9 ++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/parser/parser_test.go b/parser/parser_test.go index 85e3190c9..8dd06a9bf 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1323,6 +1323,14 @@ ERROR: :1:34: Syntax error: expected ']' | [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ | .................................^`, }, + { + I: "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : " + + "a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : a ? b : c", + E: "ERROR: :-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 @@ -2854,9 +2862,35 @@ func TestRecursionLimit(t *testing.T) { t.Errorf("expected recursion limit error, got none") } }) + + t.Run("DeeplyNestedTernaryLimitExceeded", func(t *testing.T) { + p, err := NewParser(MaxRecursionDepth(4), EnablePrattParser(pratt)) + if err != nil { + t.Fatalf("NewParser() failed: %v", err) + } + _, errs := p.Parse(common.NewTextSource("a ? b : a ? b : a ? b : a ? b : a ? b : c")) + if len(errs.GetErrors()) == 0 { + t.Errorf("expected recursion limit error, got none") + } + }) }) } + t.Run("PrattDeeplyNestedTernary", func(t *testing.T) { + p, err := NewParser(MaxRecursionDepth(4), EnablePrattParser(true)) + if err != nil { + t.Fatalf("NewParser() failed: %v", err) + } + _, errs := p.Parse(common.NewTextSource("a ? b : a ? b : a ? b : a ? b : c")) + if len(errs.GetErrors()) > 0 { + t.Errorf("unexpected recursion limit error at depth 4: %s", errs.ToDisplayString()) + } + _, errs = p.Parse(common.NewTextSource("a ? b : a ? b : a ? b : a ? b : a ? b : c")) + if len(errs.GetErrors()) == 0 { + t.Errorf("expected recursion limit error at depth 5, got none") + } + }) + t.Run("PrattSequentialScopesDoNotAccumulateDepth", func(t *testing.T) { p, err := NewParser(MaxRecursionDepth(2), EnablePrattParser(true)) if err != nil { diff --git a/parser/pratt_parser.go b/parser/pratt_parser.go index e38adda1a..436f43a8b 100644 --- a/parser/pratt_parser.go +++ b/parser/pratt_parser.go @@ -412,7 +412,7 @@ func (p *prattParserWorker) parseExpr() ast.Expr { func (p *prattParserWorker) parseBinaryAndTernary(minPrec int) ast.Expr { lhs := p.parseSelectorChain() - for { + for !p.recursionLimitExceeded && !p.isRecoveryLimitExceeded() { tok := p.peekTok.kind if tok == tokQuestion && minPrec <= 0 { lhs = p.parseTernary(lhs) @@ -438,6 +438,13 @@ func (p *prattParserWorker) parseBinaryAndTernary(minPrec int) ast.Expr { } func (p *prattParserWorker) parseTernary(lhs ast.Expr) ast.Expr { + if p.recursionDepth > p.maxRecursionDepth { + p.recursionLimitExceeded = true + p.errors.internalError(fmt.Sprintf("expression recursion limit exceeded: %d", p.maxRecursionDepth)) + return lhs + } + p.recursionDepth++ + defer func() { p.recursionDepth-- }() qTok := p.nextToken() opID := p.nextID(qTok) trueExpr := p.parseBinaryAndTernary(1) From 84130233c1634fd6a664c8d2b8a8ffbd2c871a3c Mon Sep 17 00:00:00 2001 From: dplotnikov Date: Fri, 18 Sep 2026 16:37:44 -0700 Subject: [PATCH 2/2] Use explicit recursionDepth decrement in parseTernary instead of defer. --- parser/pratt_parser.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parser/pratt_parser.go b/parser/pratt_parser.go index 436f43a8b..30bc2f62e 100644 --- a/parser/pratt_parser.go +++ b/parser/pratt_parser.go @@ -444,14 +444,15 @@ func (p *prattParserWorker) parseTernary(lhs ast.Expr) ast.Expr { return lhs } p.recursionDepth++ - defer func() { p.recursionDepth-- }() qTok := p.nextToken() opID := p.nextID(qTok) trueExpr := p.parseBinaryAndTernary(1) if !p.expect(tokColon, "expected ':' in conditional expression") { + p.recursionDepth-- return lhs } falseExpr := p.parseBinaryAndTernary(0) + p.recursionDepth-- return p.helper.newGlobalCall(opID, operators.Conditional, lhs, trueExpr, falseExpr) }