From df2cccd267636baa18c7e19468ffa59c61222c9f Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Thu, 9 Mar 2023 23:49:13 +0100 Subject: [PATCH] Fix nil key check in map (#351) --- checker/checker.go | 2 +- expr_test.go | 39 +++++++++++++++++++++++++++++++++++++++ vm/runtime/runtime.go | 20 +++++++++++++++----- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/checker/checker.go b/checker/checker.go index 76e3d0be8..5ce9b31fa 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -417,7 +417,7 @@ func (v *visitor) MemberNode(node *ast.MemberNode) (reflect.Type, info) { return anyType, info{} case reflect.Map: - if !prop.AssignableTo(base.Key()) && !isAny(prop) { + if prop != nil && !prop.AssignableTo(base.Key()) && !isAny(prop) { return v.error(node.Property, "cannot use %v to get an element from %v", prop, base) } t, c := deref(base.Elem()) diff --git a/expr_test.go b/expr_test.go index a62e330e3..0549dfe72 100644 --- a/expr_test.go +++ b/expr_test.go @@ -1772,6 +1772,45 @@ func TestRun_NilCoalescingOperator(t *testing.T) { }) } +func TestEval_nil_in_maps(t *testing.T) { + env := map[string]interface{}{ + "m": map[interface{}]interface{}{nil: "bar"}, + "empty": map[interface{}]interface{}{}, + } + t.Run("nil key exists", func(t *testing.T) { + p, err := expr.Compile(`m[nil]`, expr.Env(env)) + assert.NoError(t, err) + + out, err := expr.Run(p, env) + assert.NoError(t, err) + assert.Equal(t, "bar", out) + }) + t.Run("no nil key", func(t *testing.T) { + p, err := expr.Compile(`empty[nil]`, expr.Env(env)) + assert.NoError(t, err) + + out, err := expr.Run(p, env) + assert.NoError(t, err) + assert.Equal(t, nil, out) + }) + t.Run("nil in m", func(t *testing.T) { + p, err := expr.Compile(`nil in m`, expr.Env(env)) + assert.NoError(t, err) + + out, err := expr.Run(p, env) + assert.NoError(t, err) + assert.Equal(t, true, out) + }) + t.Run("nil in empty", func(t *testing.T) { + p, err := expr.Compile(`nil in empty`, expr.Env(env)) + assert.NoError(t, err) + + out, err := expr.Run(p, env) + assert.NoError(t, err) + assert.Equal(t, false, out) + }) +} + // Mock types type mockEnv struct { diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go index 93ead17bf..b2eeb65d8 100644 --- a/vm/runtime/runtime.go +++ b/vm/runtime/runtime.go @@ -48,7 +48,12 @@ func Fetch(from, i interface{}) interface{} { } case reflect.Map: - value := v.MapIndex(reflect.ValueOf(i)) + var value reflect.Value + if i == nil { + value = v.MapIndex(reflect.Zero(v.Type().Key())) + } else { + value = v.MapIndex(reflect.ValueOf(i)) + } if value.IsValid() { return value.Interface() } else { @@ -221,11 +226,16 @@ func In(needle interface{}, array interface{}) bool { return false case reflect.Map: - n := reflect.ValueOf(needle) - if !n.IsValid() { - panic(fmt.Sprintf("cannot use %T as index to %T", needle, array)) + var value reflect.Value + if needle == nil { + value = v.MapIndex(reflect.Zero(v.Type().Key())) + } else { + n := reflect.ValueOf(needle) + if !n.IsValid() { + panic(fmt.Sprintf("cannot use %T as index to %T", needle, array)) + } + value = v.MapIndex(n) } - value := v.MapIndex(n) if value.IsValid() { return true }