diff --git a/builtin/builtin_test.go b/builtin/builtin_test.go index a5dabbbb..ec424b54 100644 --- a/builtin/builtin_test.go +++ b/builtin/builtin_test.go @@ -150,6 +150,8 @@ func TestBuiltin(t *testing.T) { {`reduce(1..9, # + #acc)`, 45}, {`reduce([.5, 1.5, 2.5], # + #acc, 0)`, 4.5}, {`reduce([], 5, 0)`, 0}, + {`reduce(10..1, # + #acc, 100)`, 100}, + {`reduce([], # + #acc, 42)`, 42}, {`concat(ArrayOfString, ArrayOfInt)`, []any{"foo", "bar", "baz", 1, 2, 3}}, {`concat(PtrArrayWithNil, [nil])`, []any{42, nil}}, {`flatten([["a", "b"], [1, 2]])`, []any{"a", "b", 1, 2}}, diff --git a/compiler/compiler.go b/compiler/compiler.go index ed8942c9..f66cf9ed 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1124,9 +1124,18 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) { c.derefInNeeded(node.Arguments[2]) c.emit(OpSetAcc) } else { + // When no initial value is provided, we use the first element as the + // accumulator. But first we must check if the array is empty to avoid + // an index out of range panic. + empty := c.emit(OpJumpIfEnd, placeholder) c.emit(OpPointer) c.emit(OpIncrementIndex) c.emit(OpSetAcc) + jumpPastError := c.emit(OpJump, placeholder) + c.patchJump(empty) + c.emit(OpPush, c.addConstant(fmt.Errorf("reduce of empty array with no initial value"))) + c.emit(OpThrow) + c.patchJump(jumpPastError) } c.emitLoop(func() { c.compile(node.Arguments[1]) diff --git a/expr_test.go b/expr_test.go index 28b2c54b..1bce3c8d 100644 --- a/expr_test.go +++ b/expr_test.go @@ -1485,6 +1485,12 @@ func TestExpr_error(t *testing.T) { | ArrayOfAny[-7] | ..........^`, }, + { + `reduce(10..1, # + #acc)`, + `reduce of empty array with no initial value (1:1) + | reduce(10..1, # + #acc) + | ^`, + }, } for _, tt := range tests { diff --git a/test/fuzz/fuzz_test.go b/test/fuzz/fuzz_test.go index 91a85d4f..e24b96cd 100644 --- a/test/fuzz/fuzz_test.go +++ b/test/fuzz/fuzz_test.go @@ -54,6 +54,7 @@ func FuzzExpr(f *testing.F) { regexp.MustCompile(`operator "in" not defined on .*`), regexp.MustCompile(`cannot sum .*`), regexp.MustCompile(`index out of range: .* \(array length is .*\)`), + regexp.MustCompile(`reduce of empty array with no initial value`), regexp.MustCompile(`cannot use as argument \(type .*\) to call .*`), regexp.MustCompile(`illegal base64 data at input byte .*`), }