From d81d1c0468d8f30732868b26bc7dbc1103bfe9ca Mon Sep 17 00:00:00 2001 From: netliomax25-code Date: Tue, 2 Jun 2026 13:21:15 +0530 Subject: [PATCH] fix(ast): avoid panic in ConstantNode.String on non-JSON floats --- ast/print.go | 6 +++++- ast/print_test.go | 3 +++ optimizer/optimizer_test.go | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ast/print.go b/ast/print.go index 1c197445e..afc301714 100644 --- a/ast/print.go +++ b/ast/print.go @@ -43,7 +43,11 @@ func (n *ConstantNode) String() string { } b, err := json.Marshal(n.Value) if err != nil { - panic(err) + // json.Marshal rejects values such as NaN and ±Inf, which can + // reach here after constant folding (e.g. an array literal like + // [0/0]). Fall back to a non-panicking representation instead of + // crashing the caller (the optimizer runs this during Compile). + return fmt.Sprintf("%v", n.Value) } return string(b) } diff --git a/ast/print_test.go b/ast/print_test.go index bcdad782c..7b31b7273 100644 --- a/ast/print_test.go +++ b/ast/print_test.go @@ -1,6 +1,7 @@ package ast_test import ( + "math" "testing" "github.com/expr-lang/expr/internal/testify/assert" @@ -120,6 +121,8 @@ func TestPrint_ConstantNode(t *testing.T) { {"a", `"a"`}, {[]int{1, 2, 3}, `[1,2,3]`}, {map[string]int{"a": 1}, `{"a":1}`}, + {[]any{math.NaN()}, `[NaN]`}, + {math.Inf(1), `+Inf`}, } for _, tt := range tests { diff --git a/optimizer/optimizer_test.go b/optimizer/optimizer_test.go index 03458689f..5d7b4b8c7 100644 --- a/optimizer/optimizer_test.go +++ b/optimizer/optimizer_test.go @@ -458,3 +458,17 @@ func TestOptimize_predicate_combination_nested(t *testing.T) { assert.Equal(t, ast.Dump(expected), ast.Dump(tree.Node)) } + +func TestOptimize_predicate_combination_with_non_json_float(t *testing.T) { + // Constant folding turns [0/0] into a ConstantNode holding []any{NaN}. + // predicateCombination compares the collection arguments via String(), + // which must not panic on values json.Marshal rejects (NaN, ±Inf). + for _, code := range []string{ + `any([0/0], # > 0) || any([0/0], # > 0)`, + `all([1/0], # > 0) && all([1/0], # > 0)`, + `none([0/0], # > 0) && none([0/0], # > 0)`, + } { + _, err := expr.Compile(code) + require.NoError(t, err, code) + } +}