From 431bcfa592c0b4775740cbad9a415a3fb5c99ad3 Mon Sep 17 00:00:00 2001 From: Ville Vesilehto Date: Sun, 30 Nov 2025 21:35:26 +0200 Subject: [PATCH] fix(vm): handle nil arguments in variadic funcs 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 --- test/issues/817/issue_test.go | 33 +++++++++++++++++++++++++++++++++ vm/vm.go | 20 ++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 test/issues/817/issue_test.go diff --git a/test/issues/817/issue_test.go b/test/issues/817/issue_test.go new file mode 100644 index 000000000..f397f9d1b --- /dev/null +++ b/test/issues/817/issue_test.go @@ -0,0 +1,33 @@ +package issue_test + +import ( + "fmt" + "testing" + + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/internal/testify/require" +) + +func TestIssue817_1(t *testing.T) { + out, err := expr.Eval( + `sprintf("result: %v %v", 1, nil)`, + map[string]any{ + "sprintf": fmt.Sprintf, + }, + ) + require.NoError(t, err) + require.Equal(t, "result: 1 ", out) +} + +func TestIssue817_2(t *testing.T) { + out, err := expr.Eval( + `thing(nil)`, + map[string]any{ + "thing": func(arg ...any) string { + return fmt.Sprintf("result: (%T) %v", arg[0], arg[0]) + }, + }, + ) + require.NoError(t, err) + require.Equal(t, "result: () ", out) +} diff --git a/vm/vm.go b/vm/vm.go index ed61d2f90..960009f0a 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -330,13 +330,29 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) { vm.push(runtime.Slice(node, from, to)) case OpCall: - fn := reflect.ValueOf(vm.pop()) + v := vm.pop() + if v == nil { + panic("invalid operation: cannot call nil") + } + fn := reflect.ValueOf(v) + if fn.Kind() != reflect.Func { + panic(fmt.Sprintf("invalid operation: cannot call non-function of type %T", v)) + } + fnType := fn.Type() size := arg in := make([]reflect.Value, size) + isVariadic := fnType.IsVariadic() + numIn := fnType.NumIn() for i := int(size) - 1; i >= 0; i-- { param := vm.pop() if param == nil { - in[i] = reflect.Zero(fn.Type().In(i)) + var inType reflect.Type + if isVariadic && i >= numIn-1 { + inType = fnType.In(numIn - 1).Elem() + } else { + inType = fnType.In(i) + } + in[i] = reflect.Zero(inType) } else { in[i] = reflect.ValueOf(param) }