Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,25 @@ func equal(lhsV, rhsV reflect.Value) bool {
return reflect.DeepEqual(lhsV.Interface(), rhsV.Interface())
}

// isHashable returns true if the value can be used as a map key without
// panicking. A nil interface is a valid map key even though
// reflect.Value.Comparable reports false for it.
func isHashable(v reflect.Value) bool {
if v.Kind() == reflect.Interface && v.IsNil() {
return true
}
return v.Comparable()
}

// hashableTypeString returns the type string of the value for error messages,
// unwrapping interfaces to show the dynamic type.
func hashableTypeString(v reflect.Value) string {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v.Type().String()
}

func getMapIndex(key reflect.Value, aMap reflect.Value) reflect.Value {
if aMap.IsNil() {
return nilValue
Expand All @@ -264,6 +283,10 @@ func getMapIndex(key reflect.Value, aMap reflect.Value) reflect.Value {
if err != nil {
return nilValue
}
if !isHashable(key) {
// an unhashable key can never be in a map
return nilValue
}

// From reflect MapIndex:
// It returns the zero Value if key is not found in the map or if v represents a nil map.
Expand Down
8 changes: 8 additions & 0 deletions vm/vmContainers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,14 @@ func TestMaps(t *testing.T) {
{Script: `b[1] = 1`, RunError: fmt.Errorf("undefined symbol 'b'")},
{Script: `z.y.x = 1`, RunError: fmt.Errorf("undefined symbol 'z'")},

// unhashable map keys should error instead of panic
{Script: `{[1,2]: 3}`, RunError: fmt.Errorf("type []interface {} cannot be used as map key")},
{Script: `a = {}; a[[1,2]] = 3`, RunError: fmt.Errorf("type []interface {} cannot be used as map key"), Output: map[string]interface{}{"a": map[interface{}]interface{}{}}},
{Script: `a = {}; a[{"x":1}] = 3`, RunError: fmt.Errorf("type map[interface {}]interface {} cannot be used as map key"), Output: map[string]interface{}{"a": map[interface{}]interface{}{}}},
{Script: `a = {}; a[[1,2]]`, RunOutput: nil, Output: map[string]interface{}{"a": map[interface{}]interface{}{}}},
{Script: `a = {"b": 1}; delete(a, [1,2])`, RunError: fmt.Errorf("type []interface {} cannot be used as map key in delete"), Output: map[string]interface{}{"a": map[interface{}]interface{}{"b": int64(1)}}},
{Script: `a = {}; a[nil] = 1; a[nil]`, RunOutput: int64(1)},

{Script: `{}`, RunOutput: map[interface{}]interface{}{}},
{Script: `{"b": nil}`, RunOutput: map[interface{}]interface{}{"b": nil}},
{Script: `{"b": true}`, RunOutput: map[interface{}]interface{}{"b": true}},
Expand Down
10 changes: 10 additions & 0 deletions vm/vmExpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ func (runInfo *runInfoStruct) invokeExpr() {
return
}
key = runInfo.rv
if !isHashable(key) {
runInfo.err = newStringError(expr, "type "+hashableTypeString(key)+" cannot be used as map key")
runInfo.rv = nilValue
return
}

runInfo.expr = expr.Values[i]
runInfo.invokeExpr()
Expand Down Expand Up @@ -132,6 +137,11 @@ func (runInfo *runInfoStruct) invokeExpr() {
runInfo.rv = nilValue
return
}
if !isHashable(key) {
runInfo.err = newStringError(expr, "type "+hashableTypeString(key)+" cannot be used as map key")
runInfo.rv = nilValue
return
}

runInfo.expr = expr.Values[i]
runInfo.invokeExpr()
Expand Down
5 changes: 5 additions & 0 deletions vm/vmLetExpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ func (runInfo *runInfoStruct) invokeLetExpr() {
runInfo.rv = nilValue
return
}
if !isHashable(runInfo.rv) {
runInfo.err = newStringError(expr, "type "+hashableTypeString(runInfo.rv)+" cannot be used as map key")
runInfo.rv = nilValue
return
}

value, runInfo.err = convertReflectValueToType(value, item.Type().Elem())
if runInfo.err != nil {
Expand Down
5 changes: 5 additions & 0 deletions vm/vmStmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,11 @@ func (runInfo *runInfoStruct) runSingleStmt() {
runInfo.rv = nilValue
return
}
if !isHashable(runInfo.rv) {
runInfo.err = newStringError(stmt, "type "+hashableTypeString(runInfo.rv)+" cannot be used as map key in delete")
runInfo.rv = nilValue
return
}
item.SetMapIndex(runInfo.rv, reflect.Value{})
runInfo.rv = nilValue
default:
Expand Down
Loading