Skip to content

Commit cf589a4

Browse files
fix(vm): handle nil arguments in variadic funcs (#868)
Update OpCall to properly handle nil values passed to variadic functions. Previously, passing nil as the last argument caused an index out of range panic, and a single nil argument was incorrectly treated as a nil slice. Added regression tests. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi> Co-authored-by: Anton Medvedev <anton@medv.io>
1 parent 73384e1 commit cf589a4

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

test/issues/817/issue_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package issue_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/expr-lang/expr"
8+
"github.com/expr-lang/expr/internal/testify/require"
9+
)
10+
11+
func TestIssue817_1(t *testing.T) {
12+
out, err := expr.Eval(
13+
`sprintf("result: %v %v", 1, nil)`,
14+
map[string]any{
15+
"sprintf": fmt.Sprintf,
16+
},
17+
)
18+
require.NoError(t, err)
19+
require.Equal(t, "result: 1 <nil>", out)
20+
}
21+
22+
func TestIssue817_2(t *testing.T) {
23+
out, err := expr.Eval(
24+
`thing(nil)`,
25+
map[string]any{
26+
"thing": func(arg ...any) string {
27+
return fmt.Sprintf("result: (%T) %v", arg[0], arg[0])
28+
},
29+
},
30+
)
31+
require.NoError(t, err)
32+
require.Equal(t, "result: (<nil>) <nil>", out)
33+
}

vm/vm.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,13 +330,29 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
330330
vm.push(runtime.Slice(node, from, to))
331331

332332
case OpCall:
333-
fn := reflect.ValueOf(vm.pop())
333+
v := vm.pop()
334+
if v == nil {
335+
panic("invalid operation: cannot call nil")
336+
}
337+
fn := reflect.ValueOf(v)
338+
if fn.Kind() != reflect.Func {
339+
panic(fmt.Sprintf("invalid operation: cannot call non-function of type %T", v))
340+
}
341+
fnType := fn.Type()
334342
size := arg
335343
in := make([]reflect.Value, size)
344+
isVariadic := fnType.IsVariadic()
345+
numIn := fnType.NumIn()
336346
for i := int(size) - 1; i >= 0; i-- {
337347
param := vm.pop()
338348
if param == nil {
339-
in[i] = reflect.Zero(fn.Type().In(i))
349+
var inType reflect.Type
350+
if isVariadic && i >= numIn-1 {
351+
inType = fnType.In(numIn - 1).Elem()
352+
} else {
353+
inType = fnType.In(i)
354+
}
355+
in[i] = reflect.Zero(inType)
340356
} else {
341357
in[i] = reflect.ValueOf(param)
342358
}

0 commit comments

Comments
 (0)