Skip to content

Commit d81d1c0

Browse files
fix(ast): avoid panic in ConstantNode.String on non-JSON floats
1 parent 630bbf0 commit d81d1c0

3 files changed

Lines changed: 22 additions & 1 deletion

File tree

ast/print.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ func (n *ConstantNode) String() string {
4343
}
4444
b, err := json.Marshal(n.Value)
4545
if err != nil {
46-
panic(err)
46+
// json.Marshal rejects values such as NaN and ±Inf, which can
47+
// reach here after constant folding (e.g. an array literal like
48+
// [0/0]). Fall back to a non-panicking representation instead of
49+
// crashing the caller (the optimizer runs this during Compile).
50+
return fmt.Sprintf("%v", n.Value)
4751
}
4852
return string(b)
4953
}

ast/print_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ast_test
22

33
import (
4+
"math"
45
"testing"
56

67
"github.com/expr-lang/expr/internal/testify/assert"
@@ -120,6 +121,8 @@ func TestPrint_ConstantNode(t *testing.T) {
120121
{"a", `"a"`},
121122
{[]int{1, 2, 3}, `[1,2,3]`},
122123
{map[string]int{"a": 1}, `{"a":1}`},
124+
{[]any{math.NaN()}, `[NaN]`},
125+
{math.Inf(1), `+Inf`},
123126
}
124127

125128
for _, tt := range tests {

optimizer/optimizer_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,17 @@ func TestOptimize_predicate_combination_nested(t *testing.T) {
458458

459459
assert.Equal(t, ast.Dump(expected), ast.Dump(tree.Node))
460460
}
461+
462+
func TestOptimize_predicate_combination_with_non_json_float(t *testing.T) {
463+
// Constant folding turns [0/0] into a ConstantNode holding []any{NaN}.
464+
// predicateCombination compares the collection arguments via String(),
465+
// which must not panic on values json.Marshal rejects (NaN, ±Inf).
466+
for _, code := range []string{
467+
`any([0/0], # > 0) || any([0/0], # > 0)`,
468+
`all([1/0], # > 0) && all([1/0], # > 0)`,
469+
`none([0/0], # > 0) && none([0/0], # > 0)`,
470+
} {
471+
_, err := expr.Compile(code)
472+
require.NoError(t, err, code)
473+
}
474+
}

0 commit comments

Comments
 (0)